Merge "Handle cases when multiple type are animating"
diff --git a/Android.bp b/Android.bp
index 554ceae..eb9928c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -226,6 +226,7 @@
":framework-wifi-non-updatable-sources",
":PacProcessor-aidl-sources",
":ProxyHandler-aidl-sources",
+ ":net-utils-framework-common-srcs",
// AIDL from frameworks/base/native/
":platform-compat-native-aidl",
@@ -348,7 +349,6 @@
"android.hardware.vibrator-V1.1-java",
"android.hardware.vibrator-V1.2-java",
"android.hardware.vibrator-V1.3-java",
- "android.hardware.wifi-V1.0-java-constants",
"devicepolicyprotosnano",
"com.android.sysprop.apex",
@@ -409,6 +409,7 @@
filegroup {
name: "libincident_aidl",
srcs: [
+ "core/java/android/os/IIncidentDumpCallback.aidl",
"core/java/android/os/IIncidentManager.aidl",
"core/java/android/os/IIncidentReportStatusListener.aidl",
],
@@ -416,6 +417,13 @@
}
filegroup {
+ name: "graphicsstats_proto",
+ srcs: [
+ "libs/hwui/protos/graphicsstats.proto",
+ ],
+}
+
+filegroup {
name: "libvibrator_aidl",
srcs: [
"core/java/android/os/IExternalVibrationController.aidl",
@@ -499,7 +507,11 @@
defaults: ["framework-defaults"],
srcs: [":framework-all-sources"],
installable: false,
- static_libs: ["exoplayer2-core"],
+ static_libs: [
+ "exoplayer2-core",
+ "android.hardware.wifi-V1.0-java-constants",
+ ],
+ libs: ["icing-java-proto-lite"],
apex_available: ["//apex_available:platform"],
}
@@ -597,17 +609,22 @@
filegroup {
name: "framework-annotations",
srcs: [
+ "core/java/android/annotation/CallbackExecutor.java",
+ "core/java/android/annotation/CheckResult.java",
"core/java/android/annotation/IntDef.java",
"core/java/android/annotation/IntRange.java",
"core/java/android/annotation/NonNull.java",
"core/java/android/annotation/Nullable.java",
"core/java/android/annotation/RequiresPermission.java",
"core/java/android/annotation/SdkConstant.java",
+ "core/java/android/annotation/StringDef.java",
"core/java/android/annotation/SystemApi.java",
+ "core/java/android/annotation/SystemService.java",
"core/java/android/annotation/TestApi.java",
"core/java/android/annotation/UnsupportedAppUsage.java",
"core/java/com/android/internal/annotations/GuardedBy.java",
"core/java/com/android/internal/annotations/VisibleForTesting.java",
+ "core/java/com/android/internal/annotations/Immutable.java",
],
}
@@ -621,7 +638,11 @@
name: "framework-ike-shared-srcs",
visibility: ["//frameworks/opt/net/ike"],
srcs: [
+ "core/java/android/annotation/StringDef.java",
"core/java/android/net/annotations/PolicyDirection.java",
+ "core/java/com/android/internal/util/IState.java",
+ "core/java/com/android/internal/util/State.java",
+ "core/java/com/android/internal/util/StateMachine.java",
"telephony/java/android/telephony/Annotation.java",
],
}
@@ -1105,6 +1126,7 @@
"core/java/android/util/TimeUtils.java",
"core/java/com/android/internal/os/SomeArgs.java",
"core/java/com/android/internal/util/AsyncChannel.java",
+ "core/java/com/android/internal/util/AsyncService.java",
"core/java/com/android/internal/util/BitwiseInputStream.java",
"core/java/com/android/internal/util/FastXmlSerializer.java",
"core/java/com/android/internal/util/HexDump.java",
@@ -1141,13 +1163,36 @@
],
}
+// utility classes statically linked into framework-wifi and dynamically linked
+// into wifi-service
+java_library {
+ name: "framework-wifi-util-lib",
+ sdk_version: "core_current",
+ srcs: [
+ "core/java/android/content/pm/BaseParceledListSlice.java",
+ "core/java/android/content/pm/ParceledListSlice.java",
+ "core/java/android/net/shared/Inet4AddressUtils.java",
+ "core/java/android/os/HandlerExecutor.java",
+ "core/java/com/android/internal/util/AsyncChannel.java",
+ "core/java/com/android/internal/util/AsyncService.java",
+ "core/java/com/android/internal/util/Protocol.java",
+ "core/java/com/android/internal/util/Preconditions.java",
+ "telephony/java/android/telephony/Annotation.java",
+ ],
+ libs: [
+ "framework-annotations-lib",
+ "unsupportedappusage",
+ "android_system_stubs_current",
+ ],
+ visibility: ["//frameworks/base/wifi"],
+}
+
+// utility classes statically linked into wifi-service
filegroup {
name: "framework-wifi-service-shared-srcs",
srcs: [
- ":framework-annotations",
"core/java/android/net/InterfaceConfiguration.java",
"core/java/android/os/BasicShellCommandHandler.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",
@@ -1155,11 +1200,9 @@
"core/java/com/android/internal/util/HexDump.java",
"core/java/com/android/internal/util/IState.java",
"core/java/com/android/internal/util/MessageUtils.java",
- "core/java/com/android/internal/util/Preconditions.java",
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
"core/java/com/android/internal/util/WakeupMessage.java",
- "core/java/com/android/internal/util/XmlUtils.java",
],
}
diff --git a/ApiDocs.bp b/ApiDocs.bp
index e373db6..c40004c 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -121,8 +121,10 @@
doc_defaults {
name: "framework-docs-default",
- libs: framework_docs_only_libs +
- ["stub-annotations"],
+ libs: framework_docs_only_libs + [
+ "stub-annotations",
+ "unsupportedappusage",
+ ],
html_dirs: [
"docs/html",
],
diff --git a/StubLibraries.bp b/StubLibraries.bp
index d195047..baa3c61 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -41,10 +41,9 @@
]
stubs_defaults {
- name: "metalava-api-stubs-default",
+ name: "metalava-non-updatable-api-stubs-default",
srcs: [
":framework-non-updatable-sources",
- ":framework-updatable-sources",
"core/java/**/*.logtags",
":opt-telephony-srcs",
":opt-net-voip-srcs",
@@ -64,14 +63,23 @@
"sdk-dir",
"api-versions-jars-dir",
],
- sdk_version: "core_platform",
filter_packages: packages_to_document,
}
+stubs_defaults {
+ name: "metalava-api-stubs-default",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ srcs: [":framework-updatable-sources"],
+ sdk_version: "core_platform",
+}
+
/////////////////////////////////////////////////////////////////////
// *-api-stubs-docs modules providing source files for the stub libraries
/////////////////////////////////////////////////////////////////////
+// api-stubs-docs, system-api-stubs-docs, and test-api-stubs-docs have APIs
+// from the non-updatable part of the platform as well as from the updatable
+// modules
droidstubs {
name: "api-stubs-docs",
defaults: ["metalava-api-stubs-default"],
@@ -112,7 +120,10 @@
arg_files: [
"core/res/AndroidManifest.xml",
],
- args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)",
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
check_api: {
current: {
api_file: "api/system-current.txt",
@@ -155,6 +166,111 @@
}
/////////////////////////////////////////////////////////////////////
+// Following droidstubs modules are for extra APIs for modules.
+// The framework currently have two more API surfaces for modules:
+// @SystemApi(client=MODULE_APPS) and @SystemApi(client=MODULE_LIBRARIES)
+/////////////////////////////////////////////////////////////////////
+
+// TODO(b/146727827) remove the *-api modules when we can teach metalava
+// about the relationship among the API surfaces. Currently, these modules are only to generate
+// the API signature files and ensure that the APIs evolve in a backwards compatible manner.
+// They however are NOT used for building the API stub.
+droidstubs {
+ name: "module-app-api",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+ check_api: {
+ current: {
+ api_file: "api/module-app-current.txt",
+ removed_api_file: "api/module-app-removed.txt",
+ },
+ // TODO(b/147559833) enable the compatibility check against the last release API
+ // and the API lint
+ //last_released: {
+ // api_file: ":last-released-module-app-api",
+ // removed_api_file: "api/module-app-removed.txt",
+ // baseline_file: ":module-app-api-incompatibilities-with-last-released"
+ //},
+ //api_lint: {
+ // enabled: true,
+ // new_since: ":last-released-module-app-api",
+ // baseline_file: "api/module-app-lint-baseline.txt",
+ //},
+ },
+ //jdiff_enabled: true,
+}
+
+droidstubs {
+ name: "module-lib-api",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+ check_api: {
+ current: {
+ api_file: "api/module-lib-current.txt",
+ removed_api_file: "api/module-lib-removed.txt",
+ },
+ // TODO(b/147559833) enable the compatibility check against the last release API
+ // and the API lint
+ //last_released: {
+ // api_file: ":last-released-module-lib-api",
+ // removed_api_file: "api/module-lib-removed.txt",
+ // baseline_file: ":module-lib-api-incompatibilities-with-last-released"
+ //},
+ //api_lint: {
+ // enabled: true,
+ // new_since: ":last-released-module-lib-api",
+ // baseline_file: "api/module-lib-lint-baseline.txt",
+ //},
+ },
+ //jdiff_enabled: true,
+}
+
+// The following two droidstubs modules generate source files for the API stub libraries for
+// modules. Note that they not only include their own APIs but also other APIs that have
+// narrower scope. For example, module-lib-api-stubs-docs includes all @SystemApis not just
+// the ones with 'client=MODULE_LIBRARIES'.
+droidstubs {
+ name: "module-app-api-stubs-docs",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)" +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+droidstubs {
+ name: "module-lib-api-stubs-docs",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)" +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)" +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+/////////////////////////////////////////////////////////////////////
// android_*_stubs_current modules are the stubs libraries compiled
// from *-api-stubs-docs
/////////////////////////////////////////////////////////////////////
@@ -169,7 +285,6 @@
java_resources: [
":notices-for-framework-stubs",
],
- sdk_version: "core_current",
system_modules: "none",
java_version: "1.8",
compile_dex: true,
@@ -187,6 +302,7 @@
"private-stub-annotations-jar",
],
defaults: ["framework-stubs-default"],
+ sdk_version: "core_current",
}
java_library_static {
@@ -201,6 +317,7 @@
"private-stub-annotations-jar",
],
defaults: ["framework-stubs-default"],
+ sdk_version: "core_current",
}
java_library_static {
@@ -215,6 +332,37 @@
"private-stub-annotations-jar",
],
defaults: ["framework-stubs-default"],
+ sdk_version: "core_current",
+}
+
+java_library_static {
+ name: "framework_module_app_stubs_current",
+ srcs: [
+ ":module-app-api-stubs-docs",
+ ],
+ libs: [
+ "stub-annotations",
+ "framework-all",
+ ],
+ static_libs: [
+ "private-stub-annotations-jar",
+ ],
+ defaults: ["framework-stubs-default"],
+}
+
+java_library_static {
+ name: "framework_module_lib_stubs_current",
+ srcs: [
+ ":module-lib-api-stubs-docs",
+ ],
+ libs: [
+ "stub-annotations",
+ "framework-all",
+ ],
+ static_libs: [
+ "private-stub-annotations-jar",
+ ],
+ defaults: ["framework-stubs-default"],
}
/////////////////////////////////////////////////////////////////////
diff --git a/apex/Android.bp b/apex/Android.bp
index 56f7db2..abebfa3 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -37,6 +37,36 @@
stubs_defaults {
name: "framework-module-stubs-defaults-systemapi",
- args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) ",
+ args: mainline_stubs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) ",
+ installable: false,
+}
+
+stubs_defaults {
+ name: "framework-module-stubs-defaults-module_apps_api",
+ args: mainline_stubs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) " +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) ",
+ installable: false,
+}
+
+stubs_defaults {
+ name: "framework-module-stubs-defaults-module_libs_api",
+ args: mainline_stubs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) " +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) " +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+ "process=android.annotation.SystemApi.Process.ALL\\) ",
installable: false,
}
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 1f30dda..2e88115 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -13,29 +13,32 @@
// limitations under the License.
filegroup {
- name: "framework-appsearch-sources",
- srcs: [
- "java/**/*.java",
- "java/**/*.aidl",
- ],
- path: "java",
+ name: "framework-appsearch-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ],
+ path: "java",
}
java_library {
- name: "framework-appsearch",
- installable: true,
- sdk_version: "core_platform", // TODO(b/146218515) should be core_current
- srcs: [":framework-appsearch-sources"],
- hostdex: true, // for hiddenapi check
- libs: [
- "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs
- ],
- visibility: [
- "//frameworks/base/apex/appsearch:__subpackages__",
- // TODO(b/146218515) remove this when framework is built with the stub of appsearch
- "//frameworks/base",
- ],
- apex_available: ["com.android.appsearch"],
+ name: "framework-appsearch",
+ installable: true,
+ sdk_version: "core_platform", // TODO(b/146218515) should be core_current
+ srcs: [":framework-appsearch-sources"],
+ hostdex: true, // for hiddenapi check
+ libs: [
+ "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs
+ ],
+ static_libs: [
+ "icing-java-proto-lite",
+ ],
+ visibility: [
+ "//frameworks/base/apex/appsearch:__subpackages__",
+ // TODO(b/146218515) remove this when framework is built with the stub of appsearch
+ "//frameworks/base",
+ ],
+ apex_available: ["com.android.appsearch"],
}
metalava_appsearch_docs_args =
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java
new file mode 100644
index 0000000..e779b69
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java
@@ -0,0 +1,762 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import android.annotation.CurrentTimeSecondsLong;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyProto;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Collection of all AppSearch Document Types.
+ *
+ * @hide
+ */
+// TODO(b/143789408) Spilt this class to make all subclasses to their own file.
+public final class AppSearch {
+
+ private AppSearch() {}
+ /**
+ * Represents a document unit.
+ *
+ * <p>Documents are constructed via {@link Document.Builder}.
+ *
+ * @hide
+ */
+ // TODO(b/143789408) set TTL for document in mProtoBuilder
+ // TODO(b/144518768) add visibility field if the stakeholders are comfortable with a no-op
+ // opt-in for this release.
+ public static class Document {
+ private static final String TAG = "AppSearch.Document";
+
+ /**
+ * The maximum number of elements in a repeatable field. Will reject the request if exceed
+ * this limit.
+ */
+ private static final int MAX_REPEATED_PROPERTY_LENGTH = 100;
+
+ /**
+ * The maximum {@link String#length} of a {@link String} field. Will reject the request if
+ * {@link String}s longer than this.
+ */
+ private static final int MAX_STRING_LENGTH = 20_000;
+
+ /**
+ * Contains {@link Document} basic information (uri, schemaType etc) and properties ordered
+ * by keys.
+ */
+ @NonNull
+ private final DocumentProto mProto;
+
+ /** Contains all properties in {@link #mProto} to support get properties via keys. */
+ @NonNull
+ private final Bundle mPropertyBundle;
+
+ /**
+ * Create a new {@link Document}.
+ * @param proto Contains {@link Document} basic information (uri, schemaType etc) and
+ * properties ordered by keys.
+ * @param propertyBundle Contains all properties in {@link #mProto} to support get
+ * properties via keys.
+ */
+ private Document(@NonNull DocumentProto proto, @NonNull Bundle propertyBundle) {
+ this.mProto = proto;
+ this.mPropertyBundle = propertyBundle;
+ }
+
+ /**
+ * Create a new {@link Document} from an existing instance.
+ *
+ * <p>This method should be only used by constructor of a subclass.
+ */
+ // TODO(b/143789408) add constructor take DocumentProto to create a document.
+ protected Document(@NonNull Document document) {
+ this(document.mProto, document.mPropertyBundle);
+ }
+
+ /**
+ * Creates a new {@link Document.Builder}.
+ *
+ * @param uri The uri of {@link Document}.
+ * @param schemaType The schema type of the {@link Document}. The passed-in
+ * {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior to
+ * inserting a document of this {@code schemaType} into the AppSearch index using
+ * {@link AppSearchManager#put}. Otherwise, the document will be rejected by
+ * {@link AppSearchManager#put}.
+ * @hide
+ */
+ @NonNull
+ public static Builder newBuilder(@NonNull String uri, @NonNull String schemaType) {
+ return new Builder(uri, schemaType);
+ }
+
+ /**
+ * Get the {@link DocumentProto} of the {@link Document}.
+ *
+ * <p>The {@link DocumentProto} contains {@link Document}'s basic information and all
+ * properties ordered by keys.
+ * @hide
+ */
+ @NonNull
+ @VisibleForTesting
+ public DocumentProto getProto() {
+ return mProto;
+ }
+
+ /**
+ * Get the uri of the {@link Document}.
+ *
+ * @hide
+ */
+ @NonNull
+ public String getUri() {
+ return mProto.getUri();
+ }
+
+ /**
+ * Get the schema type of the {@link Document}.
+ * @hide
+ */
+ @NonNull
+ public String getSchemaType() {
+ return mProto.getSchema();
+ }
+
+ /**
+ * Get the creation timestamp in seconds of the {@link Document}.
+ *
+ * @hide
+ */
+ // TODO(b/143789408) Change seconds to millis with Icing library.
+ @CurrentTimeSecondsLong
+ public long getCreationTimestampSecs() {
+ return mProto.getCreationTimestampSecs();
+ }
+
+ /**
+ * Returns the score of the {@link Document}.
+ *
+ * <p>The score is a query-independent measure of the document's quality, relative to other
+ * {@link Document}s of the same type.
+ *
+ * <p>The default value is 0.
+ *
+ * @hide
+ */
+ public int getScore() {
+ return mProto.getScore();
+ }
+
+ /**
+ * Retrieve a {@link String} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@link String} associated with the given key or {@code null} if there
+ * is no such key or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public String getPropertyString(@NonNull String key) {
+ String[] propertyArray = getPropertyStringArray(key);
+ if (ArrayUtils.isEmpty(propertyArray)) {
+ return null;
+ }
+ warnIfSinglePropertyTooLong("String", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /**
+ * Retrieve a {@link Long} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@link Long} associated with the given key or {@code null} if there
+ * is no such key or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public Long getPropertyLong(@NonNull String key) {
+ long[] propertyArray = getPropertyLongArray(key);
+ if (ArrayUtils.isEmpty(propertyArray)) {
+ return null;
+ }
+ warnIfSinglePropertyTooLong("Long", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /**
+ * Retrieve a {@link Double} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@link Double} associated with the given key or {@code null} if there
+ * is no such key or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public Double getPropertyDouble(@NonNull String key) {
+ double[] propertyArray = getPropertyDoubleArray(key);
+ // TODO(tytytyww): Add support double array to ArraysUtils.isEmpty().
+ if (propertyArray == null || propertyArray.length == 0) {
+ return null;
+ }
+ warnIfSinglePropertyTooLong("Double", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /**
+ * Retrieve a {@link Boolean} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@link Boolean} associated with the given key or {@code null} if there
+ * is no such key or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public Boolean getPropertyBoolean(@NonNull String key) {
+ boolean[] propertyArray = getPropertyBooleanArray(key);
+ if (ArrayUtils.isEmpty(propertyArray)) {
+ return null;
+ }
+ warnIfSinglePropertyTooLong("Boolean", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /** Prints a warning to logcat if the given propertyLength is greater than 1. */
+ private static void warnIfSinglePropertyTooLong(
+ @NonNull String propertyType, @NonNull String key, int propertyLength) {
+ if (propertyLength > 1) {
+ Log.w(TAG, "The value for \"" + key + "\" contains " + propertyLength
+ + " elements. Only the first one will be returned from "
+ + "getProperty" + propertyType + "(). Try getProperty" + propertyType
+ + "Array().");
+ }
+ }
+
+ /**
+ * Retrieve a repeated {@code String} property by key.
+ *
+ * @param key The key to look for.
+ * @return The {@code String[]} associated with the given key, or {@code null} if no value
+ * is set or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public String[] getPropertyStringArray(@NonNull String key) {
+ return getAndCastPropertyArray(key, String[].class);
+ }
+
+ /**
+ * Retrieve a repeated {@code long} property by key.
+ *
+ * @param key The key to look for.
+ * @return The {@code long[]} associated with the given key, or {@code null} if no value is
+ * set or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public long[] getPropertyLongArray(@NonNull String key) {
+ return getAndCastPropertyArray(key, long[].class);
+ }
+
+ /**
+ * Retrieve a repeated {@code double} property by key.
+ *
+ * @param key The key to look for.
+ * @return The {@code double[]} associated with the given key, or {@code null} if no value
+ * is set or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public double[] getPropertyDoubleArray(@NonNull String key) {
+ return getAndCastPropertyArray(key, double[].class);
+ }
+
+ /**
+ * Retrieve a repeated {@code boolean} property by key.
+ *
+ * @param key The key to look for.
+ * @return The {@code boolean[]} associated with the given key, or {@code null} if no value
+ * is set or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public boolean[] getPropertyBooleanArray(@NonNull String key) {
+ return getAndCastPropertyArray(key, boolean[].class);
+ }
+
+ /**
+ * Gets a repeated property of the given key, and casts it to the given class type, which
+ * must be an array class type.
+ */
+ @Nullable
+ private <T> T getAndCastPropertyArray(@NonNull String key, @NonNull Class<T> tClass) {
+ Object value = mPropertyBundle.get(key);
+ if (value == null) {
+ return null;
+ }
+ try {
+ return tClass.cast(value);
+ } catch (ClassCastException e) {
+ Log.w(TAG, "Error casting to requested type for key \"" + key + "\"", e);
+ return null;
+ }
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ // Check only proto's equality is sufficient here since all properties in
+ // mPropertyBundle are ordered by keys and stored in proto.
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof Document)) {
+ return false;
+ }
+ Document otherDocument = (Document) other;
+ return this.mProto.equals(otherDocument.mProto);
+ }
+
+ @Override
+ public int hashCode() {
+ // Hash only proto is sufficient here since all properties in mPropertyBundle are
+ // ordered by keys and stored in proto.
+ return mProto.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return mProto.toString();
+ }
+
+ /**
+ * The builder class for {@link Document}.
+ *
+ * @param <BuilderType> Type of subclass who extend this.
+ * @hide
+ */
+ public static class Builder<BuilderType extends Builder> {
+
+ private final Bundle mPropertyBundle = new Bundle();
+ private final DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
+ private final BuilderType mBuilderTypeInstance;
+
+ /**
+ * Create a new {@link Document.Builder}.
+ *
+ * @param uri The uri of {@link Document}.
+ * @param schemaType The schema type of the {@link Document}. The passed-in
+ * {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior
+ * to inserting a document of this {@code schemaType} into the AppSearch index using
+ * {@link AppSearchManager#put}. Otherwise, the document will be rejected by
+ * {@link AppSearchManager#put}.
+ * @hide
+ */
+ protected Builder(@NonNull String uri, @NonNull String schemaType) {
+ mBuilderTypeInstance = (BuilderType) this;
+ mProtoBuilder.setUri(uri).setSchema(schemaType);
+ // Set current timestamp for creation timestamp by default.
+ setCreationTimestampSecs(
+ TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
+ }
+
+ /**
+ * Set the score of the {@link Document}.
+ *
+ * <p>The score is a query-independent measure of the document's quality, relative to
+ * other {@link Document}s of the same type.
+ *
+ * @throws IllegalArgumentException If the provided value is negative.
+ * @hide
+ */
+ @NonNull
+ public BuilderType setScore(@IntRange(from = 0, to = Integer.MAX_VALUE) int score) {
+ if (score < 0) {
+ throw new IllegalArgumentException("Document score cannot be negative");
+ }
+ mProtoBuilder.setScore(score);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Set the creation timestamp in seconds of the {@link Document}.
+ *
+ * @hide
+ */
+ @NonNull
+ public BuilderType setCreationTimestampSecs(
+ @CurrentTimeSecondsLong long creationTimestampSecs) {
+ mProtoBuilder.setCreationTimestampSecs(creationTimestampSecs);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Sets one or multiple {@code String} values for a property, replacing its previous
+ * values.
+ *
+ * @param key The key associated with the {@code values}.
+ * @param values The {@code String} values of the property.
+ * @hide
+ */
+ @NonNull
+ public BuilderType setProperty(@NonNull String key, @NonNull String... values) {
+ putInBundle(mPropertyBundle, key, values);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Sets one or multiple {@code boolean} values for a property, replacing its previous
+ * values.
+ *
+ * @param key The key associated with the {@code values}.
+ * @param values The {@code boolean} values of the schema.org property.
+ * @hide
+ */
+ @NonNull
+ public BuilderType setProperty(@NonNull String key, @NonNull boolean... values) {
+ putInBundle(mPropertyBundle, key, values);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Sets one or multiple {@code long} values for a property, replacing its previous
+ * values.
+ *
+ * @param key The key associated with the {@code values}.
+ * @param values The {@code long} values of the schema.org property.
+ * @hide
+ */
+ @NonNull
+ public BuilderType setProperty(@NonNull String key, @NonNull long... values) {
+ putInBundle(mPropertyBundle, key, values);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Sets one or multiple {@code double} values for a property, replacing its previous
+ * values.
+ *
+ * @param key The key associated with the {@code values}.
+ * @param values The {@code double} values of the schema.org property.
+ * @hide
+ */
+ @NonNull
+ public BuilderType setProperty(@NonNull String key, @NonNull double... values) {
+ putInBundle(mPropertyBundle, key, values);
+ return mBuilderTypeInstance;
+ }
+
+ private static void putInBundle(
+ @NonNull Bundle bundle, @NonNull String key, @NonNull String... values)
+ throws IllegalArgumentException {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
+ validateRepeatedPropertyLength(key, values.length);
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] == null) {
+ throw new IllegalArgumentException("The String at " + i + " is null.");
+ } else if (values[i].length() > MAX_STRING_LENGTH) {
+ throw new IllegalArgumentException("The String at " + i + " length is: "
+ + values[i].length() + ", which exceeds length limit: "
+ + MAX_STRING_LENGTH + ".");
+ }
+ }
+ bundle.putStringArray(key, values);
+ }
+
+ private static void putInBundle(
+ @NonNull Bundle bundle, @NonNull String key, @NonNull boolean... values) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
+ validateRepeatedPropertyLength(key, values.length);
+ bundle.putBooleanArray(key, values);
+ }
+
+ private static void putInBundle(
+ @NonNull Bundle bundle, @NonNull String key, @NonNull double... values) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
+ validateRepeatedPropertyLength(key, values.length);
+ bundle.putDoubleArray(key, values);
+ }
+
+ private static void putInBundle(
+ @NonNull Bundle bundle, @NonNull String key, @NonNull long... values) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
+ validateRepeatedPropertyLength(key, values.length);
+ bundle.putLongArray(key, values);
+ }
+
+ private static void validateRepeatedPropertyLength(@NonNull String key, int length) {
+ if (length == 0) {
+ throw new IllegalArgumentException("The input array is empty.");
+ } else if (length > MAX_REPEATED_PROPERTY_LENGTH) {
+ throw new IllegalArgumentException(
+ "Repeated property \"" + key + "\" has length " + length
+ + ", which exceeds the limit of "
+ + MAX_REPEATED_PROPERTY_LENGTH);
+ }
+ }
+
+ /**
+ * Builds the {@link Document} object.
+ * @hide
+ */
+ public Document build() {
+ // Build proto by sorting the keys in propertyBundle to exclude the influence of
+ // order. Therefore documents will generate same proto as long as the contents are
+ // same. Note that the order of repeated fields is still preserved.
+ ArrayList<String> keys = new ArrayList<>(mPropertyBundle.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ Object values = mPropertyBundle.get(key);
+ PropertyProto.Builder propertyProto = PropertyProto.newBuilder().setName(key);
+ if (values instanceof boolean[]) {
+ for (boolean value : (boolean[]) values) {
+ propertyProto.addBooleanValues(value);
+ }
+ } else if (values instanceof long[]) {
+ for (long value : (long[]) values) {
+ propertyProto.addInt64Values(value);
+ }
+ } else if (values instanceof double[]) {
+ for (double value : (double[]) values) {
+ propertyProto.addDoubleValues(value);
+ }
+ } else if (values instanceof String[]) {
+ for (String value : (String[]) values) {
+ propertyProto.addStringValues(value);
+ }
+ } else {
+ throw new IllegalStateException(
+ "Property \"" + key + "\" has unsupported value type \""
+ + values.getClass().getSimpleName() + "\"");
+ }
+ mProtoBuilder.addProperties(propertyProto);
+ }
+ return new Document(mProtoBuilder.build(), mPropertyBundle);
+ }
+ }
+ }
+
+ /**
+ * Encapsulates a {@link Document} that represent an email.
+ *
+ * <p>This class is a higher level implement of {@link Document}.
+ *
+ * <p>This class will eventually migrate to Jetpack, where it will become public API.
+ *
+ * @hide
+ */
+ public static class Email extends Document {
+
+ /** The name of the schema type for {@link Email} documents.*/
+ public static final String SCHEMA_TYPE = "builtin:Email";
+
+ private static final String KEY_FROM = "from";
+ private static final String KEY_TO = "to";
+ private static final String KEY_CC = "cc";
+ private static final String KEY_BCC = "bcc";
+ private static final String KEY_SUBJECT = "subject";
+ private static final String KEY_BODY = "body";
+
+ /**
+ * Creates a new {@link Email} from the contents of an existing {@link Document}.
+ *
+ * @param document The {@link Document} containing the email content.
+ */
+ public Email(@NonNull Document document) {
+ super(document);
+ }
+
+ /**
+ * Creates a new {@link Email.Builder}.
+ *
+ * @param uri The uri of {@link Email}.
+ */
+ public static Builder newBuilder(@NonNull String uri) {
+ return new Builder(uri);
+ }
+
+ /**
+ * Get the from address of {@link Email}.
+ *
+ * @return Returns the subject of {@link Email} or {@code null} if it's not been set yet.
+ * @hide
+ */
+ @Nullable
+ public String getFrom() {
+ return getPropertyString(KEY_FROM);
+ }
+
+ /**
+ * Get the destination address of {@link Email}.
+ *
+ * @return Returns the destination address of {@link Email} or {@code null} if it's not been
+ * set yet.
+ * @hide
+ */
+ @Nullable
+ public String[] getTo() {
+ return getPropertyStringArray(KEY_TO);
+ }
+
+ /**
+ * Get the CC list of {@link Email}.
+ *
+ * @return Returns the CC list of {@link Email} or {@code null} if it's not been set yet.
+ * @hide
+ */
+ @Nullable
+ public String[] getCc() {
+ return getPropertyStringArray(KEY_CC);
+ }
+
+ /**
+ * Get the BCC list of {@link Email}.
+ *
+ * @return Returns the BCC list of {@link Email} or {@code null} if it's not been set yet.
+ * @hide
+ */
+ @Nullable
+ public String[] getBcc() {
+ return getPropertyStringArray(KEY_BCC);
+ }
+
+ /**
+ * Get the subject of {@link Email}.
+ *
+ * @return Returns the value subject of {@link Email} or {@code null} if it's not been set
+ * yet.
+ * @hide
+ */
+ @Nullable
+ public String getSubject() {
+ return getPropertyString(KEY_SUBJECT);
+ }
+
+ /**
+ * Get the body of {@link Email}.
+ *
+ * @return Returns the body of {@link Email} or {@code null} if it's not been set yet.
+ * @hide
+ */
+ @Nullable
+ public String getBody() {
+ return getPropertyString(KEY_BODY);
+ }
+
+ /**
+ * The builder class for {@link Email}.
+ * @hide
+ */
+ public static class Builder extends Document.Builder<Email.Builder> {
+
+ /**
+ * Create a new {@link Email.Builder}
+ * @param uri The Uri of the Email.
+ * @hide
+ */
+ private Builder(@NonNull String uri) {
+ super(uri, SCHEMA_TYPE);
+ }
+
+ /**
+ * Set the from address of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setFrom(@NonNull String from) {
+ setProperty(KEY_FROM, from);
+ return this;
+ }
+
+ /**
+ * Set the destination address of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setTo(@NonNull String... to) {
+ setProperty(KEY_TO, to);
+ return this;
+ }
+
+ /**
+ * Set the CC list of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setCc(@NonNull String... cc) {
+ setProperty(KEY_CC, cc);
+ return this;
+ }
+
+ /**
+ * Set the BCC list of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setBcc(@NonNull String... bcc) {
+ setProperty(KEY_BCC, bcc);
+ return this;
+ }
+
+ /**
+ * Set the subject of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setSubject(@NonNull String subject) {
+ setProperty(KEY_SUBJECT, subject);
+ return this;
+ }
+
+ /**
+ * Set the body of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setBody(@NonNull String body) {
+ setProperty(KEY_BODY, body);
+ return this;
+ }
+
+ /**
+ * Builds the {@link Email} object.
+ *
+ * @hide
+ */
+ @NonNull
+ @Override
+ public Email build() {
+ return new Email(super.build());
+ }
+ }
+ }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index a8ee35c..83195dc 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -15,21 +15,116 @@
*/
package android.app.appsearch;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
import android.annotation.SystemService;
+import android.app.appsearch.AppSearch.Document;
import android.content.Context;
+import android.os.RemoteException;
+
+import com.android.internal.infra.AndroidFuture;
+
+import com.google.android.icing.proto.SchemaProto;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
- * TODO(b/142567528): add comments when implement this class
+ * This class provides access to the centralized AppSearch index maintained by the system.
+ *
+ * <p>Apps can index structured text documents with AppSearch, which can then be retrieved through
+ * the query API.
+ *
* @hide
*/
@SystemService(Context.APP_SEARCH_SERVICE)
public class AppSearchManager {
private final IAppSearchManager mService;
+
+ /** @hide */
+ public AppSearchManager(@NonNull IAppSearchManager service) {
+ mService = service;
+ }
+
/**
- * TODO(b/142567528): add comments when implement this class
+ * Sets the schema being used by documents provided to the #put method.
+ *
+ * <p>This operation is performed asynchronously. On success, the provided callback will be
+ * called with {@code null}. On failure, the provided callback will be called with a
+ * {@link Throwable} describing the failure.
+ *
+ * <p>It is a no-op to set the same schema as has been previously set; this is handled
+ * efficiently.
+ *
+ * <p>AppSearch automatically handles the following types of schema changes:
+ * <ul>
+ * <li>Addition of new types (No changes to storage or index)
+ * <li>Removal of an existing type (All documents of the removed type are deleted)
+ * <li>Addition of new 'optional' property to a type (No changes to storage or index)
+ * <li>Removal of existing property of any cardinality (All documents reindexed)
+ * </ul>
+ *
+ * <p>This method will return an error when attempting to make the following types of changes:
+ * <ul>
+ * <li>Changing the type of an existing property
+ * <li>Adding a 'required' property
+ * </ul>
+ *
+ * @param schema The schema config for this app.
+ * @param executor Executor on which to invoke the callback.
+ * @param callback Callback to receive errors resulting from setting the schema. If the
+ * operation succeeds, the callback will be invoked with {@code null}.
+ *
* @hide
*/
- public AppSearchManager(IAppSearchManager service) {
- mService = service;
+ // TODO(b/143789408): linkify #put after that API is created
+ // TODO(b/145635424): add a 'force' param to setSchema after the corresponding API is finalized
+ // in Icing Library
+ // TODO(b/145635424): Update the documentation above once the Schema mutation APIs of Icing
+ // Library are finalized
+ public void setSchema(
+ @NonNull AppSearchSchema schema,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<? super Throwable> callback) {
+ SchemaProto schemaProto = schema.getProto();
+ byte[] schemaBytes = schemaProto.toByteArray();
+ AndroidFuture<Void> future = new AndroidFuture<>();
+ try {
+ mService.setSchema(schemaBytes, future);
+ } catch (RemoteException e) {
+ future.completeExceptionally(e);
+ }
+ future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
+ }
+
+ /**
+ * Index {@link Document} to AppSearch
+ *
+ * <p>You should not call this method directly; instead, use the {@code AppSearch#put()} API
+ * provided by JetPack.
+ *
+ * <p>The schema should be set via {@link #setSchema} method.
+ *
+ * @param documents {@link Document Documents} that need to be indexed.
+ * @param executor Executor on which to invoke the callback.
+ * @param callback Callback to receive errors resulting from setting the schema. If the
+ * operation succeeds, the callback will be invoked with {@code null}.
+ */
+ public void put(@NonNull List<Document> documents,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<? super Throwable> callback) {
+ AndroidFuture<Void> future = new AndroidFuture<>();
+ for (Document document : documents) {
+ // TODO(b/146386470) batching Document protos
+ try {
+ mService.put(document.getProto().toByteArray(), future);
+ } catch (RemoteException e) {
+ future.completeExceptionally(e);
+ break;
+ }
+ }
+ // TODO(b/147614371) Fix error report for multiple documents.
+ future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
}
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
new file mode 100644
index 0000000..7e5f187
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Representation of the AppSearch Schema.
+ *
+ * <p>The schema is the set of document types, properties, and config (like tokenization type)
+ * understood by AppSearch for this app.
+ *
+ * @hide
+ */
+public final class AppSearchSchema {
+ private final SchemaProto mProto;
+
+ private AppSearchSchema(SchemaProto proto) {
+ mProto = proto;
+ }
+
+ /** Creates a new {@link AppSearchSchema.Builder}. */
+ @NonNull
+ public static AppSearchSchema.Builder newBuilder() {
+ return new AppSearchSchema.Builder();
+ }
+
+ /** Creates a new {@link SchemaType.Builder}. */
+ @NonNull
+ public static SchemaType.Builder newSchemaTypeBuilder(@NonNull String typeName) {
+ return new SchemaType.Builder(typeName);
+ }
+
+ /** Creates a new {@link PropertyConfig.Builder}. */
+ @NonNull
+ public static PropertyConfig.Builder newPropertyBuilder(@NonNull String propertyName) {
+ return new PropertyConfig.Builder(propertyName);
+ }
+
+ /** Creates a new {@link IndexingConfig.Builder}. */
+ @NonNull
+ public static IndexingConfig.Builder newIndexingConfigBuilder() {
+ return new IndexingConfig.Builder();
+ }
+
+ /**
+ * Returns the schema proto populated by the {@link AppSearchSchema} builders.
+ * @hide
+ */
+ @NonNull
+ @VisibleForTesting
+ public SchemaProto getProto() {
+ return mProto;
+ }
+
+ /** Builder for {@link AppSearchSchema objects}. */
+ public static final class Builder {
+ private final SchemaProto.Builder mProtoBuilder = SchemaProto.newBuilder();
+
+ private Builder() {}
+
+ /** Adds a supported type to this app's AppSearch schema. */
+ @NonNull
+ public AppSearchSchema.Builder addType(@NonNull SchemaType schemaType) {
+ mProtoBuilder.addTypes(schemaType.mProto);
+ return this;
+ }
+
+ /**
+ * Constructs a new {@link AppSearchSchema} from the contents of this builder.
+ *
+ * <p>After calling this method, the builder must no longer be used.
+ */
+ @NonNull
+ public AppSearchSchema build() {
+ return new AppSearchSchema(mProtoBuilder.build());
+ }
+ }
+
+ /**
+ * Represents a type of a document.
+ *
+ * <p>For example, an e-mail message or a music recording could be a schema type.
+ */
+ public static final class SchemaType {
+ private final SchemaTypeConfigProto mProto;
+
+ private SchemaType(SchemaTypeConfigProto proto) {
+ mProto = proto;
+ }
+
+ /** Builder for {@link SchemaType} objects. */
+ public static final class Builder {
+ private final SchemaTypeConfigProto.Builder mProtoBuilder =
+ SchemaTypeConfigProto.newBuilder();
+
+ private Builder(@NonNull String typeName) {
+ mProtoBuilder.setSchemaType(typeName);
+ }
+
+ /** Adds a property to the given type. */
+ @NonNull
+ public SchemaType.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
+ mProtoBuilder.addProperties(propertyConfig.mProto);
+ return this;
+ }
+
+ /**
+ * Constructs a new {@link SchemaType} from the contents of this builder.
+ *
+ * <p>After calling this method, the builder must no longer be used.
+ */
+ @NonNull
+ public SchemaType build() {
+ return new SchemaType(mProtoBuilder.build());
+ }
+ }
+ }
+
+ /**
+ * Configuration for a single property (field) of a document type.
+ *
+ * <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be
+ * a property.
+ */
+ public static final class PropertyConfig {
+ /** Physical data-types of the contents of the property. */
+ // NOTE: The integer values of these constants must match the proto enum constants in
+ // com.google.android.icing.proto.PropertyConfigProto.DataType.Code.
+ @IntDef(prefix = {"DATA_TYPE_"}, value = {
+ DATA_TYPE_STRING,
+ DATA_TYPE_INT64,
+ DATA_TYPE_DOUBLE,
+ DATA_TYPE_BOOLEAN,
+ DATA_TYPE_BYTES,
+ DATA_TYPE_DOCUMENT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DataType {}
+
+ public static final int DATA_TYPE_STRING = 1;
+ public static final int DATA_TYPE_INT64 = 2;
+ public static final int DATA_TYPE_DOUBLE = 3;
+ public static final int DATA_TYPE_BOOLEAN = 4;
+
+ /** Unstructured BLOB. */
+ public static final int DATA_TYPE_BYTES = 5;
+
+ /**
+ * Indicates that the property itself is an Document, making it part a hierarchical
+ * Document schema. Any property using this DataType MUST have a valid
+ * {@code schemaType}.
+ */
+ public static final int DATA_TYPE_DOCUMENT = 6;
+
+ /** The cardinality of the property (whether it is required, optional or repeated). */
+ // NOTE: The integer values of these constants must match the proto enum constants in
+ // com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code.
+ @IntDef(prefix = {"CARDINALITY_"}, value = {
+ CARDINALITY_REPEATED,
+ CARDINALITY_OPTIONAL,
+ CARDINALITY_REQUIRED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Cardinality {}
+
+ /** Any number of items (including zero) [0...*]. */
+ public static final int CARDINALITY_REPEATED = 1;
+
+ /** Zero or one value [0,1]. */
+ public static final int CARDINALITY_OPTIONAL = 2;
+
+ /** Exactly one value [1]. */
+ public static final int CARDINALITY_REQUIRED = 3;
+
+ private final PropertyConfigProto mProto;
+
+ private PropertyConfig(PropertyConfigProto proto) {
+ mProto = proto;
+ }
+
+ /**
+ * Builder for {@link PropertyConfig}.
+ *
+ * <p>The following properties must be set, or {@link PropertyConfig} construction will
+ * fail:
+ * <ul>
+ * <li>dataType
+ * <li>cardinality
+ * </ul>
+ *
+ * <p>In addition, if {@code schemaType} is {@link #DATA_TYPE_DOCUMENT}, {@code schemaType}
+ * is also required.
+ */
+ public static final class Builder {
+ private final PropertyConfigProto.Builder mProtoBuilder =
+ PropertyConfigProto.newBuilder();
+
+ private Builder(String propertyName) {
+ mProtoBuilder.setPropertyName(propertyName);
+ }
+
+ /**
+ * Type of data the property contains (e.g. string, int, bytes, etc).
+ *
+ * <p>This property must be set.
+ */
+ @NonNull
+ public PropertyConfig.Builder setDataType(@DataType int dataType) {
+ PropertyConfigProto.DataType.Code dataTypeProto =
+ PropertyConfigProto.DataType.Code.forNumber(dataType);
+ if (dataTypeProto == null) {
+ throw new IllegalArgumentException("Invalid dataType: " + dataType);
+ }
+ mProtoBuilder.setDataType(dataTypeProto);
+ return this;
+ }
+
+ /**
+ * The logical schema-type of the contents of this property.
+ *
+ * <p>Only required when {@link #setDataType(int)} is set to
+ * {@link #DATA_TYPE_DOCUMENT}. Otherwise, it is ignored.
+ */
+ @NonNull
+ public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
+ mProtoBuilder.setSchemaType(schemaType);
+ return this;
+ }
+
+ /**
+ * The cardinality of the property (whether it is optional, required or repeated).
+ *
+ * <p>This property must be set.
+ */
+ @NonNull
+ public PropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
+ PropertyConfigProto.Cardinality.Code cardinalityProto =
+ PropertyConfigProto.Cardinality.Code.forNumber(cardinality);
+ if (cardinalityProto == null) {
+ throw new IllegalArgumentException("Invalid cardinality: " + cardinality);
+ }
+ mProtoBuilder.setCardinality(cardinalityProto);
+ return this;
+ }
+
+ /**
+ * Configures how this property should be indexed.
+ *
+ * <p>If this is not supplied, the property will not be indexed at all.
+ */
+ @NonNull
+ public PropertyConfig.Builder setIndexingConfig(
+ @NonNull IndexingConfig indexingConfig) {
+ mProtoBuilder.setIndexingConfig(indexingConfig.mProto);
+ return this;
+ }
+
+ /**
+ * Constructs a new {@link PropertyConfig} from the contents of this builder.
+ *
+ * <p>After calling this method, the builder must no longer be used.
+ *
+ * @throws IllegalSchemaException If the property is not correctly populated (e.g.
+ * missing {@code dataType}).
+ */
+ @NonNull
+ public PropertyConfig build() {
+ if (mProtoBuilder.getDataType() == PropertyConfigProto.DataType.Code.UNKNOWN) {
+ throw new IllegalSchemaException("Missing field: dataType");
+ }
+ if (mProtoBuilder.getSchemaType().isEmpty()
+ && mProtoBuilder.getDataType()
+ == PropertyConfigProto.DataType.Code.DOCUMENT) {
+ throw new IllegalSchemaException(
+ "Missing field: schemaType (required for configs with "
+ + "dataType = DOCUMENT)");
+ }
+ if (mProtoBuilder.getCardinality()
+ == PropertyConfigProto.Cardinality.Code.UNKNOWN) {
+ throw new IllegalSchemaException("Missing field: cardinality");
+ }
+ return new PropertyConfig(mProtoBuilder.build());
+ }
+ }
+ }
+
+ /** Configures how a property should be indexed so that it can be retrieved by queries. */
+ public static final class IndexingConfig {
+ /** Encapsulates the configurations on how AppSearch should query/index these terms. */
+ // NOTE: The integer values of these constants must match the proto enum constants in
+ // com.google.android.icing.proto.TermMatchType.Code.
+ @IntDef(prefix = {"TERM_MATCH_TYPE_"}, value = {
+ TERM_MATCH_TYPE_UNKNOWN,
+ TERM_MATCH_TYPE_EXACT_ONLY,
+ TERM_MATCH_TYPE_PREFIX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TermMatchType {}
+
+ /**
+ * Content in this property will not be tokenized or indexed.
+ *
+ * <p>Useful if the data type is not made up of terms (e.g.
+ * {@link PropertyConfig#DATA_TYPE_DOCUMENT} or {@link PropertyConfig#DATA_TYPE_BYTES}
+ * type). All the properties inside the nested property won't be indexed regardless of the
+ * value of {@code termMatchType} for the nested properties.
+ */
+ public static final int TERM_MATCH_TYPE_UNKNOWN = 0;
+
+ /**
+ * Content in this property should only be returned for queries matching the exact tokens
+ * appearing in this property.
+ *
+ * <p>Ex. A property with "fool" should NOT match a query for "foo".
+ */
+ public static final int TERM_MATCH_TYPE_EXACT_ONLY = 1;
+
+ /**
+ * Content in this property should be returned for queries that are either exact matches or
+ * query matches of the tokens appearing in this property.
+ *
+ * <p>Ex. A property with "fool" <b>should</b> match a query for "foo".
+ */
+ public static final int TERM_MATCH_TYPE_PREFIX = 2;
+
+ /** Configures how tokens should be extracted from this property. */
+ // NOTE: The integer values of these constants must match the proto enum constants in
+ // com.google.android.icing.proto.IndexingConfig.TokenizerType.Code.
+ @IntDef(prefix = {"TOKENIZER_TYPE_"}, value = {
+ TOKENIZER_TYPE_NONE,
+ TOKENIZER_TYPE_PLAIN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TokenizerType {}
+
+ /**
+ * It is only valid for tokenizer_type to be 'NONE' if the data type is
+ * {@link PropertyConfig#DATA_TYPE_DOCUMENT}.
+ */
+ public static final int TOKENIZER_TYPE_NONE = 0;
+
+ /** Tokenization for plain text. */
+ public static final int TOKENIZER_TYPE_PLAIN = 1;
+
+ private final com.google.android.icing.proto.IndexingConfig mProto;
+
+ private IndexingConfig(com.google.android.icing.proto.IndexingConfig proto) {
+ mProto = proto;
+ }
+
+ /**
+ * Builder for {@link IndexingConfig} objects.
+ *
+ * <p>You may skip adding an {@link IndexingConfig} for a property, which is equivalent to
+ * an {@link IndexingConfig} having {@code termMatchType} equal to
+ * {@link #TERM_MATCH_TYPE_UNKNOWN}. In this case the property will not be indexed.
+ */
+ public static final class Builder {
+ private final com.google.android.icing.proto.IndexingConfig.Builder mProtoBuilder =
+ com.google.android.icing.proto.IndexingConfig.newBuilder();
+
+ private Builder() {}
+
+ /** Configures how the content of this property should be matched in the index. */
+ @NonNull
+ public IndexingConfig.Builder setTermMatchType(@TermMatchType int termMatchType) {
+ com.google.android.icing.proto.TermMatchType.Code termMatchTypeProto =
+ com.google.android.icing.proto.TermMatchType.Code.forNumber(termMatchType);
+ if (termMatchTypeProto == null) {
+ throw new IllegalArgumentException("Invalid termMatchType: " + termMatchType);
+ }
+ mProtoBuilder.setTermMatchType(termMatchTypeProto);
+ return this;
+ }
+
+ /** Configures how this property should be tokenized (split into words). */
+ @NonNull
+ public IndexingConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) {
+ com.google.android.icing.proto.IndexingConfig.TokenizerType.Code
+ tokenizerTypeProto =
+ com.google.android.icing.proto.IndexingConfig
+ .TokenizerType.Code.forNumber(tokenizerType);
+ if (tokenizerTypeProto == null) {
+ throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType);
+ }
+ mProtoBuilder.setTokenizerType(tokenizerTypeProto);
+ return this;
+ }
+
+ /**
+ * Constructs a new {@link IndexingConfig} from the contents of this builder.
+ *
+ * <p>After calling this method, the builder must no longer be used.
+ */
+ @NonNull
+ public IndexingConfig build() {
+ return new IndexingConfig(mProtoBuilder.build());
+ }
+ }
+ }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index f0f4f51..fc83d8c 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -14,6 +14,19 @@
* limitations under the License.
*/
package android.app.appsearch;
+
+import com.android.internal.infra.AndroidFuture;
+
/** {@hide} */
interface IAppSearchManager {
+ /**
+ * Sets the schema.
+ *
+ * @param schemaProto serialized SchemaProto
+ * @param callback {@link AndroidFuture}<{@link Void}>. Will be completed with
+ * {@code null} upon successful completion of the setSchema call, or completed exceptionally
+ * if setSchema fails.
+ */
+ void setSchema(in byte[] schemaProto, in AndroidFuture callback);
+ void put(in byte[] documentBytes, in AndroidFuture callback);
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java b/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java
new file mode 100644
index 0000000..f9e528c
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import android.annotation.NonNull;
+
+/**
+ * Indicates that a {@link android.app.appsearch.AppSearchSchema} has logical inconsistencies such
+ * as unpopulated mandatory fields or illegal combinations of parameters.
+ *
+ * @hide
+ */
+public class IllegalSchemaException extends IllegalArgumentException {
+ /**
+ * Constructs a new {@link IllegalSchemaException}.
+ *
+ * @param message A developer-readable description of the issue with the bundle.
+ */
+ public IllegalSchemaException(@NonNull String message) {
+ super(message);
+ }
+}
diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp
index 4ebafce8..7327231 100644
--- a/apex/appsearch/service/Android.bp
+++ b/apex/appsearch/service/Android.bp
@@ -12,16 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
java_library {
- name: "service-appsearch",
- installable: true,
- srcs: [
- "java/**/*.java",
- ],
- libs: [
- "framework",
- "services.core",
- ],
- static_libs: [
- "icing-java-proto-lite",
- ]
+ name: "service-appsearch",
+ installable: true,
+ srcs: ["java/**/*.java"],
+ libs: [
+ "framework",
+ "framework-appsearch",
+ "services.core",
+ ],
+ static_libs: ["icing-java-proto-lite"],
+ apex_available: ["com.android.appsearch"],
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 4d44d9d..042f051 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -18,8 +18,11 @@
import android.app.appsearch.IAppSearchManager;
import android.content.Context;
+import com.android.internal.infra.AndroidFuture;
import com.android.server.SystemService;
+import com.google.android.icing.proto.SchemaProto;
+
/**
* TODO(b/142567528): add comments when implement this class
*/
@@ -35,5 +38,24 @@
}
private class Stub extends IAppSearchManager.Stub {
+ @Override
+ public void setSchema(byte[] schemaBytes, AndroidFuture callback) {
+ try {
+ SchemaProto schema = SchemaProto.parseFrom(schemaBytes);
+ throw new UnsupportedOperationException("setSchema not yet implemented: " + schema);
+
+ } catch (Throwable t) {
+ callback.completeExceptionally(t);
+ }
+ }
+
+ @Override
+ public void put(byte[] documentBytes, AndroidFuture callback) {
+ try {
+ throw new UnsupportedOperationException("Put document not yet implemented");
+ } catch (Throwable t) {
+ callback.completeExceptionally(t);
+ }
+ }
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
index 08811f8..ca5b884 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
+++ b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
@@ -10,6 +10,14 @@
"include-filter": "com.android.server.appsearch"
}
]
+ },
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "android.app.appsearch"
+ }
+ ]
}
]
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
index 3dbb5cf..02a79a1 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
@@ -36,8 +36,6 @@
* <p>
* Currently, only queries by single exact term are supported. There is no support for persistence,
* namespaces, i18n tokenization, or schema.
- *
- * @hide
*/
public class FakeIcing {
private final AtomicInteger mNextDocId = new AtomicInteger();
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index c9d9d6c..69a9fd8 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -9,6 +9,7 @@
],
libs: [
+ "app-compat-annotations",
"framework",
"services.core",
],
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 3f58c72..f8b2f32 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -761,6 +761,7 @@
@Override
public void onTrigger(TriggerEvent event) {
synchronized (DeviceIdleController.this) {
+ // One_shot sensors (which call onTrigger) are unregistered when onTrigger is called
active = false;
motionLocked();
}
@@ -769,6 +770,9 @@
@Override
public void onSensorChanged(SensorEvent event) {
synchronized (DeviceIdleController.this) {
+ // Since one_shot sensors are unregistered when onTrigger is called, unregister
+ // listeners here so that the MotionListener is in a consistent state when it calls
+ // out to motionLocked.
mSensorManager.unregisterListener(this, mMotionSensor);
active = false;
motionLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
new file mode 100644
index 0000000..8fbfb1d
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "file_patterns": [
+ "DeviceIdleController\\.java"
+ ],
+ "options": [
+ {"include-filter": "com.android.server.DeviceIdleControllerTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {"include-filter": "com.android.server"}
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
new file mode 100644
index 0000000..bc7a7d3
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.DeviceIdleControllerTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {"include-filter": "com.android.server"}
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 9310762..102e848 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -37,12 +37,15 @@
import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -54,6 +57,7 @@
import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -67,6 +71,7 @@
import android.os.WorkSource;
import android.provider.Settings;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -85,6 +90,7 @@
import com.android.server.DeviceIdleInternal;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.compat.PlatformCompat;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
import com.android.server.job.controllers.BackgroundJobsController;
@@ -102,6 +108,9 @@
import com.android.server.job.restrictions.ThermalStatusRestriction;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
+import com.android.server.utils.quota.Categorizer;
+import com.android.server.utils.quota.Category;
+import com.android.server.utils.quota.CountQuotaTracker;
import libcore.util.EmptyArray;
@@ -145,6 +154,16 @@
/** The maximum number of jobs that we allow an unprivileged app to schedule */
private static final int MAX_JOBS_PER_APP = 100;
+ /**
+ * {@link #schedule(JobInfo)}, {@link #scheduleAsPackage(JobInfo, String, int, String)}, and
+ * {@link #enqueue(JobInfo, JobWorkItem)} will throw a {@link IllegalStateException} if the app
+ * calls the APIs too frequently.
+ */
+ @ChangeId
+ // This means the change will be enabled for target SDK larger than 29 (Q), meaning R and up.
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ protected static final long CRASH_ON_EXCEEDED_LIMIT = 144363383L;
+
@VisibleForTesting
public static Clock sSystemClock = Clock.systemUTC();
@@ -237,6 +256,10 @@
*/
private final List<JobRestriction> mJobRestrictions;
+ private final CountQuotaTracker mQuotaTracker;
+ private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
+ private final PlatformCompat mPlatformCompat;
+
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
* when ready to execute them.
@@ -276,6 +299,11 @@
final SparseIntArray mBackingUpUids = new SparseIntArray();
/**
+ * Cache of debuggable app status.
+ */
+ final ArrayMap<String, Boolean> mDebuggableApps = new ArrayMap<>();
+
+ /**
* Named indices into standby bucket arrays, for clarity in referring to
* specific buckets' bookkeeping.
*/
@@ -315,6 +343,10 @@
final StateController sc = mControllers.get(controller);
sc.onConstantsUpdatedLocked();
}
+ mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS);
+ mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
+ mConstants.API_QUOTA_SCHEDULE_COUNT,
+ mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
} catch (IllegalArgumentException e) {
// Failed to parse the settings string, log this and move on
// with defaults.
@@ -466,6 +498,11 @@
private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
private static final String DEPRECATED_KEY_USE_HEARTBEATS = "use_heartbeats";
+ private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
+ private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count";
+ private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms";
+ private static final String KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION =
+ "aq_schedule_throw_exception";
private static final int DEFAULT_MIN_IDLE_COUNT = 1;
private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -484,6 +521,10 @@
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
+ private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
+ private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
+ private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
+ private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
/**
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -618,6 +659,24 @@
*/
public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
+ /**
+ * Whether to enable quota limits on APIs.
+ */
+ public boolean ENABLE_API_QUOTAS = DEFAULT_ENABLE_API_QUOTAS;
+ /**
+ * The maximum number of schedule() calls an app can make in a set amount of time.
+ */
+ public int API_QUOTA_SCHEDULE_COUNT = DEFAULT_API_QUOTA_SCHEDULE_COUNT;
+ /**
+ * The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over.
+ */
+ public long API_QUOTA_SCHEDULE_WINDOW_MS = DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS;
+ /**
+ * Whether to throw an exception when an app hits its schedule quota limit.
+ */
+ public boolean API_QUOTA_SCHEDULE_THROW_EXCEPTION =
+ DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION;
+
private final KeyValueListParser mParser = new KeyValueListParser(',');
void updateConstantsLocked(String value) {
@@ -678,6 +737,18 @@
DEFAULT_CONN_CONGESTION_DELAY_FRAC);
CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC,
DEFAULT_CONN_PREFETCH_RELAX_FRAC);
+
+ ENABLE_API_QUOTAS = mParser.getBoolean(KEY_ENABLE_API_QUOTAS,
+ DEFAULT_ENABLE_API_QUOTAS);
+ // Set a minimum value on the quota limit so it's not so low that it interferes with
+ // legitimate use cases.
+ API_QUOTA_SCHEDULE_COUNT = Math.max(250,
+ mParser.getInt(KEY_API_QUOTA_SCHEDULE_COUNT, DEFAULT_API_QUOTA_SCHEDULE_COUNT));
+ API_QUOTA_SCHEDULE_WINDOW_MS = mParser.getDurationMillis(
+ KEY_API_QUOTA_SCHEDULE_WINDOW_MS, DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS);
+ API_QUOTA_SCHEDULE_THROW_EXCEPTION = mParser.getBoolean(
+ KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION,
+ DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION);
}
void dump(IndentingPrintWriter pw) {
@@ -716,6 +787,12 @@
pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
+ pw.printPair(KEY_ENABLE_API_QUOTAS, ENABLE_API_QUOTAS).println();
+ pw.printPair(KEY_API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT).println();
+ pw.printPair(KEY_API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS).println();
+ pw.printPair(KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION,
+ API_QUOTA_SCHEDULE_THROW_EXCEPTION).println();
+
pw.decreaseIndent();
}
@@ -746,6 +823,12 @@
proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME);
proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC);
+
+ proto.write(ConstantsProto.ENABLE_API_QUOTAS, ENABLE_API_QUOTAS);
+ proto.write(ConstantsProto.API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT);
+ proto.write(ConstantsProto.API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS);
+ proto.write(ConstantsProto.API_QUOTA_SCHEDULE_THROW_EXCEPTION,
+ API_QUOTA_SCHEDULE_THROW_EXCEPTION);
}
}
@@ -847,6 +930,7 @@
for (int c = 0; c < mControllers.size(); ++c) {
mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
}
+ mDebuggableApps.remove(pkgName);
}
}
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
@@ -972,6 +1056,49 @@
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
int userId, String tag) {
+ if (job.isPersisted()) {
+ // Only limit schedule calls for persisted jobs.
+ final String pkg =
+ packageName == null ? job.getService().getPackageName() : packageName;
+ if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
+ Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
+ // TODO(b/145551233): attempt to restrict app
+ if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
+ && mPlatformCompat.isChangeEnabledByPackageName(
+ CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
+ final boolean isDebuggable;
+ synchronized (mLock) {
+ if (!mDebuggableApps.containsKey(packageName)) {
+ try {
+ final ApplicationInfo appInfo = AppGlobals.getPackageManager()
+ .getApplicationInfo(pkg, 0, userId);
+ if (appInfo != null) {
+ mDebuggableApps.put(packageName,
+ (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
+ } else {
+ return JobScheduler.RESULT_FAILURE;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ isDebuggable = mDebuggableApps.get(packageName);
+ }
+ if (isDebuggable) {
+ // Only throw the exception for debuggable apps.
+ throw new IllegalStateException(
+ "schedule()/enqueue() called more than "
+ + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY)
+ + " times in the past "
+ + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY)
+ + "ms");
+ }
+ }
+ return JobScheduler.RESULT_FAILURE;
+ }
+ mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
+ }
+
try {
if (ActivityManager.getService().isAppStartModeDisabled(uId,
job.getService().getPackageName())) {
@@ -1296,6 +1423,12 @@
// Set up the app standby bucketing tracker
mStandbyTracker = new StandbyTracker();
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+ mPlatformCompat =
+ (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
+ mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER);
+ mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
+ mConstants.API_QUOTA_SCHEDULE_COUNT,
+ mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class);
appStandby.addListener(mStandbyTracker);
@@ -2745,7 +2878,7 @@
return new ParceledListSlice<>(snapshots);
}
}
- };
+ }
// Shell command infrastructure: run the given job immediately
int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
@@ -2968,6 +3101,10 @@
return 0;
}
+ void resetScheduleQuota() {
+ mQuotaTracker.clear();
+ }
+
void triggerDockState(boolean idleState) {
final Intent dockIntent;
if (idleState) {
@@ -3030,6 +3167,9 @@
}
pw.println();
+ mQuotaTracker.dump(pw);
+ pw.println();
+
pw.println("Started users: " + Arrays.toString(mStartedUsers));
pw.print("Registered ");
pw.print(mJobs.size());
@@ -3217,6 +3357,9 @@
for (int u : mStartedUsers) {
proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u);
}
+
+ mQuotaTracker.dump(proto, JobSchedulerServiceDumpProto.QUOTA_TRACKER);
+
if (mJobs.size() > 0) {
final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
sortJobs(jobs);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index a5c6c01..6becf04 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -66,6 +66,8 @@
return getJobState(pw);
case "heartbeat":
return doHeartbeat(pw);
+ case "reset-schedule-quota":
+ return resetScheduleQuota(pw);
case "trigger-dock-state":
return triggerDockState(pw);
default:
@@ -344,6 +346,18 @@
return -1;
}
+ private int resetScheduleQuota(PrintWriter pw) throws Exception {
+ checkPermission("reset schedule quota");
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInternal.resetScheduleQuota();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return 0;
+ }
+
private int triggerDockState(PrintWriter pw) throws Exception {
checkPermission("trigger wireless charging dock state");
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 82292cf..b9df30a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -17,7 +17,7 @@
package com.android.server.usage;
import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
@@ -441,7 +441,7 @@
elapsedRealtime, true);
if (idle) {
appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
- appUsageHistory.bucketingReason = REASON_MAIN_FORCED;
+ appUsageHistory.bucketingReason = REASON_MAIN_FORCED_BY_USER;
} else {
appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
// This is to pretend that the app was just used, don't freeze the state anymore.
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 58eb589..eb0b54b 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -17,7 +17,8 @@
package com.android.server.usage;
import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
@@ -565,7 +566,7 @@
// If the bucket was forced by the user/developer, leave it alone.
// A usage event will be the only way to bring it out of this forced state
- if (oldMainReason == REASON_MAIN_FORCED) {
+ if (oldMainReason == REASON_MAIN_FORCED_BY_USER) {
return;
}
final int oldBucket = app.currentBucket;
@@ -783,7 +784,7 @@
// Inform listeners if necessary
if (previouslyIdle != stillIdle) {
maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket,
- REASON_MAIN_FORCED, false);
+ REASON_MAIN_FORCED_BY_USER, false);
if (!stillIdle) {
notifyBatteryStats(packageName, userId, idle);
}
@@ -1030,8 +1031,17 @@
callingPid, callingUid, userId, false, true, "setAppStandbyBucket", null);
final boolean shellCaller = callingUid == Process.ROOT_UID
|| callingUid == Process.SHELL_UID;
- final boolean systemCaller = UserHandle.isCore(callingUid);
- final int reason = systemCaller ? REASON_MAIN_FORCED : REASON_MAIN_PREDICTED;
+ final int reason;
+ // The Settings app runs in the system UID but in a separate process. Assume
+ // things coming from other processes are due to the user.
+ if ((UserHandle.isSameApp(callingUid, Process.SYSTEM_UID) && callingPid != Process.myPid())
+ || shellCaller) {
+ reason = REASON_MAIN_FORCED_BY_USER;
+ } else if (UserHandle.isCore(callingUid)) {
+ reason = REASON_MAIN_FORCED_BY_SYSTEM;
+ } else {
+ reason = REASON_MAIN_PREDICTED;
+ }
final int packageFlags = PackageManager.MATCH_ANY_USER
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE;
@@ -1087,7 +1097,11 @@
}
// If the bucket was forced, don't allow prediction to override
- if ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED && predicted) return;
+ if (predicted
+ && ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER
+ || (app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_SYSTEM)) {
+ return;
+ }
// If the bucket is required to stay in a higher state for a specified duration, don't
// override unless the duration has passed
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
new file mode 100644
index 0000000..cf70878
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.usage"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.usage"}
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp
index 5945fb3..f232e62 100644
--- a/apex/permission/Android.bp
+++ b/apex/permission/Android.bp
@@ -22,6 +22,7 @@
name: "com.android.permission-defaults",
key: "com.android.permission.key",
certificate: ":com.android.permission.certificate",
+ apps: ["PermissionController"],
}
apex_key {
diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
index 21b7767..99b9d39 100644
--- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
@@ -67,11 +67,4 @@
/** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */
oneway void triggerUidSnapshot();
-
- /** Tells StatsCompanionService to tell statsd to register a puller for the given atom id */
- oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
- in int[] additiveFields, IPullAtomCallback pullerCallback);
-
- /** Tells StatsCompanionService to tell statsd to unregister a puller for the given atom id */
- oneway void unregisterPullAtomCallback(int atomTag);
}
diff --git a/apex/statsd/aidl/android/os/IStatsManagerService.aidl b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
index dec5634..4a259f5 100644
--- a/apex/statsd/aidl/android/os/IStatsManagerService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.app.PendingIntent;
+import android.os.IPullAtomCallback;
/**
* Binder interface to communicate with the Java-based statistics service helper.
@@ -125,4 +126,11 @@
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
void removeConfiguration(in long configId, in String packageName);
-}
\ No newline at end of file
+
+ /** Tell StatsManagerService to register a puller for the given atom tag with statsd. */
+ oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+ in int[] additiveFields, IPullAtomCallback pullerCallback);
+
+ /** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */
+ oneway void unregisterPullAtomCallback(int atomTag);
+}
diff --git a/apex/statsd/framework/java/android/util/StatsEvent.java b/apex/statsd/framework/java/android/util/StatsEvent.java
index c765945..1a45c4a 100644
--- a/apex/statsd/framework/java/android/util/StatsEvent.java
+++ b/apex/statsd/framework/java/android/util/StatsEvent.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
@@ -51,6 +52,7 @@
* </pre>
* @hide
**/
+@SystemApi
public final class StatsEvent {
// Type Ids.
/**
@@ -270,6 +272,8 @@
/**
* Recycle resources used by this StatsEvent object.
* No actions should be taken on this StatsEvent after release() is called.
+ *
+ * @hide
**/
public void release() {
if (mBuffer != null) {
@@ -363,16 +367,6 @@
}
/**
- * Sets the timestamp in nanos for this StatsEvent.
- **/
- @VisibleForTesting
- @NonNull
- public Builder setTimestampNs(final long timestampNs) {
- mTimestampNs = timestampNs;
- return this;
- }
-
- /**
* Write a boolean field to this StatsEvent.
**/
@NonNull
@@ -500,14 +494,14 @@
**/
@NonNull
public Builder writeKeyValuePairs(
- @NonNull final SparseIntArray intMap,
- @NonNull final SparseLongArray longMap,
- @NonNull final SparseArray<String> stringMap,
- @NonNull final SparseArray<Float> floatMap) {
- final int intMapSize = intMap.size();
- final int longMapSize = longMap.size();
- final int stringMapSize = stringMap.size();
- final int floatMapSize = floatMap.size();
+ @Nullable final SparseIntArray intMap,
+ @Nullable final SparseLongArray longMap,
+ @Nullable final SparseArray<String> stringMap,
+ @Nullable final SparseArray<Float> floatMap) {
+ final int intMapSize = null == intMap ? 0 : intMap.size();
+ final int longMapSize = null == longMap ? 0 : longMap.size();
+ final int stringMapSize = null == stringMap ? 0 : stringMap.size();
+ final int floatMapSize = null == floatMap ? 0 : floatMap.size();
final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize;
if (totalCount > MAX_KEY_VALUE_PAIRS) {
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
index f3a8989..9103848 100644
--- a/apex/statsd/service/Android.bp
+++ b/apex/statsd/service/Android.bp
@@ -13,4 +13,8 @@
"framework-minus-apex",
"services.core",
],
+ apex_available: [
+ "com.android.os.statsd",
+ "test_com.android.os.statsd",
+ ],
}
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index d57afee..6444b4e 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -75,7 +75,6 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.IPullAtomCallback;
import android.os.IStatsCompanionService;
import android.os.IStatsd;
import android.os.IStoraged;
@@ -263,71 +262,6 @@
private StatsManagerService mStatsManagerService;
- private static final class PullerKey {
- private final int mUid;
- private final int mAtomTag;
-
- PullerKey(int uid, int atom) {
- mUid = uid;
- mAtomTag = atom;
- }
-
- public int getUid() {
- return mUid;
- }
-
- public int getAtom() {
- return mAtomTag;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mUid, mAtomTag);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof PullerKey) {
- PullerKey other = (PullerKey) obj;
- return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
- }
- return false;
- }
- }
-
- private static final class PullerValue {
- private final long mCoolDownNs;
- private final long mTimeoutNs;
- private int[] mAdditiveFields;
- private IPullAtomCallback mCallback;
-
- PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
- IPullAtomCallback callback) {
- mCoolDownNs = coolDownNs;
- mTimeoutNs = timeoutNs;
- mAdditiveFields = additiveFields;
- mCallback = callback;
- }
-
- public long getCoolDownNs() {
- return mCoolDownNs;
- }
-
- public long getTimeoutNs() {
- return mTimeoutNs;
- }
-
- public int[] getAdditiveFields() {
- return mAdditiveFields;
- }
-
- public IPullAtomCallback getCallback() {
- return mCallback;
- }
- }
-
- private final HashMap<PullerKey, PullerValue> mPullers = new HashMap<>();
-
private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
private WifiManager mWifiManager = null;
@@ -780,60 +714,6 @@
}
}
- private void addNetworkStats(
- int tag, List<StatsLogEventWrapper> ret, NetworkStats stats, boolean withFGBG) {
- int size = stats.size();
- long elapsedNanos = SystemClock.elapsedRealtimeNanos();
- long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
- NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
- for (int j = 0; j < size; j++) {
- stats.getValues(j, entry);
- StatsLogEventWrapper e = new StatsLogEventWrapper(tag, elapsedNanos, wallClockNanos);
- e.writeInt(entry.uid);
- if (withFGBG) {
- e.writeInt(entry.set);
- }
- e.writeLong(entry.rxBytes);
- e.writeLong(entry.rxPackets);
- e.writeLong(entry.txBytes);
- e.writeLong(entry.txPackets);
- ret.add(e);
- }
- }
-
- /**
- * Allows rollups per UID but keeping the set (foreground/background) slicing.
- * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
- */
- private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
- final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
-
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- entry.iface = NetworkStats.IFACE_ALL;
- entry.tag = NetworkStats.TAG_NONE;
- entry.metered = NetworkStats.METERED_ALL;
- entry.roaming = NetworkStats.ROAMING_ALL;
-
- int size = stats.size();
- NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
- for (int i = 0; i < size; i++) {
- stats.getValues(i, recycle);
-
- // Skip specific tags, since already counted in TAG_NONE
- if (recycle.tag != NetworkStats.TAG_NONE) continue;
-
- entry.set = recycle.set; // Allows slicing by background/foreground
- entry.uid = recycle.uid;
- entry.rxBytes = recycle.rxBytes;
- entry.rxPackets = recycle.rxPackets;
- entry.txBytes = recycle.txBytes;
- entry.txPackets = recycle.txPackets;
- // Operations purposefully omitted since we don't use them for statsd.
- ret.combineValues(entry);
- }
- return ret;
- }
-
/**
* Helper method to extract the Parcelable controller info from a
* SynchronousResultReceiver.
@@ -864,210 +744,6 @@
return null;
}
- private void pullKernelWakelock(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- final KernelWakelockStats wakelockStats =
- mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
- for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
- String name = ent.getKey();
- KernelWakelockStats.Entry kws = ent.getValue();
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeString(name);
- e.writeInt(kws.mCount);
- e.writeInt(kws.mVersion);
- e.writeLong(kws.mTotalTime);
- pulledData.add(e);
- }
- }
-
- private void pullWifiBytesTransfer(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- // TODO: Consider caching the following call to get BatteryStatsInternal.
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getWifiIfaces();
- if (ifaces.length == 0) {
- return;
- }
- if (mNetworkStatsService == null) {
- Slog.e(TAG, "NetworkStats Service is not available!");
- return;
- }
- // Combine all the metrics per Uid into one record.
- NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid();
- addNetworkStats(tagId, pulledData, stats, false);
- } catch (RemoteException e) {
- Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private void pullWifiBytesTransferByFgBg(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getWifiIfaces();
- if (ifaces.length == 0) {
- return;
- }
- if (mNetworkStatsService == null) {
- Slog.e(TAG, "NetworkStats Service is not available!");
- return;
- }
- NetworkStats stats = rollupNetworkStatsByFGBG(
- mNetworkStatsService.getDetailedUidStats(ifaces));
- addNetworkStats(tagId, pulledData, stats, true);
- } catch (RemoteException e) {
- Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private void pullMobileBytesTransfer(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getMobileIfaces();
- if (ifaces.length == 0) {
- return;
- }
- if (mNetworkStatsService == null) {
- Slog.e(TAG, "NetworkStats Service is not available!");
- return;
- }
- // Combine all the metrics per Uid into one record.
- NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid();
- addNetworkStats(tagId, pulledData, stats, false);
- } catch (RemoteException e) {
- Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private void pullBluetoothBytesTransfer(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- BluetoothActivityEnergyInfo info = fetchBluetoothData();
- if (info.getUidTraffic() != null) {
- for (UidTraffic traffic : info.getUidTraffic()) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeInt(traffic.getUid());
- e.writeLong(traffic.getRxBytes());
- e.writeLong(traffic.getTxBytes());
- pulledData.add(e);
- }
- }
- }
-
- private void pullMobileBytesTransferByFgBg(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getMobileIfaces();
- if (ifaces.length == 0) {
- return;
- }
- if (mNetworkStatsService == null) {
- Slog.e(TAG, "NetworkStats Service is not available!");
- return;
- }
- NetworkStats stats = rollupNetworkStatsByFGBG(
- mNetworkStatsService.getDetailedUidStats(ifaces));
- addNetworkStats(tagId, pulledData, stats, true);
- } catch (RemoteException e) {
- Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private void pullCpuTimePerFreq(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
- long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
- if (clusterTimeMs != null) {
- for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeInt(cluster);
- e.writeInt(speed);
- e.writeLong(clusterTimeMs[speed]);
- pulledData.add(e);
- }
- }
- }
- }
-
- private void pullKernelUidCpuTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
- long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeInt(uid);
- e.writeLong(userTimeUs);
- e.writeLong(systemTimeUs);
- pulledData.add(e);
- });
- }
-
- private void pullKernelUidCpuFreqTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
- for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
- if (cpuFreqTimeMs[freqIndex] != 0) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeInt(uid);
- e.writeInt(freqIndex);
- e.writeLong(cpuFreqTimeMs[freqIndex]);
- pulledData.add(e);
- }
- }
- });
- }
-
- private void pullKernelUidCpuClusterTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
- for (int i = 0; i < cpuClusterTimesMs.length; i++) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeInt(uid);
- e.writeInt(i);
- e.writeLong(cpuClusterTimesMs[i]);
- pulledData.add(e);
- }
- });
- }
-
- private void pullKernelUidCpuActiveTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeInt(uid);
- e.writeLong((long) cpuActiveTimesMs);
- pulledData.add(e);
- });
- }
-
private void pullWifiActivityInfo(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
@@ -1143,33 +819,6 @@
}
}
- private void pullBluetoothActivityInfo(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- BluetoothActivityEnergyInfo info = fetchBluetoothData();
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeLong(info.getTimeStamp());
- e.writeInt(info.getBluetoothStackState());
- e.writeLong(info.getControllerTxTimeMillis());
- e.writeLong(info.getControllerRxTimeMillis());
- e.writeLong(info.getControllerIdleTimeMillis());
- e.writeLong(info.getControllerEnergyUsed());
- pulledData.add(e);
- }
-
- private synchronized BluetoothActivityEnergyInfo fetchBluetoothData() {
- final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver(
- "bluetooth");
- adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
- return awaitControllerInfo(bluetoothReceiver);
- } else {
- Slog.e(TAG, "Failed to get bluetooth adapter!");
- return null;
- }
- }
-
private void pullSystemElapsedRealtime(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
@@ -1178,13 +827,6 @@
pulledData.add(e);
}
- private void pullSystemUpTime(int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeLong(SystemClock.uptimeMillis());
- pulledData.add(e);
- }
-
private void pullProcessMemoryState(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
@@ -1762,36 +1404,6 @@
}
}
- private void pullPowerProfile(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- PowerProfile powerProfile = new PowerProfile(mContext);
- Objects.requireNonNull(powerProfile);
-
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- ProtoOutputStream proto = new ProtoOutputStream();
- powerProfile.dumpDebug(proto);
- proto.flush();
- e.writeStorage(proto.getBytes());
- pulledData.add(e);
- }
-
- private void pullBuildInformation(int tagId,
- long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeString(Build.FINGERPRINT);
- e.writeString(Build.BRAND);
- e.writeString(Build.PRODUCT);
- e.writeString(Build.DEVICE);
- e.writeString(Build.VERSION.RELEASE);
- e.writeString(Build.ID);
- e.writeString(Build.VERSION.INCREMENTAL);
- e.writeString(Build.TYPE);
- e.writeString(Build.TAGS);
- pulledData.add(e);
- }
-
private BatteryStatsHelper getBatteryStatsHelper() {
if (mBatteryStatsHelper == null) {
final long callingToken = Binder.clearCallingIdentity();
@@ -2382,149 +1994,110 @@
long elapsedNanos = SystemClock.elapsedRealtimeNanos();
long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
switch (tagId) {
- case StatsLog.WIFI_BYTES_TRANSFER: {
- pullWifiBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.MOBILE_BYTES_TRANSFER: {
- pullMobileBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
- pullWifiBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
- pullMobileBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.BLUETOOTH_BYTES_TRANSFER: {
- pullBluetoothBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.KERNEL_WAKELOCK: {
- pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.CPU_TIME_PER_FREQ: {
- pullCpuTimePerFreq(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.CPU_TIME_PER_UID: {
- pullKernelUidCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.CPU_TIME_PER_UID_FREQ: {
- pullKernelUidCpuFreqTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.CPU_CLUSTER_TIME: {
- pullKernelUidCpuClusterTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.CPU_ACTIVE_TIME: {
- pullKernelUidCpuActiveTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
+
case StatsLog.WIFI_ACTIVITY_INFO: {
pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.MODEM_ACTIVITY_INFO: {
pullModemActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
- case StatsLog.BLUETOOTH_ACTIVITY_INFO: {
- pullBluetoothActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.SYSTEM_UPTIME: {
- pullSystemUpTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
+
case StatsLog.SYSTEM_ELAPSED_REALTIME: {
pullSystemElapsedRealtime(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.PROCESS_MEMORY_STATE: {
pullProcessMemoryState(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.PROCESS_MEMORY_HIGH_WATER_MARK: {
pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.PROCESS_MEMORY_SNAPSHOT: {
pullProcessMemorySnapshot(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.SYSTEM_ION_HEAP_SIZE: {
pullSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.PROCESS_SYSTEM_ION_HEAP_SIZE: {
pullProcessSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.BINDER_CALLS: {
pullBinderCallsStats(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.BINDER_CALLS_EXCEPTIONS: {
pullBinderCallsStatsExceptions(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.LOOPER_STATS: {
pullLooperStats(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DISK_STATS: {
pullDiskStats(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DIRECTORY_USAGE: {
pullDirectoryUsage(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.APP_SIZE: {
pullAppSize(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.CATEGORY_SIZE: {
pullCategorySize(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.NUM_FINGERPRINTS_ENROLLED: {
pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FINGERPRINT, tagId,
elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.NUM_FACES_ENROLLED: {
pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FACE, tagId, elapsedNanos,
wallClockNanos, ret);
break;
}
+
case StatsLog.PROC_STATS: {
pullProcessStats(ProcessStats.REPORT_ALL, tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.PROC_STATS_PKG_PROC: {
pullProcessStats(ProcessStats.REPORT_PKG_PROC_STATS, tagId, elapsedNanos,
wallClockNanos, ret);
break;
}
+
case StatsLog.DISK_IO: {
pullDiskIo(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
- case StatsLog.POWER_PROFILE: {
- pullPowerProfile(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.BUILD_INFORMATION: {
- pullBuildInformation(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
+
case StatsLog.PROCESS_CPU_TIME: {
pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
break;
@@ -2533,73 +2106,90 @@
pullCpuTimePerThreadFreq(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DEVICE_CALCULATED_POWER_USE: {
pullDeviceCalculatedPowerUse(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID: {
pullDeviceCalculatedPowerBlameUid(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER: {
pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.TEMPERATURE: {
pullTemperature(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.COOLING_DEVICE: {
pullCoolingDevices(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DEBUG_ELAPSED_CLOCK: {
pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: {
pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.ROLE_HOLDER: {
pullRoleHolders(elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DANGEROUS_PERMISSION_STATE: {
pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE, elapsedNanos,
wallClockNanos, ret);
break;
}
+
case StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED: {
pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED,
elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.TIME_ZONE_DATA_INFO: {
pullTimeZoneDataInfo(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.EXTERNAL_STORAGE_INFO: {
pullExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.APPS_ON_EXTERNAL_STORAGE_INFO: {
pullAppsOnExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.FACE_SETTINGS: {
pullFaceSettings(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.APP_OPS: {
pullAppOps(elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.NOTIFICATION_REMOTE_VIEWS: {
pullNotificationStats(NotificationManagerService.REPORT_REMOTE_VIEWS,
tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
@@ -2634,57 +2224,6 @@
}
}
- @Override
- public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
- int[] additiveFields, IPullAtomCallback pullerCallback) {
- synchronized (sStatsdLock) {
- // Always cache the puller in SCS.
- // If statsd is down, we will register it when it comes back up.
- int callingUid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- PullerKey key = new PullerKey(callingUid, atomTag);
- PullerValue val = new PullerValue(
- coolDownNs, timeoutNs, additiveFields, pullerCallback);
- mPullers.put(key, val);
-
- if (sStatsd == null) {
- Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
- return;
- }
- try {
- sStatsd.registerPullAtomCallback(
- callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
-
- @Override
- public void unregisterPullAtomCallback(int atomTag) {
- synchronized (sStatsdLock) {
- // Always remove the puller in SCS.
- // If statsd is down, we will not register it when it comes back up.
- int callingUid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- PullerKey key = new PullerKey(callingUid, atomTag);
- mPullers.remove(key);
-
- if (sStatsd == null) {
- Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
- return;
- }
- try {
- sStatsd.unregisterPullAtomCallback(callingUid, atomTag);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
// Statsd related code
@@ -2763,8 +2302,6 @@
// Pull the latest state of UID->app name, version mapping when
// statsd starts.
informAllUidsLocked(mContext);
- // Register all pullers. If SCS has just started, this should be empty.
- registerAllPullersLocked();
} finally {
restoreCallingIdentity(token);
}
@@ -2776,17 +2313,6 @@
}
}
- @GuardedBy("sStatsdLock")
- private void registerAllPullersLocked() throws RemoteException {
- // TODO: pass in one call, using a file descriptor (similar to uidmap).
- for (Map.Entry<PullerKey, PullerValue> entry : mPullers.entrySet()) {
- PullerKey key = entry.getKey();
- PullerValue val = entry.getValue();
- sStatsd.registerPullAtomCallback(key.getUid(), key.getAtom(), val.getCoolDownNs(),
- val.getTimeoutNs(), val.getAdditiveFields(), val.getCallback());
- }
- }
-
private class StatsdDeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
index b27d0f7..04d8b00 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -24,6 +24,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.os.Binder;
+import android.os.IPullAtomCallback;
import android.os.IStatsManagerService;
import android.os.IStatsd;
import android.os.Process;
@@ -60,8 +61,7 @@
@GuardedBy("mLock")
private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>();
@GuardedBy("mLock")
- private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap =
- new ArrayMap<>();
+ private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>();
@GuardedBy("mLock")
private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap =
new ArrayMap<>();
@@ -72,8 +72,8 @@
}
private static class ConfigKey {
- private int mUid;
- private long mConfigId;
+ private final int mUid;
+ private final long mConfigId;
ConfigKey(int uid, long configId) {
mUid = uid;
@@ -103,6 +103,126 @@
}
}
+ private static class PullerKey {
+ private final int mUid;
+ private final int mAtomTag;
+
+ PullerKey(int uid, int atom) {
+ mUid = uid;
+ mAtomTag = atom;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public int getAtom() {
+ return mAtomTag;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUid, mAtomTag);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof PullerKey) {
+ PullerKey other = (PullerKey) obj;
+ return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
+ }
+ return false;
+ }
+ }
+
+ private static class PullerValue {
+ private final long mCoolDownNs;
+ private final long mTimeoutNs;
+ private final int[] mAdditiveFields;
+ private final IPullAtomCallback mCallback;
+
+ PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
+ IPullAtomCallback callback) {
+ mCoolDownNs = coolDownNs;
+ mTimeoutNs = timeoutNs;
+ mAdditiveFields = additiveFields;
+ mCallback = callback;
+ }
+
+ public long getCoolDownNs() {
+ return mCoolDownNs;
+ }
+
+ public long getTimeoutNs() {
+ return mTimeoutNs;
+ }
+
+ public int[] getAdditiveFields() {
+ return mAdditiveFields;
+ }
+
+ public IPullAtomCallback getCallback() {
+ return mCallback;
+ }
+ }
+
+ private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>();
+
+ @Override
+ public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+ int[] additiveFields, IPullAtomCallback pullerCallback) {
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ PullerKey key = new PullerKey(callingUid, atomTag);
+ PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback);
+
+ // Always cache the puller in StatsManagerService. If statsd is down, we will register the
+ // puller when statsd comes back up.
+ synchronized (mLock) {
+ mPullers.put(key, val);
+ }
+
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd == null) {
+ return;
+ }
+
+ try {
+ statsd.registerPullAtomCallback(
+ callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unregisterPullAtomCallback(int atomTag) {
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ PullerKey key = new PullerKey(callingUid, atomTag);
+
+ // Always remove the puller from StatsManagerService even if statsd is down. When statsd
+ // comes back up, we will not re-register the removed puller.
+ synchronized (mLock) {
+ mPullers.remove(key);
+ }
+
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd == null) {
+ return;
+ }
+
+ try {
+ statsd.unregisterPullAtomCallback(callingUid, atomTag);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
public void setDataFetchOperation(long configId, PendingIntent pendingIntent,
String packageName) {
@@ -441,46 +561,85 @@
if (statsd == null) {
return;
}
- // Since we do not want to make an IPC with the a lock held, we first create local deep
- // copies of the data with the lock held before iterating through the maps.
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ registerAllPullers(statsd);
+ registerAllDataFetchOperations(statsd);
+ registerAllActiveConfigsChangedOperations(statsd);
+ registerAllBroadcastSubscribers(statsd);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "StatsManager failed to (re-)register data with statsd");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Pre-condition: the Binder calling identity has already been cleared
+ private void registerAllPullers(IStatsd statsd) throws RemoteException {
+ // Since we do not want to make an IPC with the lock held, we first create a copy of the
+ // data with the lock held before iterating through the map.
+ ArrayMap<PullerKey, PullerValue> pullersCopy;
+ synchronized (mLock) {
+ pullersCopy = new ArrayMap<>(mPullers);
+ }
+
+ for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) {
+ PullerKey key = entry.getKey();
+ PullerValue value = entry.getValue();
+ statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownNs(),
+ value.getTimeoutNs(), value.getAdditiveFields(), value.getCallback());
+ }
+ }
+
+ // Pre-condition: the Binder calling identity has already been cleared
+ private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException {
+ // Since we do not want to make an IPC with the lock held, we first create a copy of the
+ // data with the lock held before iterating through the map.
ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy;
- ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
- ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy;
synchronized (mLock) {
dataFetchCopy = new ArrayMap<>(mDataFetchPirMap);
- activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
- broadcastSubscriberCopy = new ArrayMap<>();
- for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry
- : mBroadcastSubscriberPirMap.entrySet()) {
- broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap<>(entry.getValue()));
- }
}
+
for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) {
ConfigKey key = entry.getKey();
- try {
- statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to setDataFetchOperation from pirMap");
+ statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
+ }
+ }
+
+ // Pre-condition: the Binder calling identity has already been cleared
+ private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException {
+ // Since we do not want to make an IPC with the lock held, we first create a copy of the
+ // data with the lock held before iterating through the map.
+ ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
+ synchronized (mLock) {
+ activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
+ }
+
+ for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) {
+ statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
+ }
+ }
+
+ // Pre-condition: the Binder calling identity has already been cleared
+ private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException {
+ // Since we do not want to make an IPC with the lock held, we first create a deep copy of
+ // the data with the lock held before iterating through the map.
+ ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy =
+ new ArrayMap<>();
+ synchronized (mLock) {
+ for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+ mBroadcastSubscriberPirMap.entrySet()) {
+ broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue()));
}
}
- for (Map.Entry<Integer, PendingIntentRef> entry
- : activeConfigsChangedCopy.entrySet()) {
- try {
- statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to setActiveConfigsChangedOperation from pirMap");
- }
- }
- for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry
- : broadcastSubscriberCopy.entrySet()) {
+
+ for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+ mBroadcastSubscriberPirMap.entrySet()) {
+ ConfigKey configKey = entry.getKey();
for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) {
- ConfigKey configKey = entry.getKey();
- try {
- statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
- subscriberEntry.getValue(), configKey.getUid());
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to setBroadcastSubscriber from pirMap");
- }
+ statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
+ subscriberEntry.getValue(), configKey.getUid());
}
}
}
diff --git a/api/current.txt b/api/current.txt
index edb31df..a3d8458 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -115,6 +115,7 @@
field public static final String READ_LOGS = "android.permission.READ_LOGS";
field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+ field public static final String READ_PRECISE_PHONE_STATE = "android.permission.READ_PRECISE_PHONE_STATE";
field public static final String READ_SMS = "android.permission.READ_SMS";
field public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
field public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
@@ -385,6 +386,7 @@
field public static final int canRequestFingerprintGestures = 16844109; // 0x101054d
field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
field public static final int canRetrieveWindowContent = 16843653; // 0x1010385
+ field public static final int canTakeScreenshot = 16844304; // 0x1010610
field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
field public static final int cantSaveState = 16844142; // 0x101056e
field @Deprecated public static final int capitalize = 16843113; // 0x1010169
@@ -1144,6 +1146,7 @@
field public static final int resizeable = 16843405; // 0x101028d
field public static final int resizeableActivity = 16844022; // 0x10104f6
field public static final int resource = 16842789; // 0x1010025
+ field public static final int resourcesMap = 16844297; // 0x1010609
field public static final int restoreAnyVersion = 16843450; // 0x10102ba
field @Deprecated public static final int restoreNeedsApplication = 16843421; // 0x101029d
field public static final int restrictedAccountType = 16843733; // 0x10103d5
@@ -2862,6 +2865,7 @@
method protected void onServiceConnected();
method public final boolean performGlobalAction(int);
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
+ method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>);
field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2
field public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15; // 0xf
field public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; // 0x10
@@ -2923,6 +2927,7 @@
method public int getShowMode();
method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
method public boolean setShowMode(int);
+ method public boolean switchToInputMethod(@NonNull String);
}
public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
@@ -2957,6 +2962,7 @@
field public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 64; // 0x40
field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1
+ field public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 128; // 0x80
field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityServiceInfo> CREATOR;
field public static final int DEFAULT = 1; // 0x1
field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
@@ -5837,6 +5843,7 @@
method public void enableLights(boolean);
method public void enableVibration(boolean);
method public android.media.AudioAttributes getAudioAttributes();
+ method @Nullable public String getConversationId();
method public String getDescription();
method public String getGroup();
method public String getId();
@@ -5844,12 +5851,14 @@
method public int getLightColor();
method public int getLockscreenVisibility();
method public CharSequence getName();
+ method @Nullable public String getParentChannelId();
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
method public boolean hasUserSetImportance();
method public boolean hasUserSetSound();
method public void setAllowBubbles(boolean);
method public void setBypassDnd(boolean);
+ method public void setConversationId(@Nullable String, @Nullable String);
method public void setDescription(String);
method public void setGroup(String);
method public void setImportance(int);
@@ -5862,6 +5871,7 @@
method public boolean shouldShowLights();
method public boolean shouldVibrate();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
}
@@ -5903,6 +5913,7 @@
method public final int getCurrentInterruptionFilter();
method public int getImportance();
method public android.app.NotificationChannel getNotificationChannel(String);
+ method @Nullable public android.app.NotificationChannel getNotificationChannel(@NonNull String, @NonNull String);
method public android.app.NotificationChannelGroup getNotificationChannelGroup(String);
method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
@@ -6756,6 +6767,7 @@
method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
method public CharSequence getDeviceOwnerLockScreenInfo();
method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
+ method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName);
method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName);
method @NonNull public java.util.List<byte[]> getInstalledCaCerts(@Nullable android.content.ComponentName);
@@ -6827,6 +6839,7 @@
method public boolean isManagedProfile(@NonNull android.content.ComponentName);
method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName);
+ method public boolean isOrganizationOwnedDeviceWithManagedProfile();
method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName);
method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isProfileOwnerApp(String);
@@ -6874,6 +6887,7 @@
method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence);
method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
+ method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy);
method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName);
method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String);
method public void setGlobalSetting(@NonNull android.content.ComponentName, String, String);
@@ -7108,6 +7122,21 @@
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DnsEvent> CREATOR;
}
+ public final class FactoryResetProtectionPolicy implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getFactoryResetProtectionAccounts();
+ method public boolean isFactoryResetProtectionDisabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FactoryResetProtectionPolicy> CREATOR;
+ }
+
+ public static class FactoryResetProtectionPolicy.Builder {
+ ctor public FactoryResetProtectionPolicy.Builder();
+ method @NonNull public android.app.admin.FactoryResetProtectionPolicy build();
+ method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionAccounts(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionDisabled(boolean);
+ }
+
public class FreezePeriod {
ctor public FreezePeriod(java.time.MonthDay, java.time.MonthDay);
method public java.time.MonthDay getEnd();
@@ -9823,6 +9852,7 @@
method public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int);
method public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
+ method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
@@ -10375,6 +10405,7 @@
field public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
field public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
field public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+ field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
field public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
field public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
field public static final String ACTION_DEFAULT = "android.intent.action.VIEW";
@@ -10600,6 +10631,7 @@
field public static final String EXTRA_SUSPENDED_PACKAGE_EXTRAS = "android.intent.extra.SUSPENDED_PACKAGE_EXTRAS";
field public static final String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
field public static final String EXTRA_TEXT = "android.intent.extra.TEXT";
+ field public static final String EXTRA_TIME = "android.intent.extra.TIME";
field public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
field public static final String EXTRA_UID = "android.intent.extra.UID";
field public static final String EXTRA_USER = "android.intent.extra.USER";
@@ -11358,6 +11390,7 @@
public class CrossProfileApps {
method public boolean canInteractAcrossProfiles();
method public boolean canRequestInteractAcrossProfiles();
+ method @Nullable public android.content.Intent createRequestInteractAcrossProfilesIntent();
method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle);
method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle);
method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles();
@@ -11391,6 +11424,7 @@
public final class InstallSourceInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getInitiatingPackageName();
+ method @Nullable public android.content.pm.SigningInfo getInitiatingPackageSigningInfo();
method @Nullable public String getInstallingPackageName();
method @Nullable public String getOriginatingPackageName();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -11757,6 +11791,7 @@
method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo);
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo);
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public CharSequence getBackgroundPermissionButtonLabel();
method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
@@ -16825,6 +16860,8 @@
method @Nullable public CharSequence getSubtitle();
method @NonNull public CharSequence getTitle();
method public boolean isConfirmationRequired();
+ field public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; // 0x2
+ field public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; // 0x1
field public static final int BIOMETRIC_ACQUIRED_GOOD = 0; // 0x0
field public static final int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
field public static final int BIOMETRIC_ACQUIRED_INSUFFICIENT = 2; // 0x2
@@ -16854,6 +16891,7 @@
}
public static class BiometricPrompt.AuthenticationResult {
+ method public int getAuthenticationType();
method public android.hardware.biometrics.BiometricPrompt.CryptoObject getCryptoObject();
}
@@ -17053,13 +17091,13 @@
method public abstract void close();
method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
method @NonNull public android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int, java.util.Set<java.lang.String>) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(@NonNull android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException;
method @NonNull public abstract String getId();
method public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
@@ -23655,6 +23693,7 @@
field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
field public static final int TYPE_BUILTIN_MIC = 15; // 0xf
field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
+ field public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24; // 0x18
field public static final int TYPE_BUS = 21; // 0x15
field public static final int TYPE_DOCK = 13; // 0xd
field public static final int TYPE_FM = 14; // 0xe
@@ -26288,6 +26327,59 @@
field public static final int SURFACE = 2; // 0x2
}
+ public final class MediaRoute2Info implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getConnectionState();
+ method @Nullable public CharSequence getDescription();
+ method public int getDeviceType();
+ method @Nullable public android.os.Bundle getExtras();
+ method @NonNull public java.util.List<java.lang.String> getFeatures();
+ method @Nullable public android.net.Uri getIconUri();
+ method @NonNull public String getId();
+ method @NonNull public CharSequence getName();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method public boolean hasAnyFeatures(@NonNull java.util.Collection<java.lang.String>);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+ field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+ field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.MediaRoute2Info> CREATOR;
+ field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+ field public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_REMOTE_TV = 1; // 0x1
+ field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ }
+
+ public static final class MediaRoute2Info.Builder {
+ ctor public MediaRoute2Info.Builder(@NonNull String, @NonNull CharSequence);
+ ctor public MediaRoute2Info.Builder(@NonNull android.media.MediaRoute2Info);
+ method @NonNull public android.media.MediaRoute2Info.Builder addFeature(@NonNull String);
+ method @NonNull public android.media.MediaRoute2Info.Builder addFeatures(@NonNull java.util.Collection<java.lang.String>);
+ method @NonNull public android.media.MediaRoute2Info build();
+ method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures();
+ method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String);
+ method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
+ method @NonNull public android.media.MediaRoute2Info.Builder setDeviceType(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
+ method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+ method @NonNull public android.media.MediaRoute2Info.Builder setVolume(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setVolumeHandling(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setVolumeMax(int);
+ }
+
+ public abstract class MediaRoute2ProviderService extends android.app.Service {
+ ctor public MediaRoute2ProviderService();
+ method public final void notifyRoutes(@NonNull java.util.Collection<android.media.MediaRoute2Info>);
+ method @NonNull public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public void onDiscoveryPreferenceChanged(@NonNull android.media.RouteDiscoveryPreference);
+ field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
+ }
+
public class MediaRouter {
method public void addCallback(int, android.media.MediaRouter.Callback);
method public void addCallback(int, android.media.MediaRouter.Callback, int);
@@ -26411,6 +26503,20 @@
method public abstract void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo, int);
}
+ public class MediaRouter2 {
+ method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
+ method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
+ method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
+ method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback);
+ }
+
+ public static class MediaRouter2.RouteCallback {
+ ctor public MediaRouter2.RouteCallback();
+ method public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ }
+
public class MediaScannerConnection implements android.content.ServiceConnection {
ctor public MediaScannerConnection(android.content.Context, android.media.MediaScannerConnection.MediaScannerConnectionClient);
method public void connect();
@@ -26769,6 +26875,22 @@
field public static final int URI_COLUMN_INDEX = 2; // 0x2
}
+ public final class RouteDiscoveryPreference implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
+ method public boolean isActiveScan();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
+ }
+
+ public static final class RouteDiscoveryPreference.Builder {
+ ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
+ ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
+ method @NonNull public android.media.RouteDiscoveryPreference build();
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setActiveScan(boolean);
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+ }
+
public final class Session2Command implements android.os.Parcelable {
ctor public Session2Command(int);
ctor public Session2Command(@NonNull String, @Nullable android.os.Bundle);
@@ -27027,9 +27149,11 @@
public abstract class VolumeProvider {
ctor public VolumeProvider(int, int, int);
+ ctor public VolumeProvider(int, int, int, @Nullable String);
method public final int getCurrentVolume();
method public final int getMaxVolume();
method public final int getVolumeControl();
+ method @Nullable public final String getVolumeControlId();
method public void onAdjustVolume(int);
method public void onSetVolumeTo(int);
method public final void setCurrentVolume(int);
@@ -27889,6 +28013,7 @@
method public int getMaxVolume();
method public int getPlaybackType();
method public int getVolumeControl();
+ method @Nullable public String getVolumeControlId();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.session.MediaController.PlaybackInfo> CREATOR;
field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
@@ -28590,7 +28715,9 @@
ctor public TvInputService();
method public final android.os.IBinder onBind(android.content.Intent);
method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(String);
+ method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(@NonNull String, @NonNull String);
method @Nullable public abstract android.media.tv.TvInputService.Session onCreateSession(String);
+ method @Nullable public android.media.tv.TvInputService.Session onCreateSession(@NonNull String, @NonNull String);
field public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
field public static final String SERVICE_META_DATA = "android.media.tv.input";
}
@@ -28693,7 +28820,7 @@
method public boolean isEncrypted();
method public boolean isHardOfHearing();
method public boolean isSpokenSubtitle();
- 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.media.tv.TvTrackInfo> CREATOR;
field public static final int TYPE_AUDIO = 0; // 0x0
field public static final int TYPE_SUBTITLE = 2; // 0x2
@@ -28702,21 +28829,21 @@
public static final class TvTrackInfo.Builder {
ctor public TvTrackInfo.Builder(int, @NonNull String);
- method public android.media.tv.TvTrackInfo build();
- method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
+ method @NonNull public android.media.tv.TvTrackInfo build();
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioDescription(boolean);
- method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
- method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setDescription(@NonNull CharSequence);
method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
- method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setExtra(@NonNull android.os.Bundle);
method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean);
- method public android.media.tv.TvTrackInfo.Builder setLanguage(String);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setLanguage(@NonNull String);
method @NonNull public android.media.tv.TvTrackInfo.Builder setSpokenSubtitle(boolean);
- method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
- method public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
- method public android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
- method public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
- method public android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
}
public class TvView extends android.view.ViewGroup {
@@ -29528,7 +29655,7 @@
method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
method public android.net.NetworkRequest.Builder removeCapability(int);
method public android.net.NetworkRequest.Builder removeTransportType(int);
- method public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
+ method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
}
@@ -29627,6 +29754,19 @@
method public void onStopped();
}
+ public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getSubscriptionId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.TelephonyNetworkSpecifier> CREATOR;
+ }
+
+ public static final class TelephonyNetworkSpecifier.Builder {
+ ctor public TelephonyNetworkSpecifier.Builder();
+ method @NonNull public android.net.TelephonyNetworkSpecifier build();
+ method @NonNull public android.net.TelephonyNetworkSpecifier.Builder setSubscriptionId(int);
+ }
+
public class TrafficStats {
ctor public TrafficStats();
method public static void clearThreadStatsTag();
@@ -30384,6 +30524,11 @@
field @Deprecated public static final String[] strings;
}
+ @Deprecated public static class WifiConfiguration.SuiteBCipher {
+ field @Deprecated public static final int ECDHE_ECDSA = 0; // 0x0
+ field @Deprecated public static final int ECDHE_RSA = 1; // 0x1
+ }
+
public class WifiEnterpriseConfig implements android.os.Parcelable {
ctor public WifiEnterpriseConfig();
ctor public WifiEnterpriseConfig(android.net.wifi.WifiEnterpriseConfig);
@@ -30394,6 +30539,7 @@
method @Nullable public java.security.cert.X509Certificate[] getCaCertificates();
method public java.security.cert.X509Certificate getClientCertificate();
method @Nullable public java.security.cert.X509Certificate[] getClientCertificateChain();
+ method @Nullable public java.security.PrivateKey getClientPrivateKey();
method public String getDomainSuffixMatch();
method public int getEapMethod();
method public String getIdentity();
@@ -30533,6 +30679,7 @@
field public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
field public static final String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
field public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
+ field public static final String ACTION_WIFI_SCAN_AVAILABLE = "android.net.wifi.action.WIFI_SCAN_AVAILABLE";
field @Deprecated public static final int ERROR_AUTHENTICATING = 1; // 0x1
field @Deprecated public static final String EXTRA_BSSID = "bssid";
field public static final String EXTRA_NETWORK_INFO = "networkInfo";
@@ -30541,6 +30688,7 @@
field @Deprecated public static final String EXTRA_NEW_STATE = "newState";
field public static final String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
field public static final String EXTRA_RESULTS_UPDATED = "resultsUpdated";
+ field public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE";
field @Deprecated public static final String EXTRA_SUPPLICANT_CONNECTED = "connected";
field @Deprecated public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError";
field @Deprecated public static final String EXTRA_WIFI_INFO = "wifiInfo";
@@ -30657,11 +30805,11 @@
ctor public WifiNetworkSuggestion.Builder();
method @NonNull public android.net.wifi.WifiNetworkSuggestion build();
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setBssid(@NonNull android.net.MacAddress);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setCredentialSharedWithUser(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsAppInteractionRequired(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsEnhancedOpen(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsHiddenSsid(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsMetered(boolean);
- method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserAllowedToManuallyConnect(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserInteractionRequired(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
@@ -36066,6 +36214,7 @@
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
method public boolean hasUserRestriction(String);
method public boolean isDemoUser();
+ method public boolean isManagedProfile();
method public boolean isQuietModeEnabled(android.os.UserHandle);
method public boolean isSystemUser();
method public boolean isUserAGoat();
@@ -39089,6 +39238,7 @@
method @NonNull public static android.app.PendingIntent createWriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri);
method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
+ method public static long getGeneration(@NonNull android.content.Context, @NonNull String);
method public static android.net.Uri getMediaScannerUri();
method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
method @NonNull public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
@@ -39334,6 +39484,7 @@
field @Deprecated public static final String LONGITUDE = "longitude";
field @Deprecated public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
field @Deprecated public static final String PICASA_ID = "picasa_id";
+ field public static final String SCENE_CAPTURE_TYPE = "scene_capture_type";
}
public static final class MediaStore.Images.Media implements android.provider.MediaStore.Images.ImageColumns {
@@ -39398,6 +39549,8 @@
field public static final String DISPLAY_NAME = "_display_name";
field public static final String DOCUMENT_ID = "document_id";
field public static final String DURATION = "duration";
+ field public static final String GENERATION_ADDED = "generation_added";
+ field public static final String GENERATION_MODIFIED = "generation_modified";
field public static final String GENRE = "genre";
field public static final String HEIGHT = "height";
field public static final String INSTANCE_ID = "instance_id";
@@ -42675,11 +42828,20 @@
method public android.content.Intent createEnrollIntent();
method public android.content.Intent createReEnrollIntent();
method public android.content.Intent createUnEnrollIntent();
+ method public int getParameter(int);
+ method public int getSupportedAudioCapabilities();
method public int getSupportedRecognitionModes();
+ method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
+ method public int setParameter(int, int);
method public boolean startRecognition(int);
method public boolean stopRecognition();
+ field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+ field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
+ field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
@@ -42702,6 +42864,11 @@
method @Nullable public byte[] getTriggerAudio();
}
+ public static final class AlwaysOnHotwordDetector.ModelParamRange {
+ method public int end();
+ method public int start();
+ }
+
public class VoiceInteractionService extends android.app.Service {
ctor public VoiceInteractionService();
method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
@@ -43916,6 +44083,7 @@
method public java.util.List<android.telecom.Call> getChildren();
method public java.util.List<android.telecom.Call> getConferenceableCalls();
method public android.telecom.Call.Details getDetails();
+ method @Nullable public android.telecom.Call getGenericConferenceActiveChildCall();
method public android.telecom.Call getParent();
method public String getRemainingPostDialSequence();
method @Nullable public android.telecom.Call.RttCall getRttCall();
@@ -43999,6 +44167,7 @@
method public int getCallerDisplayNamePresentation();
method public int getCallerNumberVerificationStatus();
method public final long getConnectTimeMillis();
+ method @Nullable public String getContactDisplayName();
method public long getCreationTimeMillis();
method public android.telecom.DisconnectCause getDisconnectCause();
method public android.os.Bundle getExtras();
@@ -45008,6 +45177,39 @@
field public static final int PRIORITY_MED = 2; // 0x2
}
+ public final class BarringInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.telephony.BarringInfo.BarringServiceInfo getBarringServiceInfo(int);
+ method public boolean isServiceBarred(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int BARRING_SERVICE_TYPE_CS_FALLBACK = 5; // 0x5
+ field public static final int BARRING_SERVICE_TYPE_CS_SERVICE = 0; // 0x0
+ field public static final int BARRING_SERVICE_TYPE_CS_VOICE = 2; // 0x2
+ field public static final int BARRING_SERVICE_TYPE_EMERGENCY = 8; // 0x8
+ field public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO = 7; // 0x7
+ field public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE = 6; // 0x6
+ field public static final int BARRING_SERVICE_TYPE_MO_DATA = 4; // 0x4
+ field public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING = 3; // 0x3
+ field public static final int BARRING_SERVICE_TYPE_PS_SERVICE = 1; // 0x1
+ field public static final int BARRING_SERVICE_TYPE_SMS = 9; // 0x9
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.BarringInfo> CREATOR;
+ }
+
+ public static final class BarringInfo.BarringServiceInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getBarringType();
+ method public int getConditionalBarringFactor();
+ method public int getConditionalBarringTimeSeconds();
+ method public boolean isBarred();
+ method public boolean isConditionallyBarred();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int BARRING_TYPE_CONDITIONAL = 1; // 0x1
+ field public static final int BARRING_TYPE_NONE = 0; // 0x0
+ field public static final int BARRING_TYPE_UNCONDITIONAL = 2; // 0x2
+ field public static final int BARRING_TYPE_UNKNOWN = -1; // 0xffffffff
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.BarringInfo.BarringServiceInfo> CREATOR;
+ }
+
public class CarrierConfigManager {
method @Nullable public android.os.PersistableBundle getConfig();
method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
@@ -45020,6 +45222,9 @@
field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
+ field public static final String IMSI_KEY_AVAILABILITY_INT = "imsi_key_availability_int";
+ field public static final String KEY_5G_ICON_CONFIGURATION_STRING = "5g_icon_configuration_string";
+ field public static final String KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT = "5g_icon_display_grace_period_sec_int";
field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array";
field public static final String KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrq_thresholds_int_array";
field public static final String KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY = "5g_nr_sssinr_thresholds_int_array";
@@ -45033,18 +45238,32 @@
field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool";
field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool";
field public static final String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = "allow_non_emergency_calls_in_ecm_bool";
- field public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
+ field public static final String KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL = "allow_video_calling_fallback_bool";
+ field public static final String KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL = "always_show_data_rat_icon_bool";
+ field @Deprecated public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
+ field public static final String KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN = "always_show_primary_signal_bar_in_opportunistic_network_boolean";
field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
+ field public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY = "apn_settings_default_apn_types_string_array";
field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool";
field public static final String KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL = "call_barring_supports_password_change_bool";
field public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = "call_barring_visibility_bool";
field public static final String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array";
+ field public static final String KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING = "call_redirection_service_component_name_string";
+ field public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL = "carrier_allow_deflect_ims_call_bool";
field public static final String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool";
field public static final String KEY_CARRIER_APP_REQUIRED_DURING_SIM_SETUP_BOOL = "carrier_app_required_during_setup_bool";
field public static final String KEY_CARRIER_CALL_SCREENING_APP_STRING = "call_screening_app";
+ field public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY = "carrier_certificate_string_array";
+ field public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
field public static final String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string";
field public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
+ field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY = "carrier_default_actions_on_dcfailure_string_array";
+ field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE = "carrier_default_actions_on_default_network_available_string_array";
+ field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY = "carrier_default_actions_on_redirection_string_array";
+ field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET = "carrier_default_actions_on_reset_string_array";
+ field public static final String KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY = "carrier_default_redirection_url_string_array";
+ field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL = "carrier_default_wfc_ims_enabled_bool";
field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT = "carrier_default_wfc_ims_mode_int";
field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT = "carrier_default_wfc_ims_roaming_mode_int";
field @Deprecated public static final String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL = "carrier_force_disable_etws_cmas_test_bool";
@@ -45056,11 +45275,14 @@
field public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT = "carrier_instant_lettering_length_limit_int";
field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
+ field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
+ field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool";
field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
+ field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool";
field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
@@ -45074,6 +45296,7 @@
field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
+ field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
@@ -45083,6 +45306,7 @@
field public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING = "config_ims_rcs_package_override_string";
field public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
field public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
+ field public static final String KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm";
field public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool";
field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
@@ -45094,6 +45318,7 @@
field public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
+ field public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool";
field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool";
field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array";
field public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = "display_hd_audio_property_bool";
@@ -45103,9 +45328,13 @@
field public static final String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
field public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
field public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
+ field public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool";
+ field public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool";
+ field public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = "emergency_notification_delay_int";
field public static final String KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY = "emergency_number_prefix_string_array";
field public static final String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final String KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL = "enhanced_4g_lte_on_by_default_bool";
+ field public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT = "enhanced_4g_lte_title_variant_int";
field public static final String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
field public static final String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
field public static final String KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
@@ -45114,13 +45343,17 @@
field public static final String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
+ field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
field public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
field public static final String KEY_HIDE_PRESET_APN_DETAILS_BOOL = "hide_preset_apn_details_bool";
field public static final String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
+ field public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS = "ignore_data_enabled_changed_for_video_calls";
+ field public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL = "ignore_rtt_mode_setting_bool";
field public static final String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
+ field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
field public static final String KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "lte_rsrq_thresholds_int_array";
field public static final String KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "lte_rssnr_thresholds_int_array";
field public static final String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
@@ -45156,6 +45389,7 @@
field public static final String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
field public static final String KEY_MMS_USER_AGENT_STRING = "userAgent";
field public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
+ field public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network";
field public static final String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
field public static final String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
field public static final String KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG = "opportunistic_network_data_switch_hysteresis_time_long";
@@ -45169,15 +45403,24 @@
field public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL = "prevent_clir_activation_and_deactivation_code_bool";
field public static final String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array";
field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
+ field public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = "read_only_apn_fields_string_array";
+ field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array";
field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
field @Deprecated public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
field public static final String KEY_RTT_SUPPORTED_BOOL = "rtt_supported_bool";
+ field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
+ field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
+ field public static final String KEY_SHOW_BLOCKING_PAY_PHONE_OPTION_BOOL = "show_blocking_pay_phone_option_bool";
field public static final String KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL = "show_call_blocking_disabled_notification_always_bool";
+ field public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING = "show_carrier_data_icon_pattern_string";
field public static final String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool";
field public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
+ field public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool";
field public static final String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
field public static final String KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL = "show_signal_strength_in_sim_status_bool";
+ field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool";
+ field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool";
field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
@@ -45185,14 +45428,20 @@
field public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool";
field public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
field public static final String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool";
+ field public static final String KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL = "support_enhanced_call_blocking_bool";
+ field public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL = "support_ims_conference_event_package_bool";
field public static final String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
field public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
+ field public static final String KEY_SUPPORT_TDSCDMA_BOOL = "support_tdscdma_bool";
+ field public static final String KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY = "support_tdscdma_roaming_networks_string_array";
field public static final String KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL = "treat_downgraded_video_calls_as_video_calls_bool";
field public static final String KEY_TTY_SUPPORTED_BOOL = "tty_supported_bool";
+ field public static final String KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY = "unloggable_numbers_string_array";
field public static final String KEY_USE_HFA_FOR_PROVISIONING_BOOL = "use_hfa_for_provisioning_bool";
field public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool";
field public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool";
field public static final String KEY_USE_RCS_SIP_OPTIONS_BOOL = "use_rcs_sip_options_bool";
+ field public static final String KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL = "use_wfc_home_network_mode_in_roaming_network_bool";
field public static final String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool";
field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
@@ -45205,6 +45454,8 @@
field public static final String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
field public static final String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
field public static final String KEY_VVM_TYPE_STRING = "vvm_type_string";
+ field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string";
+ field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
@@ -45524,7 +45775,7 @@
method @Nullable public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, @NonNull java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback);
}
- public final class MmsManager {
+ public class MmsManager {
method public void downloadMultimediaMessage(int, @NonNull String, @NonNull android.net.Uri, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
method public void sendMultimediaMessage(int, @NonNull android.net.Uri, @Nullable String, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
}
@@ -45652,6 +45903,7 @@
ctor public PhoneStateListener();
ctor public PhoneStateListener(@NonNull java.util.concurrent.Executor);
method public void onActiveDataSubscriptionIdChanged(int);
+ method public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onCallDisconnectCauseChanged(int, int);
method public void onCallForwardingIndicatorChanged(boolean);
method public void onCallStateChanged(int, String);
@@ -45669,6 +45921,7 @@
method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
method public void onUserMobileDataStateChanged(boolean);
field public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000
+ field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000
field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
field public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8
field public static final int LISTEN_CALL_STATE = 32; // 0x20
@@ -46267,6 +46520,7 @@
field public static final int NETWORK_TYPE_UNKNOWN = 0; // 0x0
field public static final int PHONE_TYPE_CDMA = 2; // 0x2
field public static final int PHONE_TYPE_GSM = 1; // 0x1
+ field public static final int PHONE_TYPE_IMS = 5; // 0x5
field public static final int PHONE_TYPE_NONE = 0; // 0x0
field public static final int PHONE_TYPE_SIP = 3; // 0x3
field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
@@ -54690,6 +54944,7 @@
public final class InlineSuggestionsRequest implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public String getHostPackageName();
method public int getMaxSuggestionCount();
method @NonNull public java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -58713,7 +58968,7 @@
method @android.view.ViewDebug.ExportedProperty public CharSequence getFormat24Hour();
method public String getTimeZone();
method public boolean is24HourModeEnabled();
- method public void refresh();
+ method public void refreshTime();
method public void setFormat12Hour(CharSequence);
method public void setFormat24Hour(CharSequence);
method public void setTimeZone(String);
@@ -59028,6 +59283,7 @@
public class Toast {
ctor public Toast(android.content.Context);
+ method public void addCallback(@NonNull android.widget.Toast.Callback);
method public void cancel();
method public int getDuration();
method public int getGravity();
@@ -59038,6 +59294,7 @@
method public int getYOffset();
method public static android.widget.Toast makeText(android.content.Context, CharSequence, int);
method public static android.widget.Toast makeText(android.content.Context, @StringRes int, int) throws android.content.res.Resources.NotFoundException;
+ method public void removeCallback(@NonNull android.widget.Toast.Callback);
method public void setDuration(int);
method public void setGravity(int, int, int);
method public void setMargin(float, float);
@@ -59049,6 +59306,12 @@
field public static final int LENGTH_SHORT = 0; // 0x0
}
+ public abstract static class Toast.Callback {
+ ctor public Toast.Callback();
+ method public void onToastHidden();
+ method public void onToastShown();
+ }
+
public class ToggleButton extends android.widget.CompoundButton {
ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int, int);
ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int);
diff --git a/api/module-app-current.txt b/api/module-app-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-app-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-app-removed.txt b/api/module-app-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-app-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
new file mode 100644
index 0000000..c8253a0
--- /dev/null
+++ b/api/module-lib-current.txt
@@ -0,0 +1,126 @@
+// Signature format: 2.0
+package android.app.timedetector {
+
+ public final class PhoneTimeSuggestion implements android.os.Parcelable {
+ method public void addDebugInfo(@NonNull String);
+ method public void addDebugInfo(@NonNull java.util.List<java.lang.String>);
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getDebugInfo();
+ method public int getPhoneId();
+ method @Nullable public android.os.TimestampedValue<java.lang.Long> getUtcTime();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.timedetector.PhoneTimeSuggestion> CREATOR;
+ }
+
+ public static final class PhoneTimeSuggestion.Builder {
+ ctor public PhoneTimeSuggestion.Builder(int);
+ method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder addDebugInfo(@NonNull String);
+ method @NonNull public android.app.timedetector.PhoneTimeSuggestion build();
+ method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder setUtcTime(@Nullable android.os.TimestampedValue<java.lang.Long>);
+ }
+
+ public class TimeDetector {
+ method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTime(@NonNull android.app.timedetector.PhoneTimeSuggestion);
+ }
+
+}
+
+package android.app.timezonedetector {
+
+ public final class PhoneTimeZoneSuggestion implements android.os.Parcelable {
+ method public void addDebugInfo(@NonNull String);
+ method public void addDebugInfo(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public static android.app.timezonedetector.PhoneTimeZoneSuggestion createEmptySuggestion(int, @NonNull String);
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getDebugInfo();
+ method public int getMatchType();
+ method public int getPhoneId();
+ method public int getQuality();
+ method @Nullable public String getZoneId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.timezonedetector.PhoneTimeZoneSuggestion> CREATOR;
+ field public static final int MATCH_TYPE_EMULATOR_ZONE_ID = 4; // 0x4
+ field public static final int MATCH_TYPE_NA = 0; // 0x0
+ field public static final int MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET = 3; // 0x3
+ field public static final int MATCH_TYPE_NETWORK_COUNTRY_ONLY = 2; // 0x2
+ field public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5; // 0x5
+ field public static final int QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS = 3; // 0x3
+ field public static final int QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET = 2; // 0x2
+ field public static final int QUALITY_NA = 0; // 0x0
+ field public static final int QUALITY_SINGLE_ZONE = 1; // 0x1
+ }
+
+ public static final class PhoneTimeZoneSuggestion.Builder {
+ ctor public PhoneTimeZoneSuggestion.Builder(int);
+ method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder addDebugInfo(@NonNull String);
+ method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion build();
+ method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setMatchType(int);
+ method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setQuality(int);
+ method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setZoneId(@Nullable String);
+ }
+
+ public class TimeZoneDetector {
+ method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTimeZone(@NonNull android.app.timezonedetector.PhoneTimeZoneSuggestion);
+ }
+
+}
+
+package android.os {
+
+ public final class TimestampedValue<T> implements android.os.Parcelable {
+ ctor public TimestampedValue(long, @Nullable T);
+ method public int describeContents();
+ method public long getReferenceTimeMillis();
+ method @Nullable public T getValue();
+ method public static long referenceTimeDifference(@NonNull android.os.TimestampedValue<?>, @NonNull android.os.TimestampedValue<?>);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.TimestampedValue<?>> CREATOR;
+ }
+
+}
+
+package android.timezone {
+
+ public final class CountryTimeZones {
+ method @Nullable public android.icu.util.TimeZone getDefaultTimeZone();
+ method @Nullable public String getDefaultTimeZoneId();
+ method @NonNull public java.util.List<android.timezone.CountryTimeZones.TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long);
+ method public boolean hasUtcZone(long);
+ method public boolean isDefaultTimeZoneBoosted();
+ method public boolean isForCountryCode(@NonNull String);
+ method @Nullable public android.timezone.CountryTimeZones.OffsetResult lookupByOffsetWithBias(int, @Nullable Boolean, @Nullable Integer, long, @Nullable android.icu.util.TimeZone);
+ }
+
+ public static final class CountryTimeZones.OffsetResult {
+ ctor public CountryTimeZones.OffsetResult(@NonNull android.icu.util.TimeZone, boolean);
+ method @NonNull public android.icu.util.TimeZone getTimeZone();
+ method public boolean isOnlyMatch();
+ }
+
+ public static final class CountryTimeZones.TimeZoneMapping {
+ method @Nullable public android.icu.util.TimeZone getTimeZone();
+ method @NonNull public String getTimeZoneId();
+ }
+
+ public class TelephonyLookup {
+ method @NonNull public static android.timezone.TelephonyLookup getInstance();
+ method @Nullable public android.timezone.TelephonyNetworkFinder getTelephonyNetworkFinder();
+ }
+
+ public class TelephonyNetwork {
+ method @NonNull public String getCountryIsoCode();
+ method @NonNull public String getMcc();
+ method @NonNull public String getMnc();
+ }
+
+ public class TelephonyNetworkFinder {
+ method @Nullable public android.timezone.TelephonyNetwork findNetworkByMccMnc(@NonNull String, @NonNull String);
+ }
+
+ public final class TimeZoneFinder {
+ method @NonNull public static android.timezone.TimeZoneFinder getInstance();
+ method @Nullable public android.timezone.CountryTimeZones lookupCountryTimeZones(@NonNull String);
+ }
+
+}
+
diff --git a/api/module-lib-removed.txt b/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/system-current.txt b/api/system-current.txt
index 75e8f46..d02bbfb 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -190,6 +190,7 @@
field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
+ field public static final String SECURE_ELEMENT_PRIVILEGED = "android.permission.SECURE_ELEMENT_PRIVILEGED";
field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
@@ -207,9 +208,11 @@
field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
+ field public static final String SUGGEST_PHONE_TIME_AND_ZONE = "android.permission.SUGGEST_PHONE_TIME_AND_ZONE";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
+ field public static final String TUNER_RESOURCE_ACCESS = "android.permission.TUNER_RESOURCE_ACCESS";
field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
@@ -241,7 +244,6 @@
field public static final int isVrOnly = 16844152; // 0x1010578
field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
- field public static final int resourcesMap = 16844297; // 0x1010609
field public static final int supportsAmbientMode = 16844173; // 0x101058d
field public static final int userRestriction = 16844164; // 0x1010584
}
@@ -283,6 +285,7 @@
field public static final int config_helpIntentNameKey = 17039390; // 0x104001e
field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
+ field public static final int config_systemGallery = 17039402; // 0x104002a
}
public static final class R.style {
@@ -325,6 +328,7 @@
method public void setDeviceLocales(@NonNull android.os.LocaleList);
method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public static void setPersistentVrThread(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean switchUser(@NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
}
public static interface ActivityManager.OnUidImportanceListener {
@@ -787,6 +791,7 @@
package android.app.admin {
public class DevicePolicyManager {
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwnerNameOnAnyUser();
@@ -802,7 +807,6 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk();
- method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isOrganizationOwnedDeviceWithManagedProfile();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk();
method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long);
method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
@@ -813,7 +817,9 @@
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
+ field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+ field public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
@@ -1031,6 +1037,7 @@
field public static final int AGENT_ERROR = -1003; // 0xfffffc15
field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14
field public static final String EXTRA_TRANSPORT_REGISTRATION = "android.app.backup.extra.TRANSPORT_REGISTRATION";
+ field public static final int FLAG_DATA_NOT_CHANGED = 8; // 0x8
field public static final int FLAG_INCREMENTAL = 2; // 0x2
field public static final int FLAG_NON_INCREMENTAL = 4; // 0x4
field public static final int FLAG_USER_INITIATED = 1; // 0x1
@@ -1330,6 +1337,10 @@
field public static final String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";
}
+ public class NetworkStatsManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.net.netstats.provider.NetworkStatsProviderCallback registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.AbstractNetworkStatsProvider);
+ }
+
public static final class UsageEvents.Event {
method public int getInstanceId();
method @Nullable public String getNotificationChannelId();
@@ -1536,6 +1547,10 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
}
+ public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ }
+
public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile {
method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
@@ -1544,12 +1559,20 @@
field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
}
+ public final class BluetoothMap implements android.bluetooth.BluetoothProfile {
+ method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+ field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
+ }
+
public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
method protected void finalize();
method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
method public boolean isTetheringOn();
method public void setBluetoothTethering(boolean);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
field public static final int LOCAL_NAP_ROLE = 1; // 0x1
@@ -1672,7 +1695,6 @@
}
public abstract class Context {
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
method public abstract android.content.Context createCredentialProtectedStorageContext();
method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -1682,7 +1704,7 @@
method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
field public static final String APP_PREDICTION_SERVICE = "app_prediction";
field public static final String BACKUP_SERVICE = "backup";
@@ -1695,6 +1717,7 @@
field public static final String NETD_SERVICE = "netd";
field public static final String NETWORK_POLICY_SERVICE = "netpolicy";
field public static final String NETWORK_SCORE_SERVICE = "network_score";
+ field public static final String NETWORK_STACK_SERVICE = "network_stack";
field public static final String OEM_LOCK_SERVICE = "oem_lock";
field public static final String PERMISSION_SERVICE = "permission";
field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
@@ -1781,6 +1804,7 @@
field @Deprecated public static final String EXTRA_SIM_STATE = "ss";
field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
+ field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000
field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
field @Deprecated public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
field @Deprecated public static final String SIM_LOCKED_NETWORK = "NETWORK";
@@ -2396,7 +2420,7 @@
package android.hardware.camera2 {
public abstract class CameraDevice implements java.lang.AutoCloseable {
- method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
@@ -3546,7 +3570,6 @@
}
public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
- method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR;
field public final int end;
@@ -3556,7 +3579,10 @@
public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+ field public static final int CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR;
+ field public final int audioCapabilities;
field @NonNull public final String description;
field public final int id;
field @NonNull public final String implementor;
@@ -3567,6 +3593,7 @@
field public final int powerConsumptionMw;
field public final int recognitionModes;
field public final boolean returnsTriggerInEvent;
+ field @NonNull public final String supportedModelArch;
field public final boolean supportsCaptureTransition;
field public final boolean supportsConcurrentCapture;
field @NonNull public final java.util.UUID uuid;
@@ -4052,6 +4079,10 @@
field public static final int ROLE_OUTPUT = 2; // 0x2
}
+ public final class AudioDeviceInfo {
+ field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19
+ }
+
public final class AudioFocusInfo implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.media.AudioAttributes getAttributes();
@@ -4079,6 +4110,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAddress> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAddress getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
@@ -4346,6 +4378,8 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean stopRecognition();
field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
}
public abstract static class SoundTriggerDetector.Callback {
@@ -4368,17 +4402,19 @@
method public int getDetectionServiceOperationsTimeout();
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
- method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+ method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int);
- method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+ method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
}
public static class SoundTriggerManager.Model {
- method public static android.media.soundtrigger.SoundTriggerManager.Model create(java.util.UUID, java.util.UUID, byte[]);
- method public byte[] getModelData();
- method public java.util.UUID getModelUuid();
- method public java.util.UUID getVendorUuid();
+ method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int);
+ method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]);
+ method @Nullable public byte[] getModelData();
+ method @NonNull public java.util.UUID getModelUuid();
+ method @NonNull public java.util.UUID getVendorUuid();
+ method public int getVersion();
}
}
@@ -4508,6 +4544,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void addBlockedRating(@NonNull android.media.tv.TvContentRating);
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig);
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String);
+ method @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) public int getClientPid(@NonNull String);
method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public java.util.List<android.media.tv.TvInputHardwareInfo> getHardwareList();
method @RequiresPermission(android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS) public java.util.List<android.media.tv.TvContentRatingSystemInfo> getTvContentRatingSystemList();
@@ -4519,6 +4556,7 @@
method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public void releaseTvInputHardware(int, android.media.tv.TvInputManager.Hardware);
method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void removeBlockedRating(@NonNull android.media.tv.TvContentRating);
method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void setParentalControlsEnabled(boolean);
+ field public static final int UNKNOWN_CLIENT_PID = -1; // 0xffffffff
}
public static final class TvInputManager.Hardware {
@@ -4595,6 +4633,28 @@
method public abstract int getType();
}
+ public class Lnb implements java.lang.AutoCloseable {
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int sendDiseqcMessage(@NonNull byte[]);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setSatellitePosition(int);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setTone(int);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setVoltage(int);
+ field public static final int POSITION_A = 1; // 0x1
+ field public static final int POSITION_B = 2; // 0x2
+ field public static final int POSITION_UNDEFINED = 0; // 0x0
+ field public static final int TONE_CONTINUOUS = 1; // 0x1
+ field public static final int TONE_NONE = 0; // 0x0
+ field public static final int VOLTAGE_11V = 2; // 0x2
+ field public static final int VOLTAGE_12V = 3; // 0x3
+ field public static final int VOLTAGE_13V = 4; // 0x4
+ field public static final int VOLTAGE_14V = 5; // 0x5
+ field public static final int VOLTAGE_15V = 6; // 0x6
+ field public static final int VOLTAGE_18V = 7; // 0x7
+ field public static final int VOLTAGE_19V = 8; // 0x8
+ field public static final int VOLTAGE_5V = 1; // 0x1
+ field public static final int VOLTAGE_NONE = 0; // 0x0
+ }
+
public final class Tuner implements java.lang.AutoCloseable {
ctor public Tuner(@NonNull android.content.Context);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Tuner.Descrambler openDescrambler();
@@ -4617,16 +4677,43 @@
field public static final int FILTER_STATUS_HIGH_WATER = 4; // 0x4
field public static final int FILTER_STATUS_LOW_WATER = 2; // 0x2
field public static final int FILTER_STATUS_OVERFLOW = 8; // 0x8
+ field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+ field public static final int RESULT_INVALID_STATE = 3; // 0x3
+ field public static final int RESULT_NOT_INITIALIZED = 2; // 0x2
+ field public static final int RESULT_OUT_OF_MEMORY = 5; // 0x5
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ field public static final int RESULT_UNAVAILABLE = 1; // 0x1
+ field public static final int RESULT_UNKNOWN_ERROR = 6; // 0x6
}
}
package android.media.tv.tuner.filter {
+ public abstract class FilterConfiguration {
+ field public static final int FILTER_TYPE_ALP = 16; // 0x10
+ field public static final int FILTER_TYPE_IP = 4; // 0x4
+ field public static final int FILTER_TYPE_MMTP = 2; // 0x2
+ field public static final int FILTER_TYPE_TLV = 8; // 0x8
+ field public static final int FILTER_TYPE_TS = 1; // 0x1
+ }
+
public abstract class FilterEvent {
ctor public FilterEvent();
}
+ public class PesSettings extends android.media.tv.tuner.filter.Settings {
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.PesSettings.Builder builder(@NonNull android.content.Context, int);
+ method public int getStreamId();
+ method public boolean isRaw();
+ }
+
+ public static class PesSettings.Builder {
+ method @NonNull public android.media.tv.tuner.filter.PesSettings build();
+ method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setRaw(boolean);
+ method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setStreamId(int);
+ }
+
public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
method public int getDataLength();
method public int getSectionNumber();
@@ -4634,6 +4721,22 @@
method public int getVersion();
}
+ public abstract class Settings {
+ }
+
+ public class TsFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration {
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.TsFilterConfiguration.Builder builder(@NonNull android.content.Context);
+ method @Nullable public android.media.tv.tuner.filter.Settings getSettings();
+ method public int getTpid();
+ method public int getType();
+ }
+
+ public static class TsFilterConfiguration.Builder {
+ method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration build();
+ method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setSettings(@NonNull android.media.tv.tuner.filter.Settings);
+ method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setTpid(int);
+ }
+
}
package android.metrics {
@@ -4684,7 +4787,9 @@
public class CaptivePortal implements android.os.Parcelable {
method public void logEvent(int, @NonNull String);
+ method public void reevaluateNetwork();
method public void useNetwork();
+ field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
field public static final int APP_RETURN_DISMISSED = 0; // 0x0
field public static final int APP_RETURN_UNWANTED = 1; // 0x1
field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
@@ -4698,6 +4803,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+ method @Deprecated public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int, int, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
@@ -4714,6 +4820,8 @@
field public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd
field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+ field public static final int TYPE_NONE = -1; // 0xffffffff
+ field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
}
public abstract static class ConnectivityManager.OnStartTetheringCallback {
@@ -4849,12 +4957,57 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR;
}
+ public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
+ ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException;
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR;
+ }
+
public class Network implements android.os.Parcelable {
ctor public Network(@NonNull android.net.Network);
method @NonNull public android.net.Network getPrivateDnsBypassingCopy();
field public final int netId;
}
+ public abstract class NetworkAgent {
+ method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
+ method public void onAutomaticReconnectDisabled();
+ method public void onBandwidthUpdateRequested();
+ method public void onNetworkUnwanted();
+ method public void onRemoveKeepalivePacketFilter(int);
+ method public void onSaveAcceptUnvalidated(boolean);
+ method public void onSignalStrengthThresholdsUpdated(@NonNull int[]);
+ method public void onStartSocketKeepalive(int, int, @NonNull android.net.KeepalivePacketData);
+ method public void onStopSocketKeepalive(int);
+ method public void onValidationStatus(int, @Nullable String);
+ method public void sendLinkProperties(@NonNull android.net.LinkProperties);
+ method public void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
+ method public void sendNetworkScore(int);
+ method public void sendSocketKeepaliveEvent(int, int);
+ field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
+ field public static final int VALIDATION_STATUS_VALID = 1; // 0x1
+ field @NonNull public final android.net.Network network;
+ field public final int providerId;
+ }
+
+ public final class NetworkAgentConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getSubscriberId();
+ method public boolean isNat64DetectionEnabled();
+ method public boolean isProvisioningNotificationEnabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR;
+ }
+
+ public static class NetworkAgentConfig.Builder {
+ ctor public NetworkAgentConfig.Builder();
+ method @NonNull public android.net.NetworkAgentConfig build();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification();
+ method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
+ }
+
public final class NetworkCapabilities implements android.os.Parcelable {
method public boolean deduceRestrictedCapability();
method @NonNull public int[] getTransportTypes();
@@ -4893,6 +5046,10 @@
method public abstract void onRequestScores(android.net.NetworkKey[]);
}
+ public class NetworkRequest implements android.os.Parcelable {
+ method public boolean satisfiedBy(@Nullable android.net.NetworkCapabilities);
+ }
+
public static class NetworkRequest.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
}
@@ -4913,6 +5070,9 @@
field @Deprecated public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore";
field public static final String EXTRA_NEW_SCORER = "newScorer";
field @Deprecated public static final String EXTRA_PACKAGE_NAME = "packageName";
+ field public static final int SCORE_FILTER_CURRENT_NETWORK = 1; // 0x1
+ field public static final int SCORE_FILTER_NONE = 0; // 0x0
+ field public static final int SCORE_FILTER_SCAN_RESULTS = 2; // 0x2
}
public static interface NetworkScoreManager.NetworkScoreCallback {
@@ -4996,6 +5156,10 @@
field public final android.net.RssiCurve rssiCurve;
}
+ public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ field public static final int SUCCESS = 0; // 0x0
+ }
+
public final class StaticIpConfiguration implements android.os.Parcelable {
ctor public StaticIpConfiguration();
ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
@@ -5029,6 +5193,10 @@
field @NonNull public final String specifier;
}
+ public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
+ }
+
public class TrafficStats {
method public static void setThreadStatsTagApp();
method public static void setThreadStatsTagBackup();
@@ -5790,6 +5958,7 @@
}
public class ScanResult implements android.os.Parcelable {
+ ctor public ScanResult();
field public static final int CIPHER_CCMP = 3; // 0x3
field public static final int CIPHER_GCMP_256 = 4; // 0x4
field public static final int CIPHER_NONE = 0; // 0x0
@@ -5831,7 +6000,9 @@
public final class SoftApConfiguration implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public java.util.List<android.net.MacAddress> getAllowedClientList();
method public int getBand();
+ method @NonNull public java.util.List<android.net.MacAddress> getBlockedClientList();
method @Nullable public android.net.MacAddress getBssid();
method public int getChannel();
method public int getMaxNumberOfClients();
@@ -5839,7 +6010,7 @@
method public int getSecurityType();
method public int getShutdownTimeoutMillis();
method @Nullable public String getSsid();
- method @Nullable public String getWpa2Passphrase();
+ method public boolean isClientControlByUserEnabled();
method public boolean isHiddenSsid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int BAND_2GHZ = 1; // 0x1
@@ -5857,15 +6028,16 @@
ctor public SoftApConfiguration.Builder();
ctor public SoftApConfiguration.Builder(@NonNull android.net.wifi.SoftApConfiguration);
method @NonNull public android.net.wifi.SoftApConfiguration build();
+ method @NonNull public android.net.wifi.SoftApConfiguration.Builder enableClientControlByUser(boolean);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBand(int);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBssid(@Nullable android.net.MacAddress);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int);
+ method @NonNull public android.net.wifi.SoftApConfiguration.Builder setClientList(@NonNull java.util.List<android.net.MacAddress>, @NonNull java.util.List<android.net.MacAddress>);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setShutdownTimeoutMillis(int);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String);
- method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String);
}
public final class SoftApInfo implements android.os.Parcelable {
@@ -5903,6 +6075,7 @@
method @Deprecated public static boolean isMetered(@Nullable android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiInfo);
method @Deprecated public boolean isNoInternetAccessExpected();
method @Deprecated public void setIpConfiguration(@Nullable android.net.IpConfiguration);
+ method @Deprecated public void setNetworkSelectionStatus(@NonNull android.net.wifi.WifiConfiguration.NetworkSelectionStatus);
method @Deprecated public void setProxy(@NonNull android.net.IpConfiguration.ProxySettings, @NonNull android.net.ProxyInfo);
field @Deprecated public static final int AP_BAND_2GHZ = 0; // 0x0
field @Deprecated public static final int AP_BAND_5GHZ = 1; // 0x1
@@ -5947,6 +6120,7 @@
method @Deprecated public boolean getHasEverConnected();
method @Deprecated @Nullable public static String getNetworkDisableReasonString(int);
method @Deprecated public int getNetworkSelectionDisableReason();
+ method @Deprecated public int getNetworkSelectionStatus();
method @Deprecated @NonNull public String getNetworkStatusString();
method @Deprecated public boolean isNetworkEnabled();
method @Deprecated public boolean isNetworkPermanentlyDisabled();
@@ -5961,6 +6135,16 @@
field @Deprecated public static final int DISABLED_NO_INTERNET_TEMPORARY = 4; // 0x4
field @Deprecated public static final int NETWORK_SELECTION_DISABLED_MAX = 10; // 0xa
field @Deprecated public static final int NETWORK_SELECTION_ENABLE = 0; // 0x0
+ field @Deprecated public static final int NETWORK_SELECTION_ENABLED = 0; // 0x0
+ field @Deprecated public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; // 0x2
+ field @Deprecated public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; // 0x1
+ }
+
+ @Deprecated public static final class WifiConfiguration.NetworkSelectionStatus.Builder {
+ ctor @Deprecated public WifiConfiguration.NetworkSelectionStatus.Builder();
+ method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus build();
+ method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionDisableReason(int);
+ method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionStatus(int);
}
@Deprecated public static class WifiConfiguration.RecentFailure {
@@ -6005,6 +6189,15 @@
field public static final int INVALID_RSSI = -127; // 0xffffff81
}
+ public static final class WifiInfo.Builder {
+ ctor public WifiInfo.Builder();
+ method @NonNull public android.net.wifi.WifiInfo build();
+ method @NonNull public android.net.wifi.WifiInfo.Builder setBssid(@NonNull String);
+ method @NonNull public android.net.wifi.WifiInfo.Builder setNetworkId(int);
+ method @NonNull public android.net.wifi.WifiInfo.Builder setRssi(int);
+ method @NonNull public android.net.wifi.WifiInfo.Builder setSsid(@NonNull byte[]);
+ }
+
public class WifiManager {
method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoin(int, boolean);
@@ -6028,6 +6221,7 @@
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState();
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(@NonNull java.util.List<android.net.wifi.ScanResult>);
method public boolean isApMacRandomizationSupported();
method public boolean isConnectedMacRandomizationSupported();
method @Deprecated public boolean isDeviceToDeviceRttSupported();
@@ -6098,6 +6292,8 @@
field public static final int IFACE_IP_MODE_UNSPECIFIED = -1; // 0xffffffff
field public static final int PASSPOINT_HOME_NETWORK = 0; // 0x0
field public static final int PASSPOINT_ROAMING_NETWORK = 1; // 0x1
+ field public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0; // 0x0
+ field public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1; // 0x1
field public static final int SAP_START_FAILURE_GENERAL = 0; // 0x0
field public static final int SAP_START_FAILURE_NO_CHANNEL = 1; // 0x1
field public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; // 0x2
@@ -6139,6 +6335,7 @@
}
public static interface WifiManager.SoftApCallback {
+ method public default void onBlockedClientConnecting(@NonNull android.net.wifi.WifiClient, int);
method public default void onCapabilityChanged(@NonNull android.net.wifi.SoftApCapability);
method public default void onConnectedClientsChanged(@NonNull java.util.List<android.net.wifi.WifiClient>);
method public default void onInfoChanged(@NonNull android.net.wifi.SoftApInfo);
@@ -6172,6 +6369,25 @@
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
}
+ public final class WifiOemConfigStoreMigrationHook {
+ method @Nullable public static android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData load();
+ }
+
+ public static final class WifiOemConfigStoreMigrationHook.MigrationData implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public java.util.List<android.net.wifi.WifiConfiguration> getUserSavedNetworkConfigurations();
+ method @Nullable public android.net.wifi.SoftApConfiguration getUserSoftApConfiguration();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData> CREATOR;
+ }
+
+ public static final class WifiOemConfigStoreMigrationHook.MigrationData.Builder {
+ ctor public WifiOemConfigStoreMigrationHook.MigrationData.Builder();
+ method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData build();
+ method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>);
+ method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
+ }
+
public class WifiScanner {
method @Deprecated public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.BssidInfo[]);
method @Deprecated public void configureWifiChange(android.net.wifi.WifiScanner.WifiChangeSettings);
@@ -7274,7 +7490,6 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isGuestUser();
- method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isPrimaryUser();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile();
@@ -7881,6 +8096,7 @@
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, @Nullable String, boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
+ field public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
field public static final String APP_STANDBY_ENABLED = "app_standby_enabled";
field public static final String AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "autofill_compat_mode_allowed_packages";
field public static final String CARRIER_APP_NAMES = "carrier_app_names";
@@ -7893,13 +8109,21 @@
field public static final String EUICC_UNSUPPORTED_COUNTRIES = "euicc_unsupported_countries";
field public static final String INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT = "install_carrier_app_notification_persistent";
field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis";
+ field public static final String NETWORK_RECOMMENDATIONS_ENABLED = "network_recommendations_enabled";
field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
+ field public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
field public static final String TETHER_SUPPORTED = "tether_supported";
field public static final String THEATER_MODE_ON = "theater_mode_on";
field public static final String WEBVIEW_MULTIPROCESS = "webview_multiprocess";
field public static final String WIFI_BADGING_THRESHOLDS = "wifi_badging_thresholds";
+ field public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
+ field public static final String WIFI_P2P_PENDING_FACTORY_RESET = "wifi_p2p_pending_factory_reset";
+ field public static final String WIFI_SCAN_ALWAYS_AVAILABLE = "wifi_scan_always_enabled";
+ field public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
+ field public static final String WIFI_SCORE_PARAMS = "wifi_score_params";
+ field public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled";
field public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
}
@@ -8028,7 +8252,6 @@
field @NonNull public static final String ENABLE_CMAS_PRESIDENTIAL_PREF = "enable_cmas_presidential_alerts";
field @NonNull public static final String ENABLE_CMAS_SEVERE_THREAT_PREF = "enable_cmas_severe_threat_alerts";
field @NonNull public static final String ENABLE_EMERGENCY_PERF = "enable_emergency_alerts";
- field @NonNull public static final String ENABLE_FULL_VOLUME_PREF = "use_full_volume";
field @NonNull public static final String ENABLE_PUBLIC_SAFETY_PREF = "enable_public_safety_messages";
field @NonNull public static final String ENABLE_STATE_LOCAL_TEST_PREF = "enable_state_local_test_alerts";
field @NonNull public static final String ENABLE_TEST_ALERT_PREF = "enable_test_alerts";
@@ -8663,7 +8886,10 @@
method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
+ method public void onNotificationVisibilityChanged(@NonNull String, boolean);
method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>);
+ method public void onPanelHidden();
+ method public void onPanelRevealed(int);
method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
method public final void unsnoozeNotification(@NonNull String);
field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
@@ -9249,6 +9475,11 @@
field public static final int TRANSPORT_TYPE_WWAN = 1; // 0x1
}
+ public final class BarringInfo implements android.os.Parcelable {
+ ctor public BarringInfo();
+ method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy();
+ }
+
public final class CallAttributes implements android.os.Parcelable {
ctor public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
method public int describeContents();
@@ -9261,6 +9492,7 @@
public final class CallQuality implements android.os.Parcelable {
ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int);
+ ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean);
method public int describeContents();
method public int getAverageRelativeJitter();
method public int getAverageRoundTripTime();
@@ -9273,6 +9505,9 @@
method public int getNumRtpPacketsTransmitted();
method public int getNumRtpPacketsTransmittedLost();
method public int getUplinkCallQualityLevel();
+ method public boolean isIncomingSilenceDetected();
+ method public boolean isOutgoingSilenceDetected();
+ method public boolean isRtpInactivityDetected();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CALL_QUALITY_BAD = 4; // 0x4
field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0
@@ -10126,7 +10361,9 @@
public class ServiceState implements android.os.Parcelable {
method @NonNull public android.telephony.ServiceState createLocationInfoSanitizedCopy(boolean);
method public void fillInNotifierBundle(@NonNull android.os.Bundle);
+ method public int getDataNetworkType();
method public int getDataRegistrationState();
+ method public boolean getDataRoamingFromRegistration();
method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoList();
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int);
@@ -10272,9 +10509,20 @@
method public boolean disableCellBroadcastRange(int, int, int);
method public boolean enableCellBroadcastRange(int, int, int);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public java.util.List<android.telephony.SmsMessage> getMessagesFromIcc();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc();
method public void sendMultipartTextMessage(@NonNull String, @NonNull String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int);
+ field public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; // 0x3
+ field public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1; // 0x1
+ field public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2; // 0x2
+ field public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0; // 0x0
+ }
+
+ public class SmsMessage {
+ method @Nullable public static android.telephony.SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[], boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static byte[] getSubmitPduEncodedMessage(boolean, @NonNull String, @NonNull String, int, int, int, int, int, int);
}
public class SubscriptionInfo implements android.os.Parcelable {
@@ -10372,6 +10620,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(int);
method public String getCdmaPrlVersion();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaRoamingMode();
method public int getCurrentPhoneType();
method public int getCurrentPhoneType(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getDataActivationState();
@@ -10384,6 +10633,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
method public int getEmergencyNumberDbVersion();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
method public int getMaxNumberOfSimultaneouslyActiveSims();
@@ -10412,6 +10662,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
method public boolean isCurrentSimOperator(@NonNull String, int, @Nullable String);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataConnectionEnabled();
method public boolean isDataConnectivityPossible();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
@@ -10443,6 +10694,8 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAlwaysAllowMmsData(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaRoamingMode(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaSubscriptionMode(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
@@ -10467,12 +10720,16 @@
method public void updateServiceLocation();
method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String);
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_ANOMALY_REPORTED = "android.telephony.action.ANOMALY_REPORTED";
+ field public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
+ field public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
+ field public static final String ACTION_CARRIER_SIGNAL_REDIRECTED = "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
+ field public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED = "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
+ field public static final String ACTION_CARRIER_SIGNAL_RESET = "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
field public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED = "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED";
field public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED = "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED";
field public static final String ACTION_EMERGENCY_ASSISTANCE = "android.telephony.action.EMERGENCY_ASSISTANCE";
field public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
field public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
- field public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
field public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = "com.android.omadm.service.CONFIGURATION_UPDATE";
field public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
@@ -10482,8 +10739,23 @@
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final int CDMA_SUBSCRIPTION_NV = 1; // 0x1
+ field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0
+ field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff
+ field public static final int DEFAULT_PREFERRED_NETWORK_MODE = 0; // 0x0
field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
+ field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto";
+ field public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt";
+ field @Deprecated public static final String EXTRA_APN_TYPE = "apnType";
+ field public static final String EXTRA_APN_TYPE_INT = "apnTypeInt";
+ field public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable";
+ field public static final String EXTRA_ERROR_CODE = "errorCode";
+ field public static final String EXTRA_PCO_ID = "pcoId";
+ field public static final String EXTRA_PCO_VALUE = "pcoValue";
+ field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
+ field public static final String EXTRA_PHONE_IN_EMERGENCY_CALL = "android.telephony.extra.PHONE_IN_EMERGENCY_CALL";
+ field public static final String EXTRA_REDIRECTION_URL = "redirectionUrl";
field public static final String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE";
field public static final String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL";
field public static final String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING";
@@ -10510,6 +10782,7 @@
field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L
+ field public static final int PHONE_TYPE_THIRD_PARTY = 4; // 0x4
field public static final int RADIO_POWER_OFF = 0; // 0x0
field public static final int RADIO_POWER_ON = 1; // 0x1
field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
@@ -10533,6 +10806,7 @@
public class TelephonyRegistryManager {
method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
method public void addOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
+ method public void notifyBarringInfoChanged(int, int, @NonNull android.telephony.BarringInfo);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
method public void notifyCarrierNetworkChange(boolean);
method public void notifyRegistrationFailed(int, int, @NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
@@ -11088,6 +11362,7 @@
public class ImsManager {
method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+ field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
}
public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
@@ -11382,13 +11657,30 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+ field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
+ field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
+ field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
+ field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
+ field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
+ field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
+ field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
+ field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
+ field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10
+ field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
+ field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+ field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
+ field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
+ field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
@@ -11401,6 +11693,47 @@
method public void onProvisioningStringChanged(int, @NonNull String);
}
+ public final class RcsContactUceCapability implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getCapableExtensionTags();
+ method @NonNull public android.net.Uri getContactUri();
+ method @Nullable public android.net.Uri getServiceUri(int);
+ method public boolean isCapable(int);
+ method public boolean isCapable(@NonNull String);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CAPABILITY_CHAT_SESSION = 2; // 0x2
+ field public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = 4; // 0x4
+ field public static final int CAPABILITY_CHAT_STANDALONE = 1; // 0x1
+ field public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = 4096; // 0x1000
+ field public static final int CAPABILITY_FILE_TRANSFER = 8; // 0x8
+ field public static final int CAPABILITY_FILE_TRANSFER_HTTP = 64; // 0x40
+ field public static final int CAPABILITY_FILE_TRANSFER_SMS = 128; // 0x80
+ field public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = 32; // 0x20
+ field public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = 16; // 0x10
+ field public static final int CAPABILITY_GEOLOCATION_PULL = 131072; // 0x20000
+ field public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = 262144; // 0x40000
+ field public static final int CAPABILITY_GEOLOCATION_PUSH = 32768; // 0x8000
+ field public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = 65536; // 0x10000
+ field public static final int CAPABILITY_IMAGE_SHARE = 256; // 0x100
+ field public static final int CAPABILITY_IP_VIDEO_CALL = 16384; // 0x4000
+ field public static final int CAPABILITY_IP_VOICE_CALL = 8192; // 0x2000
+ field public static final int CAPABILITY_RCS_VIDEO_CALL = 1048576; // 0x100000
+ field public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = 2097152; // 0x200000
+ field public static final int CAPABILITY_RCS_VOICE_CALL = 524288; // 0x80000
+ field public static final int CAPABILITY_SOCIAL_PRESENCE = 2048; // 0x800
+ field public static final int CAPABILITY_VIDEO_SHARE = 1024; // 0x400
+ field public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = 512; // 0x200
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR;
+ }
+
+ public static class RcsContactUceCapability.Builder {
+ ctor public RcsContactUceCapability.Builder(@NonNull android.net.Uri);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int, @NonNull android.net.Uri);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(@NonNull String);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability build();
+ }
+
public interface RegistrationManager {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -11590,6 +11923,7 @@
method public String getConfigString(int);
method public final void notifyProvisionedValueChanged(int, int);
method public final void notifyProvisionedValueChanged(int, String);
+ method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method public int setConfig(int, int);
method public int setConfig(int, String);
field public static final int CONFIG_RESULT_FAILED = 1; // 0x1
@@ -11805,7 +12139,28 @@
method public int getUid();
}
+ public final class StatsEvent {
+ method @NonNull public static android.util.StatsEvent.Builder newBuilder();
+ }
+
+ public static final class StatsEvent.Builder {
+ method @NonNull public android.util.StatsEvent.Builder addBooleanAnnotation(byte, boolean);
+ method @NonNull public android.util.StatsEvent.Builder addIntAnnotation(byte, int);
+ method @NonNull public android.util.StatsEvent build();
+ method @NonNull public android.util.StatsEvent.Builder setAtomId(int);
+ method @NonNull public android.util.StatsEvent.Builder usePooledBuffer();
+ method @NonNull public android.util.StatsEvent.Builder writeAttributionChain(@NonNull int[], @NonNull String[]);
+ method @NonNull public android.util.StatsEvent.Builder writeBoolean(boolean);
+ method @NonNull public android.util.StatsEvent.Builder writeByteArray(@NonNull byte[]);
+ method @NonNull public android.util.StatsEvent.Builder writeFloat(float);
+ method @NonNull public android.util.StatsEvent.Builder writeInt(int);
+ method @NonNull public android.util.StatsEvent.Builder writeKeyValuePairs(@Nullable android.util.SparseIntArray, @Nullable android.util.SparseLongArray, @Nullable android.util.SparseArray<java.lang.String>, @Nullable android.util.SparseArray<java.lang.Float>);
+ method @NonNull public android.util.StatsEvent.Builder writeLong(long);
+ method @NonNull public android.util.StatsEvent.Builder writeString(@NonNull String);
+ }
+
public final class StatsLog {
+ method public static void write(@NonNull android.util.StatsEvent);
method public static void writeRaw(@NonNull byte[], int);
}
@@ -11967,6 +12322,12 @@
method public void onJsResultComplete(android.webkit.JsResult);
}
+ public interface PacProcessor {
+ method @NonNull public static android.webkit.PacProcessor getInstance();
+ method @Nullable public String makeProxyRequest(@NonNull String);
+ method public boolean setProxyScript(@NonNull String);
+ }
+
public class SslErrorHandler extends android.os.Handler {
ctor public SslErrorHandler();
}
@@ -12101,6 +12462,7 @@
method public android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess);
method public android.webkit.CookieManager getCookieManager();
method public android.webkit.GeolocationPermissions getGeolocationPermissions();
+ method @NonNull public default android.webkit.PacProcessor getPacProcessor();
method public android.webkit.ServiceWorkerController getServiceWorkerController();
method public android.webkit.WebViewFactoryProvider.Statics getStatics();
method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
diff --git a/api/test-current.txt b/api/test-current.txt
index d017dd6..6498e9a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34,6 +34,7 @@
public static final class R.string {
field public static final int config_defaultAssistant = 17039393; // 0x1040021
field public static final int config_defaultDialer = 17039395; // 0x1040023
+ field public static final int config_systemGallery = 17039402; // 0x104002a
}
}
@@ -435,6 +436,8 @@
}
public class StatusBarManager {
+ method public void collapsePanels();
+ method public void expandNotificationsPanel();
method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
@@ -468,7 +471,10 @@
}
public class WallpaperManager {
+ method @Nullable public android.graphics.Bitmap getBitmap();
method @RequiresPermission("android.permission.SET_WALLPAPER_COMPONENT") public boolean setWallpaperComponent(android.content.ComponentName);
+ method public boolean shouldEnableWideColorGamut();
+ method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
}
public class WindowConfiguration implements java.lang.Comparable<android.app.WindowConfiguration> android.os.Parcelable {
@@ -754,6 +760,7 @@
field public static final String BUGREPORT_SERVICE = "bugreport";
field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
+ field public static final String NETWORK_STACK_SERVICE = "network_stack";
field public static final String PERMISSION_SERVICE = "permission";
field public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
field public static final String ROLLBACK_SERVICE = "rollback";
@@ -1018,7 +1025,7 @@
package android.hardware.camera2 {
public abstract class CameraDevice implements java.lang.AutoCloseable {
- method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
@@ -1494,7 +1501,9 @@
public class CaptivePortal implements android.os.Parcelable {
method public void logEvent(int, @NonNull String);
+ method public void reevaluateNetwork();
method public void useNetwork();
+ field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
field public static final int APP_RETURN_DISMISSED = 0; // 0x0
field public static final int APP_RETURN_UNWANTED = 1; // 0x1
field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
@@ -2731,6 +2740,11 @@
method @Nullable public android.util.SparseArray<android.service.autofill.InternalOnClickAction> getActions();
}
+ public static final class Dataset.Builder {
+ ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation);
+ }
+
public final class DateTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
method public void apply(@NonNull android.service.autofill.ValueFinder, @NonNull android.widget.RemoteViews, int) throws java.lang.Exception;
}
@@ -2929,7 +2943,10 @@
method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
+ method public void onNotificationVisibilityChanged(@NonNull String, boolean);
method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>);
+ method public void onPanelHidden();
+ method public void onPanelRevealed(int);
method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
method public final void unsnoozeNotification(@NonNull String);
field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
@@ -3120,8 +3137,18 @@
field public static final int TRANSPORT_TYPE_WWAN = 1; // 0x1
}
+ public final class BarringInfo implements android.os.Parcelable {
+ ctor public BarringInfo();
+ ctor public BarringInfo(@Nullable android.telephony.CellIdentity, @NonNull android.util.SparseArray<android.telephony.BarringInfo.BarringServiceInfo>);
+ }
+
+ public static final class BarringInfo.BarringServiceInfo implements android.os.Parcelable {
+ ctor public BarringInfo.BarringServiceInfo(int, boolean, int, int);
+ }
+
public final class CallQuality implements android.os.Parcelable {
ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int);
+ ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean);
method public int describeContents();
method public int getAverageRelativeJitter();
method public int getAverageRoundTripTime();
@@ -3134,6 +3161,9 @@
method public int getNumRtpPacketsTransmitted();
method public int getNumRtpPacketsTransmittedLost();
method public int getUplinkCallQualityLevel();
+ method public boolean isIncomingSilenceDetected();
+ method public boolean isOutgoingSilenceDetected();
+ method public boolean isRtpInactivityDetected();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CALL_QUALITY_BAD = 4; // 0x4
field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0
@@ -3246,6 +3276,7 @@
public class ServiceState implements android.os.Parcelable {
method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
+ method public int getDataNetworkType();
method public void setCdmaSystemAndNetworkId(int, int);
method public void setCellBandwidths(int[]);
method public void setChannelNumber(int);
@@ -3517,6 +3548,7 @@
public class ImsManager {
method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+ field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
}
public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
@@ -3807,13 +3839,30 @@
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public int getProvisioningIntValue(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public String getProvisioningStringValue(int);
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+ field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
+ field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
+ field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
+ field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
+ field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
+ field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
+ field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
+ field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
+ field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10
+ field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
+ field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+ field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
+ field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
+ field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
@@ -4015,6 +4064,7 @@
method public String getConfigString(int);
method public final void notifyProvisionedValueChanged(int, int);
method public final void notifyProvisionedValueChanged(int, String);
+ method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method public int setConfig(int, int);
method public int setConfig(int, String);
field public static final int CONFIG_RESULT_FAILED = 1; // 0x1
@@ -4244,6 +4294,7 @@
field public static final String FFLAG_OVERRIDE_PREFIX = "sys.fflag.override.";
field public static final String FFLAG_PREFIX = "sys.fflag.";
field public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
+ field public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = "settings_notif_convo_bypass_shortcut_req";
field public static final String PERSIST_PREFIX = "persist.sys.fflag.override.";
field public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
field public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer";
@@ -4343,6 +4394,7 @@
}
public final class Display {
+ method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
method public boolean hasAccess(int);
}
@@ -4460,6 +4512,8 @@
}
public class AccessibilityNodeInfo implements android.os.Parcelable {
+ method public void addChild(@NonNull android.os.IBinder);
+ method public void setLeashedParent(@Nullable android.os.IBinder, int);
method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
method public void writeToParcelNoRecycle(android.os.Parcel, int);
}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 459520a..8fac31a 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1113,7 +1113,7 @@
SurfaceComposerClient::Transaction t;
t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset)
.setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight));
- t.setDisplayProjection(mDisplayToken, 0 /* orientation */, layerStackRect, displayRect);
+ t.setDisplayProjection(mDisplayToken, ui::ROTATION_0, layerStackRect, displayRect);
t.apply();
mTargetInset = mCurrentInset = 0;
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 229628c..4074789 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -33,6 +33,8 @@
namespace {
+#define REWRITE_PACKAGE(resid, package_id) \
+ (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
std::string ConcatPolicies(const std::vector<std::string>& policies) {
@@ -154,6 +156,7 @@
return Error("root element is not <overlay> tag");
}
+ const uint8_t target_package_id = target_package->GetPackageId();
const uint8_t overlay_package_id = overlay_package->GetPackageId();
auto overlay_it_end = root_it.end();
for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
@@ -187,6 +190,9 @@
continue;
}
+ // Retrieve the compile-time resource id of the target resource.
+ target_id = REWRITE_PACKAGE(target_id, target_package_id);
+
if (overlay_resource->dataType == Res_value::TYPE_STRING) {
overlay_resource->data += string_pool_offset;
}
@@ -214,6 +220,7 @@
const AssetManager2* target_am, const AssetManager2* overlay_am,
const LoadedPackage* target_package, const LoadedPackage* overlay_package) {
ResourceMapping resource_mapping;
+ const uint8_t target_package_id = target_package->GetPackageId();
const auto end = overlay_package->end();
for (auto iter = overlay_package->begin(); iter != end; ++iter) {
const ResourceId overlay_resid = *iter;
@@ -225,11 +232,14 @@
// Find the resource with the same type and entry name within the target package.
const std::string full_name =
base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
- const ResourceId target_resource = target_am->GetResourceId(full_name);
+ ResourceId target_resource = target_am->GetResourceId(full_name);
if (target_resource == 0U) {
continue;
}
+ // Retrieve the compile-time resource id of the target resource.
+ target_resource = REWRITE_PACKAGE(target_resource, target_package_id);
+
resource_mapping.AddMapping(target_resource, Res_value::TYPE_REFERENCE, overlay_resid,
/* rewrite_overlay_reference */ false);
}
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index cfd77c2..62312d1 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -123,14 +123,17 @@
// ================================================================================
ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
- const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
- const sp<Throttler>& throttler)
+ const sp<Broadcaster>& broadcaster,
+ const sp<Looper>& handlerLooper,
+ const sp<Throttler>& throttler,
+ const vector<BringYourOwnSection*>& registeredSections)
:mLock(),
mWorkDirectory(workDirectory),
mBroadcaster(broadcaster),
mHandlerLooper(handlerLooper),
mBacklogDelay(DEFAULT_DELAY_NS),
mThrottler(throttler),
+ mRegisteredSections(registeredSections),
mBatch(new ReportBatch()) {
}
@@ -185,7 +188,7 @@
return;
}
- sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);
+ sp<Reporter> reporter = new Reporter(mWorkDirectory, batch, mRegisteredSections);
// Take the report, which might take a while. More requests might queue
// up while we're doing this, and we'll handle them in their next batch.
@@ -237,7 +240,7 @@
mWorkDirectory = new WorkDirectory();
mBroadcaster = new Broadcaster(mWorkDirectory);
mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
- mThrottler);
+ mThrottler, mRegisteredSections);
mBroadcaster->setHandler(mHandler);
}
@@ -327,6 +330,11 @@
incidentArgs.addSection(id);
}
}
+ for (const Section* section : mRegisteredSections) {
+ if (!section_requires_specific_mention(section->id)) {
+ incidentArgs.addSection(section->id);
+ }
+ }
// The ReportRequest takes ownership of the fd, so we need to dup it.
int fd = dup(stream.get());
@@ -339,6 +347,45 @@
return Status::ok();
}
+Status IncidentService::registerSection(const int id, const String16& name16,
+ const sp<IIncidentDumpCallback>& callback) {
+ const char* name = String8(name16).c_str();
+ ALOGI("Register section %d: %s", id, name);
+ if (callback == nullptr) {
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+ }
+ const uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ for (int i = 0; i < mRegisteredSections.size(); i++) {
+ if (mRegisteredSections.at(i)->id == id) {
+ if (mRegisteredSections.at(i)->uid != callingUid) {
+ ALOGW("Error registering section %d: calling uid does not match", id);
+ return Status::fromExceptionCode(Status::EX_SECURITY);
+ }
+ mRegisteredSections.at(i) = new BringYourOwnSection(id, name, callingUid, callback);
+ return Status::ok();
+ }
+ }
+ mRegisteredSections.push_back(new BringYourOwnSection(id, name, callingUid, callback));
+ return Status::ok();
+}
+
+Status IncidentService::unregisterSection(const int id) {
+ ALOGI("Unregister section %d", id);
+ uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) {
+ if ((*it)->id == id) {
+ if ((*it)->uid != callingUid) {
+ ALOGW("Error unregistering section %d: calling uid does not match", id);
+ return Status::fromExceptionCode(Status::EX_SECURITY);
+ }
+ mRegisteredSections.erase(it);
+ return Status::ok();
+ }
+ }
+ ALOGW("Section %d not found", id);
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+}
+
Status IncidentService::systemRunning() {
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index b2c7f23..49fc566 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -40,12 +40,16 @@
using namespace android::binder;
using namespace android::os;
+class BringYourOwnSection;
+
// ================================================================================
class ReportHandler : public MessageHandler {
public:
ReportHandler(const sp<WorkDirectory>& workDirectory,
- const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
- const sp<Throttler>& throttler);
+ const sp<Broadcaster>& broadcaster,
+ const sp<Looper>& handlerLooper,
+ const sp<Throttler>& throttler,
+ const vector<BringYourOwnSection*>& registeredSections);
virtual ~ReportHandler();
virtual void handleMessage(const Message& message);
@@ -79,6 +83,8 @@
nsecs_t mBacklogDelay;
sp<Throttler> mThrottler;
+ const vector<BringYourOwnSection*>& mRegisteredSections;
+
sp<ReportBatch> mBatch;
/**
@@ -126,6 +132,11 @@
virtual Status reportIncidentToDumpstate(unique_fd stream,
const sp<IIncidentReportStatusListener>& listener);
+ virtual Status registerSection(int id, const String16& name,
+ const sp<IIncidentDumpCallback>& callback);
+
+ virtual Status unregisterSection(int id);
+
virtual Status systemRunning();
virtual Status getIncidentReportList(const String16& pkg, const String16& cls,
@@ -149,6 +160,7 @@
sp<Broadcaster> mBroadcaster;
sp<ReportHandler> mHandler;
sp<Throttler> mThrottler;
+ vector<BringYourOwnSection*> mRegisteredSections;
/**
* Commands print out help.
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 02b6bbe..aa40f85 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -364,7 +364,6 @@
mSectionBufferSuccess = false;
mHadError = false;
mSectionErrors.clear();
-
}
void ReportWriter::setSectionStats(const FdBuffer& buffer) {
@@ -470,10 +469,13 @@
// ================================================================================
-Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch)
+Reporter::Reporter(const sp<WorkDirectory>& workDirectory,
+ const sp<ReportBatch>& batch,
+ const vector<BringYourOwnSection*>& registeredSections)
:mWorkDirectory(workDirectory),
mWriter(batch),
- mBatch(batch) {
+ mBatch(batch),
+ mRegisteredSections(registeredSections) {
}
Reporter::~Reporter() {
@@ -580,50 +582,15 @@
// For each of the report fields, see if we need it, and if so, execute the command
// and report to those that care that we're doing it.
for (const Section** section = SECTION_LIST; *section; section++) {
- const int sectionId = (*section)->id;
-
- // If nobody wants this section, skip it.
- if (!mBatch->containsSection(sectionId)) {
- continue;
- }
-
- ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string());
- IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections();
-
- // Notify listener of starting
- mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
- listener->onReportSectionStatus(
- sectionId, IIncidentReportStatusListener::STATUS_STARTING);
- });
-
- // Go get the data and write it into the file descriptors.
- mWriter.startSection(sectionId);
- err = (*section)->Execute(&mWriter);
- mWriter.endSection(sectionMetadata);
-
- // Sections returning errors are fatal. Most errors should not be fatal.
- if (err != NO_ERROR) {
- mWriter.error((*section), err, "Section failed. Stopping report.");
+ if (execute_section(*section, &metadata, reportByteSize) != NO_ERROR) {
goto DONE;
}
+ }
- // The returned max data size is used for throttling too many incident reports.
- (*reportByteSize) += sectionMetadata->report_size_bytes();
-
- // For any requests that failed during this section, remove them now. We do this
- // before calling back about section finished, so listeners do not erroniously get the
- // impression that the section succeeded. But we do it here instead of inside
- // writeSection so that the callback is done from a known context and not from the
- // bowels of a section, where changing the batch could cause odd errors.
- cancel_and_remove_failed_requests();
-
- // Notify listener of finishing
- mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
- listener->onReportSectionStatus(
- sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
- });
-
- ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string());
+ for (const Section* section : mRegisteredSections) {
+ if (execute_section(section, &metadata, reportByteSize) != NO_ERROR) {
+ goto DONE;
+ }
}
DONE:
@@ -681,6 +648,55 @@
ALOGI("Done taking incident report err=%s", strerror(-err));
}
+status_t Reporter::execute_section(const Section* section, IncidentMetadata* metadata,
+ size_t* reportByteSize) {
+ const int sectionId = section->id;
+
+ // If nobody wants this section, skip it.
+ if (!mBatch->containsSection(sectionId)) {
+ return NO_ERROR;
+ }
+
+ ALOGD("Start incident report section %d '%s'", sectionId, section->name.string());
+ IncidentMetadata::SectionStats* sectionMetadata = metadata->add_sections();
+
+ // Notify listener of starting
+ mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+ listener->onReportSectionStatus(
+ sectionId, IIncidentReportStatusListener::STATUS_STARTING);
+ });
+
+ // Go get the data and write it into the file descriptors.
+ mWriter.startSection(sectionId);
+ status_t err = section->Execute(&mWriter);
+ mWriter.endSection(sectionMetadata);
+
+ // Sections returning errors are fatal. Most errors should not be fatal.
+ if (err != NO_ERROR) {
+ mWriter.error(section, err, "Section failed. Stopping report.");
+ return err;
+ }
+
+ // The returned max data size is used for throttling too many incident reports.
+ (*reportByteSize) += sectionMetadata->report_size_bytes();
+
+ // For any requests that failed during this section, remove them now. We do this
+ // before calling back about section finished, so listeners do not erroniously get the
+ // impression that the section succeeded. But we do it here instead of inside
+ // writeSection so that the callback is done from a known context and not from the
+ // bowels of a section, where changing the batch could cause odd errors.
+ cancel_and_remove_failed_requests();
+
+ // Notify listener of finishing
+ mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+ listener->onReportSectionStatus(
+ sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
+ });
+
+ ALOGD("Finish incident report section %d '%s'", sectionId, section->name.string());
+ return NO_ERROR;
+}
+
void Reporter::cancel_and_remove_failed_requests() {
// Handle a failure in the persisted file
if (mPersistedFile != nullptr) {
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index fb3961a..cbc8b13 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -21,6 +21,7 @@
#include "frameworks/base/core/proto/android/os/metadata.pb.h"
#include <android/content/ComponentName.h>
#include <android/os/IIncidentReportStatusListener.h>
+#include <android/os/IIncidentDumpCallback.h>
#include <android/os/IncidentReportArgs.h>
#include <android/util/protobuf.h>
@@ -39,6 +40,7 @@
using namespace android::content;
using namespace android::os;
+class BringYourOwnSection;
class Section;
// ================================================================================
@@ -122,7 +124,7 @@
void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func);
/**
- * Call func(request) for each file descriptor that has
+ * Call func(request) for each file descriptor.
*/
void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func);
@@ -251,7 +253,9 @@
// ================================================================================
class Reporter : public virtual RefBase {
public:
- Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch);
+ Reporter(const sp<WorkDirectory>& workDirectory,
+ const sp<ReportBatch>& batch,
+ const vector<BringYourOwnSection*>& registeredSections);
virtual ~Reporter();
@@ -263,6 +267,10 @@
ReportWriter mWriter;
sp<ReportBatch> mBatch;
sp<ReportFile> mPersistedFile;
+ const vector<BringYourOwnSection*>& mRegisteredSections;
+
+ status_t execute_section(const Section* section, IncidentMetadata* metadata,
+ size_t* reportByteSize);
void cancel_and_remove_failed_requests();
};
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index c9277a5..2229e1c 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -267,7 +267,7 @@
signal(SIGPIPE, sigpipe_handler);
WorkerThreadData* data = (WorkerThreadData*)cookie;
- status_t err = data->section->BlockingCall(data->pipe.writeFd().get());
+ status_t err = data->section->BlockingCall(data->pipe.writeFd());
{
unique_lock<mutex> lock(data->lock);
@@ -458,7 +458,7 @@
DumpsysSection::~DumpsysSection() {}
-status_t DumpsysSection::BlockingCall(int pipeWriteFd) const {
+status_t DumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const {
// checkService won't wait for the service to show up like getService will.
sp<IBinder> service = defaultServiceManager()->checkService(mService);
@@ -467,7 +467,7 @@
return NAME_NOT_FOUND;
}
- service->dump(pipeWriteFd, mArgs);
+ service->dump(pipeWriteFd.get(), mArgs);
return NO_ERROR;
}
@@ -526,7 +526,7 @@
return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
}
-status_t LogSection::BlockingCall(int pipeWriteFd) const {
+status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const {
// Open log buffer and getting logs since last retrieved time if any.
unique_ptr<logger_list, void (*)(logger_list*)> loggers(
gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end()
@@ -643,7 +643,7 @@
}
}
gLastLogsRetrieved[mLogID] = lastTimestamp;
- if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+ if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
return EPIPE;
}
@@ -660,7 +660,7 @@
TombstoneSection::~TombstoneSection() {}
-status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
+status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const {
std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
if (proc.get() == nullptr) {
ALOGE("opendir /proc failed: %s\n", strerror(errno));
@@ -768,7 +768,7 @@
dumpPipe.readFd().reset();
}
- if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+ if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
if (err != NO_ERROR) {
return EPIPE;
@@ -778,6 +778,22 @@
return err;
}
+// ================================================================================
+BringYourOwnSection::BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+ const sp<IIncidentDumpCallback>& callback)
+ : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), uid(callingUid), mCallback(callback) {
+ name = "registered ";
+ name += customName;
+}
+
+BringYourOwnSection::~BringYourOwnSection() {}
+
+status_t BringYourOwnSection::BlockingCall(unique_fd& pipeWriteFd) const {
+ android::os::ParcelFileDescriptor pfd(std::move(pipeWriteFd));
+ mCallback->onDumpSection(pfd);
+ return NO_ERROR;
+}
+
} // namespace incidentd
} // namespace os
} // namespace android
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index fcf12f7..0bb9da9 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -23,6 +23,8 @@
#include <stdarg.h>
#include <map>
+#include <android/os/IIncidentDumpCallback.h>
+
#include <utils/String16.h>
#include <utils/String8.h>
#include <utils/Vector.h>
@@ -89,7 +91,7 @@
virtual status_t Execute(ReportWriter* writer) const;
- virtual status_t BlockingCall(int pipeWriteFd) const = 0;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const = 0;
};
/**
@@ -117,7 +119,7 @@
DumpsysSection(int id, const char* service, ...);
virtual ~DumpsysSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
String16 mService;
@@ -132,7 +134,7 @@
SystemPropertyDumpsysSection(int id, const char* service, ...);
virtual ~SystemPropertyDumpsysSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
String16 mService;
@@ -153,7 +155,7 @@
LogSection(int id, const char* logID, ...);
virtual ~LogSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
log_id_t mLogID;
@@ -169,12 +171,29 @@
TombstoneSection(int id, const char* type, int64_t timeoutMs = 120000 /* 2 minutes */);
virtual ~TombstoneSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
std::string mType;
};
+/**
+ * Section that gets data from a registered dump callback.
+ */
+class BringYourOwnSection : public WorkerThreadSection {
+public:
+ const uid_t uid;
+
+ BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+ const sp<IIncidentDumpCallback>& callback);
+ virtual ~BringYourOwnSection();
+
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
+
+private:
+ const sp<IIncidentDumpCallback>& mCallback;
+};
+
/**
* These sections will not be generated when doing an 'all' report, either
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 6033655..c2ee6dc 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -103,6 +103,10 @@
runSetVirtualDisk();
} else if ("set-isolated-storage".equals(op)) {
runIsolatedStorage();
+ } else if ("start-checkpoint".equals(op)) {
+ runStartCheckpoint();
+ } else if ("supports-checkpoint".equals(op)) {
+ runSupportsCheckpoint();
} else {
throw new IllegalArgumentException();
}
@@ -313,6 +317,27 @@
}
}
+ private void runStartCheckpoint() throws RemoteException {
+ final String numRetriesString = nextArg();
+ if (numRetriesString == null) {
+ throw new IllegalArgumentException("Expected <num-retries>");
+ }
+ int numRetries;
+ try {
+ numRetries = Integer.parseInt(numRetriesString);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("<num-retries> must be a positive integer");
+ }
+ if (numRetries <= 0) {
+ throw new IllegalArgumentException("<num-retries> must be a positive integer");
+ }
+ mSm.startCheckpoint(numRetries);
+ }
+
+ private void runSupportsCheckpoint() throws RemoteException {
+ System.out.println(mSm.supportsCheckpoint());
+ }
+
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
@@ -344,6 +369,10 @@
System.err.println("");
System.err.println(" sm set-isolated-storage [on|off|default]");
System.err.println("");
+ System.err.println(" sm start-checkpoint <num-retries>");
+ System.err.println("");
+ System.err.println(" sm supports-checkpoint");
+ System.err.println("");
return 1;
}
}
diff --git a/cmds/statsd/OWNERS b/cmds/statsd/OWNERS
index 04464ce..a61babf 100644
--- a/cmds/statsd/OWNERS
+++ b/cmds/statsd/OWNERS
@@ -1,7 +1,8 @@
-jianjin@google.com
+jeffreyhuang@google.com
joeo@google.com
jtnguyen@google.com
muhammadq@google.com
+ruchirr@google.com
singhtejinder@google.com
tsaichristine@google.com
yaochen@google.com
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 6fc1e23..967fd32 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -261,6 +261,11 @@
return Matcher(Field(tag, getSimpleField(field)), 0xff7f0000);
}
+inline Matcher getFirstUidMatcher(int32_t atomId) {
+ int32_t pos[] = {1, 1, 1};
+ return Matcher(Field(atomId, pos, 2), 0xff7f7f7f);
+}
+
/**
* A wrapper for a union type to contain multiple types of values.
*
diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto
index 6d2bd04..946c550 100644
--- a/cmds/statsd/src/atom_field_options.proto
+++ b/cmds/statsd/src/atom_field_options.proto
@@ -30,6 +30,8 @@
PRIMARY = 1;
// The field that represents the state. It's an exclusive state.
EXCLUSIVE = 2;
+
+ PRIMARY_FIELD_FIRST_UID = 3;
}
// Used to annotate an atom that reprsents a state change. A state change atom must have exactly ONE
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 19b9709..cbece78 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -126,10 +126,10 @@
AppStartOccurred app_start_occurred = 48;
AppStartCanceled app_start_canceled = 49;
AppStartFullyDrawn app_start_fully_drawn = 50;
- LmkKillOccurred lmk_kill_occurred = 51;
+ LmkKillOccurred lmk_kill_occurred = 51 [(module) = "lmkd"];
PictureInPictureStateChanged picture_in_picture_state_changed = 52;
WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53 [(module) = "wifi"];
- LmkStateChanged lmk_state_changed = 54;
+ LmkStateChanged lmk_state_changed = 54 [(module) = "lmkd"];
AppStartMemoryStateCaptured app_start_memory_state_captured = 55;
ShutdownSequenceReported shutdown_sequence_reported = 56;
BootSequenceReported boot_sequence_reported = 57;
@@ -333,10 +333,16 @@
MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"];
MediaProviderIdleMaintenance media_provider_idle_maintenance =
237 [(module) = "mediaprovider"];
+ RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238;
+ BootTimeEventDuration boot_time_event_duration_reported = 239;
+ BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240;
+ BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
+ BootTimeEventErrorCode boot_time_event_error_code_reported = 242;
+ UserspaceRebootReported userspace_reboot_reported = 243;
}
// Pulled events will start at field 10000.
- // Next: 10068
+ // Next: 10070
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000;
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -405,6 +411,8 @@
VmsClientStats vms_client_stats = 10065;
NotificationRemoteViews notification_remote_views = 10066;
DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067;
+ GraphicsStats graphics_stats = 10068;
+ RuntimeAppOpsAccess runtime_app_ops_access = 10069;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -906,14 +914,16 @@
* TODO
*/
message WakelockStateChanged {
- repeated AttributionNode attribution_node = 1;
+ repeated AttributionNode attribution_node = 1
+ [(state_field_option).option = PRIMARY_FIELD_FIRST_UID];
// The type (level) of the wakelock; e.g. a partial wakelock or a full wakelock.
// From frameworks/base/core/proto/android/os/enums.proto.
- optional android.os.WakeLockLevelEnum type = 2;
+ optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).option = PRIMARY];
+ ;
// The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
- optional string tag = 3;
+ optional string tag = 3 [(state_field_option).option = PRIMARY];
enum State {
RELEASE = 0;
@@ -921,7 +931,7 @@
CHANGE_RELEASE = 2;
CHANGE_ACQUIRE = 3;
}
- optional State state = 4;
+ optional State state = 4 [(state_field_option).option = EXCLUSIVE];
}
/**
@@ -3923,6 +3933,207 @@
optional float normalized_expired_media = 5;
}
+/**
+ * Represents boot time event with duration in ms.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventDuration {
+ enum DurationEvent {
+ UNKNOWN = 0;
+ // Bootloader time excluding BOOTLOADER_UI_WAIT + boot complete time. Logged from bootstat.
+ ABSOLUTE_BOOT_TIME = 1;
+ // Bootloader's 1st stage execution time.
+ // Logged from bootstat.
+ BOOTLOADER_FIRST_STAGE_EXEC = 2;
+ // Bootloader's 1st stage loading time.
+ // Logged from bootstat.
+ BOOTLOADER_FIRST_STAGE_LOAD = 3;
+ // Bootloader's kernel loading time.
+ // Logged from bootstat.
+ BOOTLOADER_KERNEL_LOAD = 4;
+ // Bootloader's 2nd stage execution time.
+ // Logged from bootstat.
+ BOOTLOADER_SECOND_STAGE_EXEC = 5;
+ // Bootloader's 2nd stage loading time.
+ // Logged from bootstat.
+ BOOTLOADER_SECOND_STAGE_LOAD = 6;
+ // Duration for Bootloader to show unlocked device's warning UI. This should not happen
+ // for locked device.
+ // Logged from bootstat.
+ BOOTLOADER_UI_WAIT = 7;
+ // Total time spend in bootloader. This is the sum of all BOOTLOADER_* listed above.
+ // Logged from bootstat.
+ BOOTLOADER_TOTAL = 8;
+ // Shutdown duration inside init for the reboot before the current boot up.
+ // Logged from f/b/services/.../BootReceiver.java.
+ SHUTDOWN_DURATION = 9;
+ // Total time for mounting of disk devices during bootup.
+ // Logged from f/b/services/.../BootReceiver.java.
+ MOUNT_DEFAULT_DURATION = 10;
+ // Total time for early stage mounting of disk devices during bootup.
+ // Logged from f/b/services/.../BootReceiver.java.
+ MOUNT_EARLY_DURATION = 11;
+ // Total time for late stage mounting of disk devices during bootup.
+ // Logged from f/b/services/.../BootReceiver.java.
+ MOUNT_LATE_DURATION = 12;
+ // Average time to scan non-system app after OTA
+ // Logged from f/b/services/.../PackageManagerService.java
+ OTA_PACKAGE_MANAGER_INIT_TIME = 13;
+ // Time to initialize Package manager after OTA
+ // Logged from f/b/services/.../PackageManagerService.java
+ OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME = 14;
+ // Time to scan all system app from Package manager after OTA
+ // Logged from f/b/services/.../PackageManagerService.java
+ OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME = 15;
+ // Init's total time for cold boot stage.
+ // Logged from bootstat.
+ COLDBOOT_WAIT = 16;
+ // Init's total time for initializing selinux.
+ // Logged from bootstat.
+ SELINUX_INIT = 17;
+ // Time since last factory reset.
+ // Logged from bootstat.
+ FACTORY_RESET_TIME_SINCE_RESET = 18;
+ }
+
+ // Type of the event.
+ optional DurationEvent event = 1;
+ // Duration of the event in ms.
+ optional int64 duration_millis = 2;
+}
+
+/**
+ * Represents the start of specific boot time event during bootup in ms. This is usually a time
+ * since boot-up.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventElapsedTime {
+ enum ElapsedTimeEvent {
+ UNKNOWN = 0;
+ // Time when init starts 1st stage. Logged from bootstat.
+ ANDROID_INIT_STAGE_1 = 1;
+ // Time when sys.boot_completed prop is set.
+ // Logged from bootstat.
+ BOOT_COMPLETE = 2;
+ // BOOT_COMPLETE for encrypted device.
+ BOOT_COMPLETE_ENCRYPTION = 3;
+ // BOOT_COMPLETE for device with no encryption.
+ BOOT_COMPLETE_NO_ENCRYPTION = 4;
+ // Adjusted BOOT_COMPLETE for encrypted device extracting decryption time.
+ BOOT_COMPLETE_POST_DESCRYPT = 5;
+ // BOOT_COMPLETE after factory reset.
+ FACTORY_RESET_BOOT_COMPLETE = 6;
+ // BOOT_COMPLETE_NO_ENCRYPTION after factory reset.
+ FACTORY_RESET_BOOT_COMPLETE_NO_ENCRYPTION = 7;
+ // BOOT_COMPLETE_POST_DESCRYPT after factory reset.
+ FACTORY_RESET_BOOT_COMPLETE_POST_DESCRYPT = 8;
+ // BOOT_COMPLETE after OTA.
+ OTA_BOOT_COMPLETE = 9;
+ // BOOT_COMPLETE_NO_ENCRYPTION after OTA.
+ OTA_BOOT_COMPLETE_NO_ENCRYPTION = 10;
+ // BOOT_COMPLETE_POST_DESCRYPT after OTA.
+ OTA_BOOT_COMPLETE_POST_DESCRYPT = 11;
+ // Time when the system starts sending LOCKED_BOOT_COMPLETED broadcast.
+ // Logged from f/b/services/.../UserController.java
+ FRAMEWORK_LOCKED_BOOT_COMPLETED = 12;
+ // Time when the system starts sending BOOT_COMPLETED broadcast.
+ // Logged from f/b/services/.../UserController.java
+ FRAMEWORK_BOOT_COMPLETED = 13;
+ // Time when the package manager starts init.
+ // Logged from f/b/services/.../SystemServer.java
+ PACKAGE_MANAGER_INIT_START = 14;
+ // Time when package manager is ready
+ // Logged from f/b/services/.../SystemServer.java
+ PACKAGE_MANAGER_INIT_READY = 15;
+ // Represents the time when user has entered unlock credential for system with user pin.
+ // Logged from bootstat.
+ POST_DECRYPT = 16;
+ // Represents the start of zygote's init.
+ // Logged from zygote itself.
+ ZYGOTE_INIT_START = 17;
+ // Represents the start of secondary zygote's init.
+ // TODO: add logging to zygote
+ SECONDARY_ZYGOTE_INIT_START = 18;
+ // Represents the start of system server's init.
+ // Logged from f/b/services/.../SystemServer.java
+ SYSTEM_SERVER_INIT_START = 19;
+ // Represents the completion of system server's init.
+ // Logged from f/b/services/.../SystemServer.java
+ SYSTEM_SERVER_READY = 20;
+ // Represents the start of launcher during boot-up.
+ // TODO: add logging
+ LAUNCHER_START = 21;
+ // Represents the completion of launcher's initial rendering. User can use other apps from
+ // launcher from this point.
+ // TODO: add logging
+ LAUNCHER_SHOWN = 22;
+ }
+
+ // Type of the event.
+ optional ElapsedTimeEvent event = 1;
+ // Time since bootup for the event.
+ // It should be acquired from SystemClock elapsedRealtime() call or equivalent.
+ optional int64 time_millis = 2;
+}
+
+/**
+ * Boot time events with UTC time.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventUtcTime {
+ enum UtcTimeEvent {
+ UNKNOWN = 0;
+ // Time of the bootstat's marking of 1st boot after the last factory reset.
+ // Logged from bootstat.
+ FACTORY_RESET_RESET_TIME = 1;
+ // The time when bootstat records FACTORY_RESET_* events. This is close to
+ // BOOT_COMPLETE time for the current bootup.
+ // Logged from bootstat.
+ FACTORY_RESET_CURRENT_TIME = 2;
+ // DUplicate of FACTORY_RESET_RESET_TIME added for debugging purpose.
+ // Logged from bootstat.
+ FACTORY_RESET_RECORD_VALUE = 3;
+ }
+
+ // Type of the event.
+ optional UtcTimeEvent event = 1;
+ // UTC time for the event.
+ optional int64 utc_time_secs = 2;
+}
+
+/**
+ * Boot time events representing specific error code during bootup.
+ * Meaning of error code can be different per each event type.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventErrorCode {
+ enum ErrorCodeEvent {
+ UNKNOWN = 0;
+ // Linux error code for time() call to get the current UTC time.
+ // Logged from bootstat.
+ FACTORY_RESET_CURRENT_TIME_FAILURE = 1;
+ // Represents UmountStat before the reboot for the current boot up. Error codes defined
+ // as UMOUNT_STAT_* from init/reboot.cpp.
+ // Logged from f/b/services/.../BootReceiver.java.
+ SHUTDOWN_UMOUNT_STAT = 2;
+ // Reprepsents fie system mounting error code of /data partition for the current boot.
+ // Error codes defined as combination of FsStatFlags from system/core/fs_mgr/fs_mgr.cpp.
+ // Logged from f/b/services/.../BootReceiver.java.
+ FS_MGR_FS_STAT_DATA_PARTITION = 3;
+ }
+
+ // Type of the event.
+ optional ErrorCodeEvent event = 1;
+ // error code defined per each event type.
+ // For example, this can have a value of FsStatFlags.FS_STAT_FULL_MOUNT_FAILED for the event of
+ // FS_MGR_FS_STAT.
+ optional int32 error_code = 2;
+}
+
//////////////////////////////////////////////////////////////////////
// Pulled atoms below this line //
//////////////////////////////////////////////////////////////////////
@@ -4723,36 +4934,69 @@
}
message AggStats {
- optional int64 min = 1;
+ // These are all in byte resolution.
+ optional int64 min = 1 [deprecated = true];
+ optional int64 average = 2 [deprecated = true];
+ optional int64 max = 3 [deprecated = true];
- optional int64 average = 2;
-
- optional int64 max = 3;
+ // These are all in kilobyte resolution. Can fit in int32, so smaller on the wire than the above
+ // int64 fields.
+ optional int32 mean_kb = 4;
+ optional int32 max_kb = 5;
}
+// A reduced subset of process states; reducing the number of possible states allows more
+// aggressive device-side aggregation of statistics and hence reduces metric upload size.
+enum ProcessStateAggregated {
+ PROCESS_STATE_UNKNOWN = 0;
+ // Persistent system process.
+ PROCESS_STATE_PERSISTENT = 1;
+ // Top activity; actually any visible activity.
+ PROCESS_STATE_TOP = 2;
+ // Process binding to top or a foreground service.
+ PROCESS_STATE_BOUND_TOP_OR_FGS = 3;
+ // Processing running a foreground service.
+ PROCESS_STATE_FGS = 4;
+ // Important foreground process (ime, wallpaper, etc).
+ PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
+ // Important background process.
+ PROCESS_STATE_BACKGROUND = 6;
+ // Process running a receiver.
+ PROCESS_STATE_RECEIVER = 7;
+ // All kinds of cached processes.
+ PROCESS_STATE_CACHED = 8;
+}
+
+// Next tag: 13
message ProcessStatsStateProto {
optional android.service.procstats.ScreenState screen_state = 1;
- optional android.service.procstats.MemoryState memory_state = 2;
+ optional android.service.procstats.MemoryState memory_state = 2 [deprecated = true];
// this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java
// and not frameworks/base/core/java/android/app/ActivityManager.java
- optional android.service.procstats.ProcessState process_state = 3;
+ optional android.service.procstats.ProcessState process_state = 3 [deprecated = true];
+
+ optional ProcessStateAggregated process_state_aggregated = 10;
// Millisecond uptime duration spent in this state
- optional int64 duration_millis = 4;
+ optional int64 duration_millis = 4 [deprecated = true];
+ // Same as above, but with minute resolution so it fits into an int32.
+ optional int32 duration_minutes = 11;
// Millisecond elapsed realtime duration spent in this state
- optional int64 realtime_duration_millis = 9;
+ optional int64 realtime_duration_millis = 9 [deprecated = true];
+ // Same as above, but with minute resolution so it fits into an int32.
+ optional int32 realtime_duration_minutes = 12;
// # of samples taken
optional int32 sample_size = 5;
// PSS is memory reserved for this process
- optional AggStats pss = 6;
+ optional AggStats pss = 6 [deprecated = true];
// USS is memory shared between processes, divided evenly for accounting
- optional AggStats uss = 7;
+ optional AggStats uss = 7 [deprecated = true];
// RSS is memory resident for this process
optional AggStats rss = 8;
@@ -4777,7 +5021,7 @@
// PSS stats during cached kill
optional AggStats cached_pss = 3;
}
- optional Kill kill = 3;
+ optional Kill kill = 3 [deprecated = true];
// Time and memory spent in various states.
repeated ProcessStatsStateProto states = 5;
@@ -6844,7 +7088,7 @@
// Uid of the package requesting the op
optional int32 uid = 1 [(is_uid) = true];
- // Nmae of the package performing the op
+ // Name of the package performing the op
optional string package_name = 2;
// operation id; maps to the OP_* constants in AppOpsManager.java
@@ -7338,6 +7582,17 @@
}
/**
+ * Reported when the RebootEscrow HAL has attempted to recover the escrowed
+ * key to indicate whether it was successful or not.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+ */
+message RebootEscrowRecoveryReported {
+ optional bool successful = 1;
+}
+
+/**
* Global display pipeline metrics reported by SurfaceFlinger.
* Pulled from:
* frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -7553,3 +7808,139 @@
optional int32 permission_flags = 4;
}
+/**
+ * HWUI renders pipeline type: GL (0) or Vulkan (1).
+ */
+enum PipelineType {
+ GL = 0;
+ VULKAN = 1;
+}
+
+/**
+ * HWUI stats for a given app.
+ */
+message GraphicsStats {
+ // The package name of the app
+ optional string package_name = 1;
+
+ // The version code of the app
+ optional int64 version_code = 2;
+
+ // The start & end timestamps in UTC as
+ // milliseconds since January 1, 1970
+ // Compatible with java.util.Date#setTime()
+ optional int64 stats_start = 3;
+
+ optional int64 stats_end = 4;
+
+ // HWUI renders pipeline type: GL or Vulkan.
+ optional PipelineType pipeline = 5;
+
+ // Distinct frame count.
+ optional int32 total_frames = 6;
+
+ // Number of "missed vsync" events.
+ optional int32 missed_vsync_count = 7;
+
+ // Number of frames in triple-buffering scenario (high input latency)
+ optional int32 high_input_latency_count = 8;
+
+ // Number of "slow UI thread" events.
+ optional int32 slow_ui_thread_count = 9;
+
+ // Number of "slow bitmap upload" events.
+ optional int32 slow_bitmap_upload_count = 10;
+
+ // Number of "slow draw" events.
+ optional int32 slow_draw_count = 11;
+
+ // Number of frames that missed their deadline (aka, visibly janked)
+ optional int32 missed_deadline_count = 12;
+
+ // The frame time histogram for the package
+ optional FrameTimingHistogram cpu_histogram = 13
+ [(android.os.statsd.log_mode) = MODE_BYTES];
+
+ // The gpu frame time histogram for the package
+ optional FrameTimingHistogram gpu_histogram = 14
+ [(android.os.statsd.log_mode) = MODE_BYTES];
+
+ // UI mainline module version.
+ optional int64 version_ui_module = 15;
+
+ // If true, these are HWUI stats for up to a 24h period for a given app from today.
+ // If false, these are HWUI stats for a 24h period for a given app from the last complete
+ // day (yesterday). Stats from yesterday stay constant, while stats from today may change as
+ // more apps are running / rendering.
+ optional bool is_today = 16;
+}
+
+/**
+ * Message related to dangerous (runtime) app ops access
+ */
+message RuntimeAppOpsAccess {
+ // Uid of the package accessing app op
+ optional int32 uid = 1 [(is_uid) = true];
+
+ // Name of the package accessing app op
+ optional string package_name = 2;
+
+ // operation id; maps to the OP_* constants in AppOpsManager.java
+ optional int32 op_id = 3;
+
+ // feature id; provided by developer when accessing related API, limited at 50 chars by API.
+ // Features must be provided through manifest using <feature> tag available in R and above.
+ optional string feature_id = 4;
+
+ // message related to app op access, limited to 600 chars by API
+ optional string message = 5;
+
+ enum SamplingStrategy {
+ DEFAULT = 0;
+ UNIFORM = 1;
+ RARELY_USED = 2;
+ }
+
+ // sampling strategy used to collect this message
+ optional SamplingStrategy sampling_strategy = 6;
+}
+
+/*
+ * Logs userspace reboot outcome and duration.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/server/BootReceiver.java
+ */
+message UserspaceRebootReported {
+ // Possible outcomes of userspace reboot.
+ enum Outcome {
+ // Default value in case platform failed to determine the outcome.
+ OUTCOME_UNKNOWN = 0;
+ // Userspace reboot succeeded (i.e. boot completed without a fall back to hard reboot).
+ SUCCESS = 1;
+ // Userspace reboot shutdown sequence was aborted.
+ FAILED_SHUTDOWN_SEQUENCE_ABORTED = 2;
+ // Remounting userdata into checkpointing mode failed.
+ FAILED_USERDATA_REMOUNT = 3;
+ // Device didn't finish booting before timeout and userspace reboot watchdog issued a hard
+ // reboot.
+ FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED = 4;
+ }
+ // Outcome of userspace reboot. Always set.
+ optional Outcome outcome = 1;
+ // Duration of userspace reboot in case it has a successful outcome.
+ // Duration is measured as time between userspace reboot was initiated and until boot completed
+ // (e.g. sys.boot_completed=1).
+ optional int64 duration_millis = 2;
+ // State of primary user's (user0) credential encryption storage.
+ enum UserEncryptionState {
+ // Default value.
+ USER_ENCRYPTION_STATE_UNKNOWN = 0;
+ // Credential encrypted storage is unlocked.
+ UNLOCKED = 1;
+ // Credential encrypted storage is locked.
+ LOCKED = 2;
+ }
+ // State of primary user's encryption storage at the moment boot completed. Always set.
+ optional UserEncryptionState user_encryption_state = 3;
+}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 1d31873..b5920cb 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -59,161 +59,132 @@
const int64_t NO_ALARM_UPDATE = INT64_MAX;
std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
- // wifi_bytes_transfer
- {{.atomTag = android::util::WIFI_BYTES_TRANSFER},
- {.additiveFields = {2, 3, 4, 5},
- .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}},
- // wifi_bytes_transfer_by_fg_bg
- {{.atomTag = android::util::WIFI_BYTES_TRANSFER_BY_FG_BG},
- {.additiveFields = {3, 4, 5, 6},
- .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}},
- // mobile_bytes_transfer
- {{.atomTag = android::util::MOBILE_BYTES_TRANSFER},
- {.additiveFields = {2, 3, 4, 5},
- .puller = new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}},
- // mobile_bytes_transfer_by_fg_bg
- {{.atomTag = android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG},
- {.additiveFields = {3, 4, 5, 6},
- .puller =
- new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}},
- // bluetooth_bytes_transfer
- {{.atomTag = android::util::BLUETOOTH_BYTES_TRANSFER},
- {.additiveFields = {2, 3},
- .puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}},
- // kernel_wakelock
- {{.atomTag = android::util::KERNEL_WAKELOCK},
- {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
+
// subsystem_sleep_state
{{.atomTag = android::util::SUBSYSTEM_SLEEP_STATE},
{.puller = new SubsystemSleepStatePuller()}},
+
// on_device_power_measurement
{{.atomTag = android::util::ON_DEVICE_POWER_MEASUREMENT},
{.puller = new PowerStatsPuller()}},
- // cpu_time_per_freq
- {{.atomTag = android::util::CPU_TIME_PER_FREQ},
- {.additiveFields = {3},
- .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
- // cpu_time_per_uid
- {{.atomTag = android::util::CPU_TIME_PER_UID},
- {.additiveFields = {2, 3},
- .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}},
- // cpu_time_per_uid_freq
- // the throttling is 3sec, handled in
- // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
- {{.atomTag = android::util::CPU_TIME_PER_UID_FREQ},
- {.additiveFields = {4},
- .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}},
- // cpu_active_time
- // the throttling is 3sec, handled in
- // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
- {{.atomTag = android::util::CPU_ACTIVE_TIME},
- {.additiveFields = {2},
- .puller = new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}},
- // cpu_cluster_time
- // the throttling is 3sec, handled in
- // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
- {{.atomTag = android::util::CPU_CLUSTER_TIME},
- {.additiveFields = {3},
- .puller = new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}},
+
// wifi_activity_energy_info
{{.atomTag = android::util::WIFI_ACTIVITY_INFO},
{.puller = new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}},
+
// modem_activity_info
{{.atomTag = android::util::MODEM_ACTIVITY_INFO},
{.puller = new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}},
- // bluetooth_activity_info
- {{.atomTag = android::util::BLUETOOTH_ACTIVITY_INFO},
- {.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
+
// system_elapsed_realtime
{{.atomTag = android::util::SYSTEM_ELAPSED_REALTIME},
{.coolDownNs = NS_PER_SEC,
.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME),
.pullTimeoutNs = NS_PER_SEC / 2,
}},
- // system_uptime
- {{.atomTag = android::util::SYSTEM_UPTIME},
- {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
+
// remaining_battery_capacity
{{.atomTag = android::util::REMAINING_BATTERY_CAPACITY},
{.puller = new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}},
+
// full_battery_capacity
{{.atomTag = android::util::FULL_BATTERY_CAPACITY},
{.puller = new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}},
+
// battery_voltage
{{.atomTag = android::util::BATTERY_VOLTAGE},
{.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
+
// battery_level
{{.atomTag = android::util::BATTERY_LEVEL},
{.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}},
+
// battery_cycle_count
{{.atomTag = android::util::BATTERY_CYCLE_COUNT},
{.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}},
+
// process_memory_state
{{.atomTag = android::util::PROCESS_MEMORY_STATE},
{.additiveFields = {4, 5, 6, 7, 8},
.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}},
+
// process_memory_high_water_mark
{{.atomTag = android::util::PROCESS_MEMORY_HIGH_WATER_MARK},
{.puller =
new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
+
// process_memory_snapshot
{{.atomTag = android::util::PROCESS_MEMORY_SNAPSHOT},
{.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_SNAPSHOT)}},
+
// system_ion_heap_size
{{.atomTag = android::util::SYSTEM_ION_HEAP_SIZE},
{.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}},
+
// process_system_ion_heap_size
{{.atomTag = android::util::PROCESS_SYSTEM_ION_HEAP_SIZE},
{.puller = new StatsCompanionServicePuller(android::util::PROCESS_SYSTEM_ION_HEAP_SIZE)}},
+
// temperature
{{.atomTag = android::util::TEMPERATURE},
{.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}},
+
// cooling_device
{{.atomTag = android::util::COOLING_DEVICE},
{.puller = new StatsCompanionServicePuller(android::util::COOLING_DEVICE)}},
+
// binder_calls
{{.atomTag = android::util::BINDER_CALLS},
{.additiveFields = {4, 5, 6, 8, 12},
.puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS)}},
+
// binder_calls_exceptions
{{.atomTag = android::util::BINDER_CALLS_EXCEPTIONS},
{.puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS_EXCEPTIONS)}},
+
// looper_stats
{{.atomTag = android::util::LOOPER_STATS},
{.additiveFields = {5, 6, 7, 8, 9},
.puller = new StatsCompanionServicePuller(android::util::LOOPER_STATS)}},
+
// Disk Stats
{{.atomTag = android::util::DISK_STATS},
{.puller = new StatsCompanionServicePuller(android::util::DISK_STATS)}},
+
// Directory usage
{{.atomTag = android::util::DIRECTORY_USAGE},
{.puller = new StatsCompanionServicePuller(android::util::DIRECTORY_USAGE)}},
+
// Size of app's code, data, and cache
{{.atomTag = android::util::APP_SIZE},
{.puller = new StatsCompanionServicePuller(android::util::APP_SIZE)}},
+
// Size of specific categories of files. Eg. Music.
{{.atomTag = android::util::CATEGORY_SIZE},
{.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}},
+
// Number of fingerprints enrolled for each user.
{{.atomTag = android::util::NUM_FINGERPRINTS_ENROLLED},
{.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}},
+
// Number of faces enrolled for each user.
{{.atomTag = android::util::NUM_FACES_ENROLLED},
{.puller = new StatsCompanionServicePuller(android::util::NUM_FACES_ENROLLED)}},
+
// ProcStats.
{{.atomTag = android::util::PROC_STATS},
{.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}},
+
// ProcStatsPkgProc.
{{.atomTag = android::util::PROC_STATS_PKG_PROC},
{.puller = new StatsCompanionServicePuller(android::util::PROC_STATS_PKG_PROC)}},
+
// Disk I/O stats per uid.
{{.atomTag = android::util::DISK_IO},
{.additiveFields = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
.coolDownNs = 3 * NS_PER_SEC,
.puller = new StatsCompanionServicePuller(android::util::DISK_IO)}},
- // PowerProfile constants for power model calculations.
- {{.atomTag = android::util::POWER_PROFILE},
- {.puller = new StatsCompanionServicePuller(android::util::POWER_PROFILE)}},
+
// Process cpu stats. Min cool-down is 5 sec, inline with what AcitivityManagerService uses.
{{.atomTag = android::util::PROCESS_CPU_TIME},
{.coolDownNs = 5 * NS_PER_SEC /* min cool-down in seconds*/,
@@ -221,64 +192,79 @@
{{.atomTag = android::util::CPU_TIME_PER_THREAD_FREQ},
{.additiveFields = {7, 9, 11, 13, 15, 17, 19, 21},
.puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}},
+
// DeviceCalculatedPowerUse.
{{.atomTag = android::util::DEVICE_CALCULATED_POWER_USE},
{.puller = new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}},
+
// DeviceCalculatedPowerBlameUid.
{{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_UID},
{.puller = new StatsCompanionServicePuller(
android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}},
+
// DeviceCalculatedPowerBlameOther.
{{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER},
{.puller = new StatsCompanionServicePuller(
android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
+
// DebugElapsedClock.
{{.atomTag = android::util::DEBUG_ELAPSED_CLOCK},
{.additiveFields = {1, 2, 3, 4},
.puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}},
+
// DebugFailingElapsedClock.
{{.atomTag = android::util::DEBUG_FAILING_ELAPSED_CLOCK},
{.additiveFields = {1, 2, 3, 4},
.puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
- // BuildInformation.
- {{.atomTag = android::util::BUILD_INFORMATION},
- {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
+
// RoleHolder.
{{.atomTag = android::util::ROLE_HOLDER},
{.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}},
+
// PermissionState.
{{.atomTag = android::util::DANGEROUS_PERMISSION_STATE},
{.puller = new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE)}},
+
// TrainInfo.
{{.atomTag = android::util::TRAIN_INFO}, {.puller = new TrainInfoPuller()}},
+
// TimeZoneDataInfo.
{{.atomTag = android::util::TIME_ZONE_DATA_INFO},
{.puller = new StatsCompanionServicePuller(android::util::TIME_ZONE_DATA_INFO)}},
+
// ExternalStorageInfo
{{.atomTag = android::util::EXTERNAL_STORAGE_INFO},
{.puller = new StatsCompanionServicePuller(android::util::EXTERNAL_STORAGE_INFO)}},
+
// GpuStatsGlobalInfo
{{.atomTag = android::util::GPU_STATS_GLOBAL_INFO},
{.puller = new GpuStatsPuller(android::util::GPU_STATS_GLOBAL_INFO)}},
+
// GpuStatsAppInfo
{{.atomTag = android::util::GPU_STATS_APP_INFO},
{.puller = new GpuStatsPuller(android::util::GPU_STATS_APP_INFO)}},
+
// AppsOnExternalStorageInfo
{{.atomTag = android::util::APPS_ON_EXTERNAL_STORAGE_INFO},
{.puller = new StatsCompanionServicePuller(android::util::APPS_ON_EXTERNAL_STORAGE_INFO)}},
+
// Face Settings
{{.atomTag = android::util::FACE_SETTINGS},
{.puller = new StatsCompanionServicePuller(android::util::FACE_SETTINGS)}},
+
// App ops
{{.atomTag = android::util::APP_OPS},
{.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
+
// VmsClientStats
{{.atomTag = android::util::VMS_CLIENT_STATS},
{.additiveFields = {5, 6, 7, 8, 9, 10},
.puller = new CarStatsPuller(android::util::VMS_CLIENT_STATS)}},
+
// NotiifcationRemoteViews.
{{.atomTag = android::util::NOTIFICATION_REMOTE_VIEWS},
{.puller = new StatsCompanionServicePuller(android::util::NOTIFICATION_REMOTE_VIEWS)}},
+
// PermissionStateSampled.
{{.atomTag = android::util::DANGEROUS_PERMISSION_STATE_SAMPLED},
{.puller =
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
index ef59c92..3ad21e0 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -28,10 +28,14 @@
StateTracker::StateTracker(const int32_t atomId, const util::StateAtomFieldOptions& stateAtomInfo)
: mAtomId(atomId), mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) {
// create matcher for each primary field
- // TODO(tsaichristine): b/142108433 handle when primary field is first uid in chain
- for (const auto& primary : stateAtomInfo.primaryFields) {
- Matcher matcher = getSimpleMatcher(atomId, primary);
- mPrimaryFields.push_back(matcher);
+ for (const auto& primaryField : stateAtomInfo.primaryFields) {
+ if (primaryField == util::FIRST_UID_IN_CHAIN) {
+ Matcher matcher = getFirstUidMatcher(atomId);
+ mPrimaryFields.push_back(matcher);
+ } else {
+ Matcher matcher = getSimpleMatcher(atomId, primaryField);
+ mPrimaryFields.push_back(matcher);
+ }
}
// TODO(tsaichristine): b/142108433 set default state, reset state, and nesting
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index 7453370..70f1627 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -72,7 +72,7 @@
int32_t mDefaultState = kStateUnknown;
- int32_t mResetState;
+ int32_t mResetState = kStateUnknown;
// Maps primary key to state value info
std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap;
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index 26a3733..84aaa54 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -76,6 +76,23 @@
return event;
}
+// State with first uid in attribution chain as primary field - WakelockStateChanged
+std::shared_ptr<LogEvent> buildPartialWakelockEvent(int uid, const std::string& tag, bool acquire) {
+ std::vector<AttributionNodeInternal> chain;
+ chain.push_back(AttributionNodeInternal());
+ AttributionNodeInternal& attr = chain.back();
+ attr.set_uid(uid);
+
+ std::shared_ptr<LogEvent> event =
+ std::make_shared<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, 1000 /* timestamp */);
+ event->write(chain);
+ event->write((int32_t)1); // PARTIAL_WAKE_LOCK
+ event->write(tag);
+ event->write(acquire ? 1 : 0);
+ event->init();
+ return event;
+}
+
// State with multiple primary fields - OverlayStateChanged
std::shared_ptr<LogEvent> buildOverlayEvent(int uid, const std::string& packageName, int state) {
std::shared_ptr<LogEvent> event =
@@ -134,6 +151,39 @@
key->addValue(FieldValue(field1, value1));
key->addValue(FieldValue(field2, value2));
}
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
+ int pos1[] = {1, 1, 1};
+ int pos3[] = {2, 0, 0};
+ int pos4[] = {3, 0, 0};
+
+ Field field1(10 /* atom id */, pos1, 2 /* depth */);
+
+ Field field3(10 /* atom id */, pos3, 0 /* depth */);
+ Field field4(10 /* atom id */, pos4, 0 /* depth */);
+
+ Value value1((int32_t)uid);
+ Value value3((int32_t)1 /*partial*/);
+ Value value4(tag);
+
+ key->addValue(FieldValue(field1, value1));
+ key->addValue(FieldValue(field3, value3));
+ key->addValue(FieldValue(field4, value4));
+}
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
+ int pos1[] = {1, 1, 1};
+ int pos3[] = {2, 0, 0};
+
+ Field field1(10 /* atom id */, pos1, 2 /* depth */);
+ Field field3(10 /* atom id */, pos3, 0 /* depth */);
+
+ Value value1((int32_t)uid);
+ Value value3((int32_t)1 /*partial*/);
+
+ key->addValue(FieldValue(field1, value1));
+ key->addValue(FieldValue(field3, value3));
+}
// END: get primary key functions
TEST(StateListenerTest, TestStateListenerWeakPointer) {
@@ -247,7 +297,8 @@
// check StateTracker was updated by querying for state
HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY;
- EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey));
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+ getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey));
}
/**
@@ -272,7 +323,46 @@
// check StateTracker was updated by querying for state
HashableDimensionKey queryKey;
getUidProcessKey(1000 /* uid */, &queryKey);
- EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP,
+ getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+}
+
+TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) {
+ sp<TestStateListener> listener1 = new TestStateListener();
+ StateManager mgr;
+ mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener1);
+
+ // Log event.
+ std::shared_ptr<LogEvent> event =
+ buildPartialWakelockEvent(1001 /* uid */, "tag1", false /* acquire */);
+ mgr.onLogEvent(*event);
+
+ EXPECT_EQ(1, mgr.getStateTrackersCount());
+ EXPECT_EQ(1, mgr.getListenersCount(android::util::WAKELOCK_STATE_CHANGED));
+
+ // Check listener was updated.
+ EXPECT_EQ(1, listener1->updates.size());
+ EXPECT_EQ(3, listener1->updates[0].mKey.getValues().size());
+ EXPECT_EQ(1001, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+ EXPECT_EQ(1, listener1->updates[0].mKey.getValues()[1].mValue.int_value);
+ EXPECT_EQ("tag1", listener1->updates[0].mKey.getValues()[2].mValue.str_value);
+ EXPECT_EQ(WakelockStateChanged::RELEASE, listener1->updates[0].mState);
+
+ // Check StateTracker was updated by querying for state.
+ HashableDimensionKey queryKey;
+ getPartialWakelockKey(1001 /* uid */, "tag1", &queryKey);
+ EXPECT_EQ(WakelockStateChanged::RELEASE,
+ getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey));
+
+ // No state stored for this query key.
+ HashableDimensionKey queryKey2;
+ getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2);
+ EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey2));
+
+ // Partial query fails.
+ HashableDimensionKey queryKey3;
+ getPartialWakelockKey(1001 /* uid */, &queryKey3);
+ EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey3));
}
/**
@@ -297,7 +387,8 @@
// check StateTracker was updated by querying for state
HashableDimensionKey queryKey;
getOverlayKey(1000 /* uid */, "package1", &queryKey);
- EXPECT_EQ(1, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey));
+ EXPECT_EQ(OverlayStateChanged::ENTERED,
+ getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey));
}
/**
@@ -326,10 +417,12 @@
sp<TestStateListener> listener1 = new TestStateListener();
sp<TestStateListener> listener2 = new TestStateListener();
sp<TestStateListener> listener3 = new TestStateListener();
+ sp<TestStateListener> listener4 = new TestStateListener();
StateManager mgr;
mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener2);
mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener3);
+ mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener4);
std::shared_ptr<LogEvent> event1 = buildUidProcessEvent(
1000,
@@ -346,8 +439,12 @@
android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
std::shared_ptr<LogEvent> event5 =
buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
- std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1);
- std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2);
+ std::shared_ptr<LogEvent> event6 =
+ buildOverlayEvent(1000, "package1", OverlayStateChanged::ENTERED);
+ std::shared_ptr<LogEvent> event7 =
+ buildOverlayEvent(1000, "package2", OverlayStateChanged::EXITED);
+ std::shared_ptr<LogEvent> event8 = buildPartialWakelockEvent(1005, "tag1", true);
+ std::shared_ptr<LogEvent> event9 = buildPartialWakelockEvent(1005, "tag2", false);
mgr.onLogEvent(*event1);
mgr.onLogEvent(*event2);
@@ -356,11 +453,14 @@
mgr.onLogEvent(*event5);
mgr.onLogEvent(*event6);
mgr.onLogEvent(*event7);
+ mgr.onLogEvent(*event8);
+ mgr.onLogEvent(*event9);
// Query for UidProcessState of uid 1001
HashableDimensionKey queryKey1;
getUidProcessKey(1001, &queryKey1);
- EXPECT_EQ(1003, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE,
+ getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
// Query for UidProcessState of uid 1004 - not in state map
HashableDimensionKey queryKey2;
@@ -370,15 +470,30 @@
// Query for UidProcessState of uid 1001 - after change in state
mgr.onLogEvent(*event4);
- EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP,
+ getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
// Query for ScreenState
- EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+ getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
// Query for OverlayState of uid 1000, package name "package2"
HashableDimensionKey queryKey3;
getOverlayKey(1000, "package2", &queryKey3);
- EXPECT_EQ(2, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3));
+ EXPECT_EQ(OverlayStateChanged::EXITED,
+ getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3));
+
+ // Query for WakelockState of uid 1005, tag 2
+ HashableDimensionKey queryKey4;
+ getPartialWakelockKey(1005, "tag2", &queryKey4);
+ EXPECT_EQ(WakelockStateChanged::RELEASE,
+ getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey4));
+
+ // Query for WakelockState of uid 1005, tag 1
+ HashableDimensionKey queryKey5;
+ getPartialWakelockKey(1005, "tag1", &queryKey5);
+ EXPECT_EQ(WakelockStateChanged::ACQUIRE,
+ getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey5));
}
} // namespace statsd
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 7b651df..e0aecce 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -617,19 +617,6 @@
void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
EXPECT_EQ(value.field(), atomId);
- // Attribution field.
- EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
- // Uid only.
- EXPECT_EQ(value.value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value_size(), 1);
- EXPECT_EQ(value.value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value(0).field(), 1);
- EXPECT_EQ(value.value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value(0).value_int(), uid);
-}
-
-void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) {
- EXPECT_EQ(value.field(), atomId);
EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
// Attribution field.
EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 4d0acb3..4aaf727 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -22519,8 +22519,6 @@
HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhones(Landroid/content/Context;)V
HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;-><init>(Ljava/lang/String;I)V
HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;->values()[Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;-><init>(Landroid/content/Context;Landroid/os/Handler;)V
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;->notifyServiceState(I)V
HSPLcom/android/internal/telephony/PhoneSubInfoController;->callPhoneMethodWithPermissionCheck(ILjava/lang/String;Ljava/lang/String;Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;)Ljava/lang/Object;
HSPLcom/android/internal/telephony/PhoneSubInfoController;->getCarrierInfoForImsiEncryption(IILjava/lang/String;)Landroid/telephony/ImsiEncryptionInfo;
HSPLcom/android/internal/telephony/PhoneSubInfoController;->getGroupIdLevel1ForSubscriber(ILjava/lang/String;)Ljava/lang/String;
@@ -37729,7 +37727,6 @@
Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
Lcom/android/internal/telephony/PhoneInternalInterface;
Lcom/android/internal/telephony/PhoneNotifier;
-Lcom/android/internal/telephony/PhoneStateIntentReceiver;
Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;
Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;
Lcom/android/internal/telephony/PhoneSubInfoController;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 97f009c..e53c74b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -4841,7 +4841,6 @@
com.android.internal.telephony.PhoneFactory
com.android.internal.telephony.PhoneInternalInterface
com.android.internal.telephony.PhoneNotifier
-com.android.internal.telephony.PhoneStateIntentReceiver
com.android.internal.telephony.PhoneSubInfoController$CallPhoneMethodHelper
com.android.internal.telephony.PhoneSubInfoController$PermissionCheckHelper
com.android.internal.telephony.PhoneSubInfoController
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index c0fee6e..0bd8ce6 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,6 +17,7 @@
package android.accessibilityservice;
import android.accessibilityservice.GestureDescription.MotionEventGenerator;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,12 +27,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.graphics.Region;
+import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -48,10 +52,13 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Accessibility services should only be used to assist users with disabilities in using
@@ -483,6 +490,9 @@
private FingerprintGestureController mFingerprintGestureController;
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT = "screenshot";
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -1547,6 +1557,34 @@
void onShowModeChanged(@NonNull SoftKeyboardController controller,
@SoftKeyboardShowMode int showMode);
}
+
+ /**
+ * Switches the current IME for the user for whom the service is enabled. The change will
+ * persist until the current IME is explicitly changed again, and may persist beyond the
+ * life cycle of the requesting service.
+ *
+ * @param imeId The ID of the input method to make current. This IME must be installed and
+ * enabled.
+ * @return {@code true} if the current input method was successfully switched to the input
+ * method by {@code imeId},
+ * {@code false} if the input method specified is not installed, not enabled, or
+ * otherwise not available to become the current IME
+ *
+ * @see android.view.inputmethod.InputMethodInfo#getId()
+ */
+ public boolean switchToInputMethod(@NonNull String imeId) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.switchToInputMethod(imeId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ return false;
+ }
}
/**
@@ -1733,6 +1771,51 @@
}
/**
+ * Takes a screenshot of the specified display and returns it by {@link Bitmap.Config#HARDWARE}
+ * format.
+ * <p>
+ * <strong>Note:</strong> In order to take screenshot your service has
+ * to declare the capability to take screenshot by setting the
+ * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * Besides, This API is only supported for default display now
+ * {@link Display#DEFAULT_DISPLAY}.
+ * </p>
+ *
+ * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when the taking screenshot is done.
+ *
+ * @return {@code true} if the taking screenshot accepted, {@code false} if not.
+ */
+ public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Bitmap> callback) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mConnectionId);
+ if (connection == null) {
+ return false;
+ }
+ try {
+ connection.takeScreenshotWithCallback(displayId, new RemoteCallback((result) -> {
+ final Bitmap screenshot = result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.accept(screenshot));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }));
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ return true;
+ }
+
+ /**
* Implement to return the implementation of the internal accessibility
* service interface.
*/
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 5e2c1fa..12f2c3b 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -86,6 +86,7 @@
* @attr ref android.R.styleable#AccessibilityService_settingsActivity
* @attr ref android.R.styleable#AccessibilityService_nonInteractiveUiTimeout
* @attr ref android.R.styleable#AccessibilityService_interactiveUiTimeout
+ * @attr ref android.R.styleable#AccessibilityService_canTakeScreenshot
* @see AccessibilityService
* @see android.view.accessibility.AccessibilityEvent
* @see android.view.accessibility.AccessibilityManager
@@ -136,6 +137,12 @@
*/
public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 0x00000040;
+ /**
+ * Capability: This accessibility service can take screenshot.
+ * @see android.R.styleable#AccessibilityService_canTakeScreenshot
+ */
+ public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 0x00000080;
+
private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos;
/**
@@ -625,6 +632,10 @@
.AccessibilityService_canRequestFingerprintGestures, false)) {
mCapabilities |= CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES;
}
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canTakeScreenshot, false)) {
+ mCapabilities |= CAPABILITY_CAN_TAKE_SCREENSHOT;
+ }
TypedValue peekedValue = asAttributes.peekValue(
com.android.internal.R.styleable.AccessibilityService_description);
if (peekedValue != null) {
@@ -794,6 +805,7 @@
* @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
* @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
* @see #CAPABILITY_CAN_PERFORM_GESTURES
+ * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
*/
public int getCapabilities() {
return mCapabilities;
@@ -810,6 +822,7 @@
* @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
* @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
* @see #CAPABILITY_CAN_PERFORM_GESTURES
+ * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
*
* @hide
*/
@@ -1253,6 +1266,8 @@
return "CAPABILITY_CAN_PERFORM_GESTURES";
case CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES:
return "CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES";
+ case CAPABILITY_CAN_TAKE_SCREENSHOT:
+ return "CAPABILITY_CAN_TAKE_SCREENSHOT";
default:
return "UNKNOWN";
}
@@ -1314,6 +1329,10 @@
new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
R.string.capability_title_canPerformGestures,
R.string.capability_desc_canPerformGestures));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_TAKE_SCREENSHOT,
+ new CapabilityInfo(CAPABILITY_CAN_TAKE_SCREENSHOT,
+ R.string.capability_title_canTakeScreenshot,
+ R.string.capability_desc_canTakeScreenshot));
if ((context == null) || fingerprintAvailable(context)) {
sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
new CapabilityInfo(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 4841781..4ea5c62 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,8 +18,10 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.graphics.Region;
import android.os.Bundle;
+import android.os.RemoteCallback;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -91,6 +93,8 @@
void setSoftKeyboardCallbackEnabled(boolean enabled);
+ boolean switchToInputMethod(String imeId);
+
boolean isAccessibilityButtonAvailable();
void sendGesture(int sequence, in ParceledListSlice gestureSteps);
@@ -100,4 +104,10 @@
boolean isFingerprintGestureDetectionAvailable();
IBinder getOverlayWindowToken(int displayid);
+
+ int getWindowIdForLeashToken(IBinder token);
+
+ Bitmap takeScreenshot(int displayId);
+
+ void takeScreenshotWithCallback(int displayId, in RemoteCallback callback);
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 8fe2f12..f2702a8 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -25,6 +25,7 @@
import android.annotation.Size;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
import android.app.Activity;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
@@ -40,6 +41,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -528,12 +530,9 @@
* authenticator known to the AccountManager service. Empty (never
* null) if no authenticators are known.
*/
+ @UserHandleAware
public AuthenticatorDescription[] getAuthenticatorTypes() {
- try {
- return mService.getAuthenticatorTypes(UserHandle.getCallingUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getAuthenticatorTypesAsUser(mContext.getUserId());
}
/**
@@ -584,13 +583,10 @@
* @return An array of {@link Account}, one for each account. Empty (never null) if no accounts
* have been added.
*/
+ @UserHandleAware
@NonNull
public Account[] getAccounts() {
- try {
- return mService.getAccounts(null, mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getAccountsAsUser(mContext.getUserId());
}
/**
@@ -708,6 +704,7 @@
* @return An array of {@link Account}, one per matching account. Empty (never null) if no
* accounts of the specified type have been added.
*/
+ @UserHandleAware
@NonNull
public Account[] getAccountsByType(String type) {
return getAccountsByTypeAsUser(type, mContext.getUser());
@@ -1183,23 +1180,11 @@
* {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)}
* instead
*/
+ @UserHandleAware
@Deprecated
public AccountManagerFuture<Boolean> removeAccount(final Account account,
AccountManagerCallback<Boolean> callback, Handler handler) {
- if (account == null) throw new IllegalArgumentException("account is null");
- return new Future2Task<Boolean>(handler, callback) {
- @Override
- public void doWork() throws RemoteException {
- mService.removeAccount(mResponse, account, false);
- }
- @Override
- public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
- if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
- throw new AuthenticatorException("no result in response");
- }
- return bundle.getBoolean(KEY_BOOLEAN_RESULT);
- }
- }.start();
+ return removeAccountAsUser(account, callback, handler, mContext.getUser());
}
/**
@@ -1243,15 +1228,10 @@
* adding accounts (of this type) has been disabled by policy
* </ul>
*/
+ @UserHandleAware
public AccountManagerFuture<Bundle> removeAccount(final Account account,
final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
- if (account == null) throw new IllegalArgumentException("account is null");
- return new AmsTask(activity, handler, callback) {
- @Override
- public void doWork() throws RemoteException {
- mService.removeAccount(mResponse, account, activity != null);
- }
- }.start();
+ return removeAccountAsUser(account, activity, callback, handler, mContext.getUser());
}
/**
@@ -1841,24 +1821,30 @@
* creating a new account, usually because of network trouble
* </ul>
*/
+ @UserHandleAware
public AccountManagerFuture<Bundle> addAccount(final String accountType,
final String authTokenType, final String[] requiredFeatures,
final Bundle addAccountOptions,
final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- final Bundle optionsIn = new Bundle();
- if (addAccountOptions != null) {
- optionsIn.putAll(addAccountOptions);
- }
- optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
-
- return new AmsTask(activity, handler, callback) {
- @Override
- public void doWork() throws RemoteException {
- mService.addAccount(mResponse, accountType, authTokenType,
- requiredFeatures, activity != null, optionsIn);
+ if (Process.myUserHandle().equals(mContext.getUser())) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ final Bundle optionsIn = new Bundle();
+ if (addAccountOptions != null) {
+ optionsIn.putAll(addAccountOptions);
}
- }.start();
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.addAccount(mResponse, accountType, authTokenType,
+ requiredFeatures, activity != null, optionsIn);
+ }
+ }.start();
+ } else {
+ return addAccountAsUser(accountType, authTokenType, requiredFeatures, addAccountOptions,
+ activity, callback, handler, mContext.getUser());
+ }
}
/**
@@ -2002,6 +1988,7 @@
* verifying the password, usually because of network trouble
* </ul>
*/
+ @UserHandleAware
public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
final Bundle options,
final Activity activity,
@@ -3209,6 +3196,7 @@
* </ul>
* @see #startAddAccountSession and #startUpdateCredentialsSession
*/
+ @UserHandleAware
public AccountManagerFuture<Bundle> finishSession(
final Bundle sessionBundle,
final Activity activity,
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 0127138..ce68e08 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -34,7 +34,6 @@
String getPassword(in Account account);
String getUserData(in Account account, String key);
AuthenticatorDescription[] getAuthenticatorTypes(int userId);
- Account[] getAccounts(String accountType, String opPackageName);
Account[] getAccountsForPackage(String packageName, int uid, String opPackageName);
Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName);
Account[] getAccountsAsUser(String accountType, int userId, String opPackageName);
@@ -45,8 +44,6 @@
void getAccountsByFeatures(in IAccountManagerResponse response, String accountType,
in String[] features, String opPackageName);
boolean addAccountExplicitly(in Account account, String password, in Bundle extras);
- void removeAccount(in IAccountManagerResponse response, in Account account,
- boolean expectActivityLaunch);
void removeAccountAsUser(in IAccountManagerResponse response, in Account account,
boolean expectActivityLaunch, int userId);
boolean removeAccountExplicitly(in Account account);
diff --git a/core/java/android/annotation/SystemService.java b/core/java/android/annotation/SystemService.java
index 0c5d15e..c05c1ba 100644
--- a/core/java/android/annotation/SystemService.java
+++ b/core/java/android/annotation/SystemService.java
@@ -19,14 +19,12 @@
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.content.Context;
-
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Description of a system service available through
- * {@link Context#getSystemService(Class)}. This is used to auto-generate
+ * {@link android.content.Context#getSystemService(Class)}. This is used to auto-generate
* documentation explaining how to obtain a reference to the service.
*
* @hide
@@ -36,9 +34,9 @@
public @interface SystemService {
/**
* The string name of the system service that can be passed to
- * {@link Context#getSystemService(String)}.
+ * {@link android.content.Context#getSystemService(String)}.
*
- * @see Context#getSystemServiceName(Class)
+ * @see android.content.Context#getSystemServiceName(Class)
*/
String value();
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 9aef20b..c3b07c8 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4073,6 +4073,29 @@
}
/**
+ * Updates mcc mnc configuration and applies changes to the entire system.
+ *
+ * @param mcc mcc configuration to update.
+ * @param mnc mnc configuration to update.
+ * @throws RemoteException; IllegalArgumentException if mcc or mnc is null;
+ * @return Returns {@code true} if the configuration was updated successfully;
+ * {@code false} otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION)
+ public boolean updateMccMncConfiguration(@NonNull String mcc, @NonNull String mnc) {
+ if (mcc == null || mnc == null) {
+ throw new IllegalArgumentException("mcc or mnc cannot be null.");
+ }
+ try {
+ return getService().updateMccMncConfiguration(mcc, mnc);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Logs out current current foreground user by switching to the system user and stopping the
* user being switched from.
* @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 032e824..4f3e8ec 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -45,8 +45,19 @@
// Access modes for handleIncomingUser.
public static final int ALLOW_NON_FULL = 0;
+ /**
+ * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
+ * if in the same profile group.
+ * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+ */
public static final int ALLOW_NON_FULL_IN_PROFILE = 1;
public static final int ALLOW_FULL_ONLY = 2;
+ /**
+ * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
+ * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group.
+ * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+ */
+ public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3;
/**
* Verify that calling app has access to the given provider.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4a8e4e2..a11f41f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -726,7 +726,17 @@
/** @hide Capture the device's display contents and/or audio */
@UnsupportedAppUsage
public static final int OP_PROJECT_MEDIA = 46;
- /** @hide Activate a VPN connection without user intervention. */
+ /**
+ * Start (without additional user intervention) a VPN connection, as used by {@link
+ * android.net.VpnService} along with as Platform VPN connections, as used by {@link
+ * android.net.VpnManager}
+ *
+ * <p>This appop is granted to apps that have already been given user consent to start
+ * VpnService based VPN connections. As this is a superset of OP_ACTIVATE_PLATFORM_VPN, this
+ * appop also allows the starting of Platform VPNs.
+ *
+ * @hide
+ */
@UnsupportedAppUsage
public static final int OP_ACTIVATE_VPN = 47;
/** @hide Access the WallpaperManagerAPI to write wallpapers. */
@@ -852,10 +862,21 @@
public static final int OP_MANAGE_EXTERNAL_STORAGE = 92;
/** @hide Communicate cross-profile within the same profile group. */
public static final int OP_INTERACT_ACROSS_PROFILES = 93;
+ /**
+ * Start (without additional user intervention) a Platform VPN connection, as used by {@link
+ * android.net.VpnManager}
+ *
+ * <p>This appop is granted to apps that have already been given user consent to start Platform
+ * VPN connections. This appop is insufficient to start VpnService based VPNs; OP_ACTIVATE_VPN
+ * is needed for that.
+ *
+ * @hide
+ */
+ public static final int OP_ACTIVATE_PLATFORM_VPN = 94;
/** @hide */
@UnsupportedAppUsage
- public static final int _NUM_OP = 94;
+ public static final int _NUM_OP = 95;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1149,6 +1170,8 @@
/** @hide Communicate cross-profile within the same profile group. */
@SystemApi
public static final String OPSTR_INTERACT_ACROSS_PROFILES = "android:interact_across_profiles";
+ /** @hide Start Platform VPN without user intervention */
+ public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn";
/** {@link #sAppOpsToNote} not initialized yet for this op */
@@ -1333,6 +1356,7 @@
OP_QUERY_ALL_PACKAGES, // QUERY_ALL_PACKAGES
OP_MANAGE_EXTERNAL_STORAGE, // MANAGE_EXTERNAL_STORAGE
OP_INTERACT_ACROSS_PROFILES, //INTERACT_ACROSS_PROFILES
+ OP_ACTIVATE_PLATFORM_VPN, // ACTIVATE_PLATFORM_VPN
};
/**
@@ -1433,6 +1457,7 @@
OPSTR_QUERY_ALL_PACKAGES,
OPSTR_MANAGE_EXTERNAL_STORAGE,
OPSTR_INTERACT_ACROSS_PROFILES,
+ OPSTR_ACTIVATE_PLATFORM_VPN,
};
/**
@@ -1533,7 +1558,8 @@
"ACCESS_MEDIA_LOCATION",
"QUERY_ALL_PACKAGES",
"MANAGE_EXTERNAL_STORAGE",
- "INTERACT_ACROSS_PROFILES"
+ "INTERACT_ACROSS_PROFILES",
+ "ACTIVATE_PLATFORM_VPN",
};
/**
@@ -1636,6 +1662,7 @@
null, // no permission for OP_QUERY_ALL_PACKAGES
Manifest.permission.MANAGE_EXTERNAL_STORAGE,
android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+ null, // no permission for OP_ACTIVATE_PLATFORM_VPN
};
/**
@@ -1738,6 +1765,7 @@
null, // QUERY_ALL_PACKAGES
null, // MANAGE_EXTERNAL_STORAGE
null, // INTERACT_ACROSS_PROFILES
+ null, // ACTIVATE_PLATFORM_VPN
};
/**
@@ -1839,6 +1867,7 @@
false, // QUERY_ALL_PACKAGES
false, // MANAGE_EXTERNAL_STORAGE
false, // INTERACT_ACROSS_PROFILES
+ false, // ACTIVATE_PLATFORM_VPN
};
/**
@@ -1939,6 +1968,7 @@
AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES
AppOpsManager.MODE_DEFAULT, // MANAGE_EXTERNAL_STORAGE
AppOpsManager.MODE_DEFAULT, // INTERACT_ACROSS_PROFILES
+ AppOpsManager.MODE_IGNORED, // ACTIVATE_PLATFORM_VPN
};
/**
@@ -2043,6 +2073,7 @@
false, // QUERY_ALL_PACKAGES
false, // MANAGE_EXTERNAL_STORAGE
false, // INTERACT_ACROSS_PROFILES
+ false, // ACTIVATE_PLATFORM_VPN
};
/**
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 1c6a561..d39a8c4 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -133,6 +133,10 @@
// Default flags to use with PackageManager when no flags are given.
private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES;
+ // Name of the resource which provides background permission button string
+ public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS =
+ "app_permission_button_allow_always";
+
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -807,6 +811,26 @@
}
@Override
+ public CharSequence getBackgroundPermissionButtonLabel() {
+ try {
+
+ String permissionController = getPermissionControllerPackageName();
+ Context context =
+ mContext.createPackageContext(permissionController, 0);
+
+ int textId = context.getResources().getIdentifier(APP_PERMISSION_BUTTON_ALLOW_ALWAYS,
+ "string", "com.android.permissioncontroller");
+// permissionController); STOPSHIP b/147434671
+ if (textId != 0) {
+ return context.getText(textId);
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Permission controller not found.", e);
+ }
+ return "";
+ }
+
+ @Override
public int checkSignatures(String pkg1, String pkg2) {
try {
return mPM.checkSignatures(pkg1, pkg2);
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index d993ec1..3febf71 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -256,10 +256,10 @@
};
@DataClass.Generated(
- time = 1578321462996L,
+ time = 1578516519372L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
- inputSignatures = "private final @android.annotation.IntRange(from=0L, to=93L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+ inputSignatures = "private final @android.annotation.IntRange(from=0L, to=94L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 5185941..1f323c3 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -41,8 +41,7 @@
// Don't need to worry about synchronization
private static DisabledWallpaperManager sInstance;
- // TODO(b/138939803): STOPSHIP changed to false and/or remove it
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
@NonNull
static DisabledWallpaperManager getInstance() {
@@ -53,7 +52,6 @@
}
private DisabledWallpaperManager() {
- super(null, null, null);
}
@Override
@@ -66,10 +64,6 @@
return false;
}
- // TODO(b/138939803): STOPSHIP methods below should not be necessary,
- // callers should check if isWallpaperSupported(), consider removing them to keep this class
- // simpler
-
private static <T> T unsupported() {
if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
return null;
@@ -343,4 +337,9 @@
public boolean isWallpaperBackupEligible(int which) {
return unsupportedBoolean();
}
+
+ @Override
+ public boolean wallpaperSupportsWcg(int which) {
+ return unsupportedBoolean();
+ }
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e8494c4..580382e 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -188,6 +188,16 @@
*/
@UnsupportedAppUsage
boolean updateConfiguration(in Configuration values);
+ /**
+ * Updates mcc mnc configuration and applies changes to the entire system.
+ *
+ * @param mcc mcc configuration to update.
+ * @param mnc mnc configuration to update.
+ * @throws RemoteException; IllegalArgumentException if mcc or mnc is null.
+ * @return Returns {@code true} if the configuration was updated;
+ * {@code false} otherwise.
+ */
+ boolean updateMccMncConfiguration(in String mcc, in String mnc);
boolean stopServiceToken(in ComponentName className, in IBinder token, int startId);
@UnsupportedAppUsage
void setProcessLimit(int max);
@@ -345,6 +355,12 @@
in Bundle options, int userId);
@UnsupportedAppUsage
int stopUser(int userid, boolean force, in IStopUserCallback callback);
+ /**
+ * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)}
+ * for details.
+ */
+ int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback);
+
@UnsupportedAppUsage
void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name);
void unregisterUserSwitchObserver(in IUserSwitchObserver observer);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 700b3c1..e5c046c 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -68,6 +68,7 @@
import android.os.WorkSource;
import android.service.voice.IVoiceInteractionSession;
import android.view.IRecentsAnimationRunner;
+import android.view.ITaskOrganizer;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationAdapter;
import android.view.WindowContainerTransaction;
@@ -121,6 +122,9 @@
in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options,
IBinder permissionToken, boolean ignoreTargetSecurity, int userId);
+
+ void registerTaskOrganizer(in ITaskOrganizer organizer, int windowingMode);
+
boolean isActivityStartAllowedOnDisplay(int displayId, in Intent intent, in String resolvedType,
int userId);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 86f52af..7af7a4a 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -94,8 +94,11 @@
void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group);
void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId);
+ NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, boolean returnParentIfNoConversationChannel, String conversationId);
+ void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId);
NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, String channelId);
+ void deleteConversationNotificationChannels(String pkg, int uid, String conversationId);
ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId);
ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 8c3180b..80ba464 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -39,7 +39,6 @@
boolean injectInputEvent(in InputEvent event, boolean sync);
void syncInputTransactions();
boolean setRotation(int rotation);
- Bitmap takeScreenshot(in Rect crop, int rotation);
boolean clearWindowContentFrameStats(int windowId);
WindowContentFrameStats getWindowContentFrameStats(int windowId);
void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 775b1d1..70262b0 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -251,7 +251,7 @@
}
private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) {
- if (appInfo.appComponentFactory != null && cl != null) {
+ if (mIncludeCode && appInfo.appComponentFactory != null && cl != null) {
try {
return (AppComponentFactory)
cl.loadClass(appInfo.appComponentFactory).newInstance();
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 3eee1ae..bdc7b99 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -23,6 +23,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ShortcutInfo;
import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Parcel;
@@ -30,6 +31,7 @@
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
@@ -57,6 +59,22 @@
public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
/**
+ * The formatter used by the system to create an id for notification
+ * channels when it automatically creates conversation channels on behalf of an app. The format
+ * string takes two arguments, in this order: the
+ * {@link #getId()} of the original notification channel, and the
+ * {@link ShortcutInfo#getId() id} of the conversation.
+ */
+ public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
+
+ /**
+ * TODO: STOPSHIP remove
+ * Conversation id to use for apps that aren't providing them yet.
+ * @hide
+ */
+ public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id";
+
+ /**
* The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
* limit.
*/
@@ -85,6 +103,8 @@
private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
private static final String ATT_ALLOW_BUBBLE = "can_bubble";
private static final String ATT_ORIG_IMP = "orig_imp";
+ private static final String ATT_PARENT_CHANNEL = "parent";
+ private static final String ATT_CONVERSATION_ID = "conv_id";
private static final String DELIMITER = ",";
/**
@@ -147,7 +167,7 @@
private static final boolean DEFAULT_ALLOW_BUBBLE = true;
@UnsupportedAppUsage
- private final String mId;
+ private String mId;
private String mName;
private String mDesc;
private int mImportance = DEFAULT_IMPORTANCE;
@@ -172,6 +192,8 @@
private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
private boolean mImportanceLockedByOEM;
private boolean mImportanceLockedDefaultApp;
+ private String mParentId = null;
+ private String mConversationId = null;
/**
* Creates a notification channel.
@@ -236,6 +258,8 @@
mAllowBubbles = in.readBoolean();
mImportanceLockedByOEM = in.readBoolean();
mOriginalImportance = in.readInt();
+ mParentId = in.readString();
+ mConversationId = in.readString();
}
@Override
@@ -291,6 +315,8 @@
dest.writeBoolean(mAllowBubbles);
dest.writeBoolean(mImportanceLockedByOEM);
dest.writeInt(mOriginalImportance);
+ dest.writeString(mParentId);
+ dest.writeString(mConversationId);
}
/**
@@ -363,6 +389,13 @@
// Modifiable by apps on channel creation.
/**
+ * @hide
+ */
+ public void setId(String id) {
+ mId = id;
+ }
+
+ /**
* Sets what group this channel belongs to.
*
* Group information is only used for presentation, not for behavior.
@@ -502,6 +535,23 @@
}
/**
+ * Sets this channel as being person-centric. Different settings and functionality may be
+ * exposed for people-centric channels.
+ *
+ * @param parentChannelId The {@link #getId()} id} of the generic channel that notifications of
+ * this type would be posted to in absence of a specific conversation id.
+ * For example, if this channel represents 'Messages from Person A', the
+ * parent channel would be 'Messages.'
+ * @param conversationId The {@link ShortcutInfo#getId()} of the shortcut representing this
+ * channel's conversation.
+ */
+ public void setConversationId(@Nullable String parentChannelId,
+ @Nullable String conversationId) {
+ mParentId = parentChannelId;
+ mConversationId = conversationId;
+ }
+
+ /**
* Returns the id of this channel.
*/
public String getId() {
@@ -622,6 +672,22 @@
}
/**
+ * Returns the {@link #getId() id} of the parent notification channel to this channel, if it's
+ * a conversation related channel. See {@link #setConversationId(String, String)}.
+ */
+ public @Nullable String getParentChannelId() {
+ return mParentId;
+ }
+
+ /**
+ * Returns the {@link ShortcutInfo#getId() id} of the conversation backing this channel, if it's
+ * associated with a conversation. See {@link #setConversationId(String, String)}.
+ */
+ public @Nullable String getConversationId() {
+ return mConversationId;
+ }
+
+ /**
* @hide
*/
@SystemApi
@@ -761,6 +827,8 @@
setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
+ setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL),
+ parser.getAttributeValue(null, ATT_CONVERSATION_ID));
}
@Nullable
@@ -885,6 +953,12 @@
if (getOriginalImportance() != DEFAULT_IMPORTANCE) {
out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance()));
}
+ if (getParentChannelId() != null) {
+ out.attribute(null, ATT_PARENT_CHANNEL, getParentChannelId());
+ }
+ if (getConversationId() != null) {
+ out.attribute(null, ATT_CONVERSATION_ID, getConversationId());
+ }
// mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of
// truth and so aren't written to this xml file
@@ -1042,7 +1116,9 @@
&& Objects.equals(getAudioAttributes(), that.getAudioAttributes())
&& mImportanceLockedByOEM == that.mImportanceLockedByOEM
&& mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp
- && mOriginalImportance == that.mOriginalImportance;
+ && mOriginalImportance == that.mOriginalImportance
+ && Objects.equals(getParentChannelId(), that.getParentChannelId())
+ && Objects.equals(getConversationId(), that.getConversationId());
}
@Override
@@ -1052,7 +1128,8 @@
getUserLockedFields(),
isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
- mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance);
+ mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance,
+ mParentId, mConversationId);
result = 31 * result + Arrays.hashCode(mVibration);
return result;
}
@@ -1063,26 +1140,7 @@
String output = "NotificationChannel{"
+ "mId='" + mId + '\''
+ ", mName=" + redactedName
- + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
- + ", mImportance=" + mImportance
- + ", mBypassDnd=" + mBypassDnd
- + ", mLockscreenVisibility=" + mLockscreenVisibility
- + ", mSound=" + mSound
- + ", mLights=" + mLights
- + ", mLightColor=" + mLightColor
- + ", mVibration=" + Arrays.toString(mVibration)
- + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
- + ", mFgServiceShown=" + mFgServiceShown
- + ", mVibrationEnabled=" + mVibrationEnabled
- + ", mShowBadge=" + mShowBadge
- + ", mDeleted=" + mDeleted
- + ", mGroup='" + mGroup + '\''
- + ", mAudioAttributes=" + mAudioAttributes
- + ", mBlockableSystem=" + mBlockableSystem
- + ", mAllowBubbles=" + mAllowBubbles
- + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
- + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
- + ", mOriginalImp=" + mOriginalImportance
+ + getFieldsString()
+ '}';
pw.println(prefix + output);
}
@@ -1092,7 +1150,12 @@
return "NotificationChannel{"
+ "mId='" + mId + '\''
+ ", mName=" + mName
- + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+ + getFieldsString()
+ + '}';
+ }
+
+ private String getFieldsString() {
+ return ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+ ", mImportance=" + mImportance
+ ", mBypassDnd=" + mBypassDnd
+ ", mLockscreenVisibility=" + mLockscreenVisibility
@@ -1112,7 +1175,8 @@
+ ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
+ ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
+ ", mOriginalImp=" + mOriginalImportance
- + '}';
+ + ", mParent=" + mParentId
+ + ", mConversationId=" + mConversationId;
}
/** @hide */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index fdbb8bb..1a8e15c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -843,6 +843,26 @@
}
/**
+ * Returns the notification channel settings for a given channel and conversation id.
+ *
+ * <p>The channel must belong to your package, or to a package you are an approved notification
+ * delegate for (see {@link #canNotifyAsPackage(String)}), or it will not be returned. To query
+ * a channel as a notification delegate, call this method from a context created for that
+ * package (see {@link Context#createPackageContext(String, int)}).</p>
+ */
+ public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId,
+ @NonNull String conversationId) {
+ INotificationManager service = getService();
+ try {
+ return service.getConversationNotificationChannel(mContext.getOpPackageName(),
+ mContext.getUserId(), mContext.getPackageName(), channelId, true,
+ conversationId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns all notification channels belonging to the calling package.
*
* <p>Approved notification delegates (see {@link #canNotifyAsPackage(String)}) can query
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 844e72e..736efb6 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -15,6 +15,7 @@
*/
package android.app;
+
import android.annotation.NonNull;
import android.os.SystemProperties;
import android.util.Log;
@@ -23,6 +24,7 @@
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
@@ -164,6 +166,7 @@
private static final String TAG = "PropertyInvalidatedCache";
private static final boolean DEBUG = false;
private static final boolean ENABLE = true;
+ private static final boolean VERIFY = false;
private final Object mLock = new Object();
@@ -228,6 +231,18 @@
protected abstract Result recompute(Query query);
/**
+ * Determines if a pair of responses are considered equal. Used to determine whether
+ * a cache is inadvertently returning stale results when VERIFY is set to true.
+ */
+ protected boolean debugCompareQueryResults(Result cachedResult, Result fetchedResult) {
+ // If a service crashes and returns a null result, the cached value remains valid.
+ if (fetchedResult != null) {
+ return Objects.equals(cachedResult, fetchedResult);
+ }
+ return true;
+ }
+
+ /**
* Make result up-to-date on a cache hit. Called unlocked;
* may block.
*
@@ -334,12 +349,12 @@
mCache.put(query, refreshedResult);
}
}
- return refreshedResult;
+ return maybeCheckConsistency(query, refreshedResult);
}
if (DEBUG) {
Log.d(TAG, "cache hit for " + query);
}
- return cachedResult;
+ return maybeCheckConsistency(query, cachedResult);
}
// Cache miss: make the value from scratch.
if (DEBUG) {
@@ -353,7 +368,7 @@
mCache.put(query, result);
}
}
- return result;
+ return maybeCheckConsistency(query, result);
}
}
@@ -425,4 +440,15 @@
}
SystemProperties.set(name, newValueString);
}
+
+ private Result maybeCheckConsistency(Query query, Result proposedResult) {
+ if (VERIFY) {
+ Result resultToCompare = recompute(query);
+ boolean nonceChanged = (getCurrentNonce() != mLastSeenNonce);
+ if (!nonceChanged && !debugCompareQueryResults(proposedResult, resultToCompare)) {
+ throw new AssertionError("cache returned out of date response for " + query);
+ }
+ }
+ return proposedResult;
+ }
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index aa11598..d23754e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -482,19 +482,6 @@
}
}
- if (key.mOverlayDirs != null) {
- for (final String idmapPath : key.mOverlayDirs) {
- try {
- builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
- true /*overlay*/));
- } catch (IOException e) {
- Log.w(TAG, "failed to add overlay path " + idmapPath);
-
- // continue.
- }
- }
- }
-
if (key.mLibDirs != null) {
for (final String libDir : key.mLibDirs) {
if (libDir.endsWith(".apk")) {
@@ -513,6 +500,19 @@
}
}
+ if (key.mOverlayDirs != null) {
+ for (final String idmapPath : key.mOverlayDirs) {
+ try {
+ builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
+ true /*overlay*/));
+ } catch (IOException e) {
+ Log.w(TAG, "failed to add overlay path " + idmapPath);
+
+ // continue.
+ }
+ }
+ }
+
return builder.build();
}
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 8426374..dde6dda 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -26,7 +26,6 @@
import android.os.IBinder;
import android.os.IPullAtomCallback;
import android.os.IPullAtomResultReceiver;
-import android.os.IStatsCompanionService;
import android.os.IStatsManagerService;
import android.os.IStatsPullerCallback;
import android.os.IStatsd;
@@ -61,9 +60,6 @@
private IStatsd mService;
@GuardedBy("sLock")
- private IStatsCompanionService mStatsCompanion;
-
- @GuardedBy("sLock")
private IStatsManagerService mStatsManagerService;
/**
@@ -538,7 +534,7 @@
}
synchronized (sLock) {
try {
- IStatsCompanionService service = getIStatsCompanionServiceLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
PullAtomCallbackInternal rec =
new PullAtomCallbackInternal(atomTag, callback, executor);
service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields,
@@ -560,7 +556,7 @@
public void unregisterPullAtomCallback(int atomTag) {
synchronized (sLock) {
try {
- IStatsCompanionService service = getIStatsCompanionServiceLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
service.unregisterPullAtomCallback(atomTag);
} catch (RemoteException e) {
throw new RuntimeException("Unable to unregister pull atom callback");
@@ -746,16 +742,6 @@
}
@GuardedBy("sLock")
- private IStatsCompanionService getIStatsCompanionServiceLocked() {
- if (mStatsCompanion != null) {
- return mStatsCompanion;
- }
- mStatsCompanion = IStatsCompanionService.Stub.asInterface(
- ServiceManager.getService("statscompanion"));
- return mStatsCompanion;
- }
-
- @GuardedBy("sLock")
private IStatsManagerService getIStatsManagerServiceLocked() {
if (mStatsManagerService != null) {
return mStatsManagerService;
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 078e453..42563b5 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -267,6 +267,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public void expandNotificationsPanel() {
try {
final IStatusBarService svc = getService();
@@ -284,6 +285,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public void collapsePanels() {
try {
final IStatusBarService svc = getService();
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ca3d0d7..7b21f12 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -54,6 +54,7 @@
import android.content.integrity.IAppIntegrityManager;
import android.content.om.IOverlayManager;
import android.content.om.OverlayManager;
+import android.content.pm.ApplicationInfo;
import android.content.pm.CrossProfileApps;
import android.content.pm.DataLoaderManager;
import android.content.pm.ICrossProfileApps;
@@ -170,6 +171,7 @@
import android.service.persistentdata.PersistentDataBlockManager;
import android.service.vr.IVrManager;
import android.telecom.TelecomManager;
+import android.telephony.MmsManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyRegistryManager;
import android.util.ArrayMap;
@@ -345,6 +347,14 @@
}
});
+ registerService(Context.NETWORK_STACK_SERVICE, IBinder.class,
+ new StaticServiceFetcher<IBinder>() {
+ @Override
+ public IBinder createService() {
+ return ServiceManager.getService(Context.NETWORK_STACK_SERVICE);
+ }
+ });
+
registerService(Context.TETHERING_SERVICE, TetheringManager.class,
new CachedServiceFetcher<TetheringManager>() {
@Override
@@ -631,6 +641,13 @@
return new TelecomManager(ctx.getOuterContext());
}});
+ registerService(Context.MMS_SERVICE, MmsManager.class,
+ new CachedServiceFetcher<MmsManager>() {
+ @Override
+ public MmsManager createService(ContextImpl ctx) {
+ return new MmsManager(ctx.getOuterContext());
+ }});
+
registerService(Context.UI_MODE_SERVICE, UiModeManager.class,
new CachedServiceFetcher<UiModeManager>() {
@Override
@@ -677,20 +694,20 @@
throws ServiceNotFoundException {
final IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
if (b == null) {
- // There are 2 reason service can be null:
- // 1.Device doesn't support it - that's fine
- // 2.App is running on instant mode - should fail
- final boolean enabled = Resources.getSystem()
- .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
- if (!enabled) {
- // Life moves on...
- return DisabledWallpaperManager.getInstance();
- }
- if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+ ApplicationInfo appInfo = ctx.getApplicationInfo();
+ if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P
+ && appInfo.isInstantApp()) {
// Instant app
throw new ServiceNotFoundException(Context.WALLPAPER_SERVICE);
}
+ final boolean enabled = Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
+ if (!enabled) {
+ // Device doesn't support wallpaper, return a limited manager
+ return DisabledWallpaperManager.getInstance();
+ }
// Bad state - WallpaperManager methods will throw exception
+ Log.e(TAG, "No wallpaper service");
}
IWallpaperManager service = IWallpaperManager.Stub.asInterface(b);
return new WallpaperManager(service, ctx.getOuterContext(),
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 18a3e6e..2579bd1 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -27,10 +27,7 @@
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
import android.graphics.Region;
-import android.hardware.display.DisplayManagerGlobal;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
@@ -831,39 +828,20 @@
}
/**
- * Takes a screenshot.
+ * Takes a screenshot of the default display and returns it by {@link Bitmap.Config#HARDWARE}
+ * format.
*
* @return The screenshot bitmap on success, null otherwise.
*/
public Bitmap takeScreenshot() {
+ final int connectionId;
synchronized (mLock) {
throwIfNotConnectedLocked();
+ connectionId = mConnectionId;
}
- Display display = DisplayManagerGlobal.getInstance()
- .getRealDisplay(Display.DEFAULT_DISPLAY);
- Point displaySize = new Point();
- display.getRealSize(displaySize);
-
- int rotation = display.getRotation();
-
- // Take the screenshot
- Bitmap screenShot = null;
- try {
- // Calling out without a lock held.
- screenShot = mUiAutomationConnection.takeScreenshot(
- new Rect(0, 0, displaySize.x, displaySize.y), rotation);
- if (screenShot == null) {
- return null;
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while taking screnshot!", re);
- return null;
- }
-
- // Optimization
- screenShot.setHasAlpha(false);
-
- return screenShot;
+ // Calling out without a lock held.
+ return AccessibilityInteractionClient.getInstance()
+ .takeScreenshot(connectionId, Display.DEFAULT_DISPLAY);
}
/**
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 82e9881..4fb9743 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -21,8 +21,6 @@
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Binder;
import android.os.IBinder;
@@ -180,23 +178,6 @@
}
@Override
- public Bitmap takeScreenshot(Rect crop, int rotation) {
- synchronized (mLock) {
- throwIfCalledByNotTrustedUidLocked();
- throwIfShutdownLocked();
- throwIfNotConnectedLocked();
- }
- final long identity = Binder.clearCallingIdentity();
- try {
- int width = crop.width();
- int height = crop.height();
- return SurfaceControl.screenshot(crop, width, height, rotation);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
@@ -449,7 +430,8 @@
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
- | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
+ | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+ | AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT);
try {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 2507991..9ad3d38 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -543,6 +543,13 @@
mCmProxy = new ColorManagementProxy(context);
}
+ // no-op constructor called just by DisabledWallpaperManager
+ /*package*/ WallpaperManager() {
+ mContext = null;
+ mCmProxy = null;
+ mWcgEnabled = false;
+ }
+
/**
* Retrieve a WallpaperManager associated with the given Context.
*/
@@ -568,8 +575,10 @@
*
* @see Configuration#isScreenWideColorGamut()
* @return True if wcg should be enabled for this device.
+ * @hide
*/
- private boolean shouldEnableWideColorGamut() {
+ @TestApi
+ public boolean shouldEnableWideColorGamut() {
return mWcgEnabled;
}
@@ -877,6 +886,7 @@
* @see #FLAG_SYSTEM
* @hide
*/
+ @TestApi
@RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
public boolean wallpaperSupportsWcg(int which) {
if (!shouldEnableWideColorGamut()) {
@@ -893,6 +903,8 @@
*
* @hide
*/
+ @TestApi
+ @Nullable
@UnsupportedAppUsage
public Bitmap getBitmap() {
return getBitmap(false);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 2aac94c..be8e1d6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -398,6 +398,42 @@
"android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
/**
+ * Activity action: Starts the provisioning flow which sets up a financed device.
+ *
+ * <p>During financed device provisioning, a device admin app is downloaded and set as the owner
+ * of the device. A device owner has full control over the device. The device owner can not be
+ * modified by the user.
+ *
+ * <p>A typical use case would be a device that is bought from the reseller through financing
+ * program.
+ *
+ * <p>An intent with this action can be sent only on an unprovisioned device.
+ *
+ * <p>Unlike {@link #ACTION_PROVISION_MANAGED_DEVICE}, the provisioning message can only be sent
+ * by a privileged app with the permission
+ * {@link android.Manifest.permission#DISPATCH_PROVISIONING_MESSAGE}.
+ *
+ * <p>The provisioning intent contains the following properties:
+ * <ul>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_PROVISION_FINANCED_DEVICE =
+ "android.app.action.PROVISION_FINANCED_DEVICE";
+
+ /**
* Activity action: Starts the provisioning flow which sets up a managed device.
* Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
*
@@ -864,6 +900,7 @@
* The name is displayed only during provisioning.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -876,6 +913,7 @@
* during provisioning. If the url is not HTTPS, an error will be shown.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -888,6 +926,7 @@
* as the app label of the package.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -912,6 +951,7 @@
* {@link android.content.ClipData} of the intent too.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -1371,6 +1411,16 @@
= "android.app.action.DEVICE_OWNER_CHANGED";
/**
+ * Broadcast action: sent when the factory reset protection (FRP) policy is changed.
+ *
+ * @see #setFactoryResetProtectionPolicy
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED =
+ "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
+
+ /**
* The ComponentName of the administrator component.
*
* @see #ACTION_ADD_DEVICE_ADMIN
@@ -1942,16 +1992,6 @@
public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14;
/**
- * Result code for {@link #checkProvisioningPreCondition}.
- *
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when adding a managed profile is
- * disallowed by {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
- *
- * @hide
- */
- public static final int CODE_ADD_MANAGED_PROFILE_DISALLOWED = 15;
-
- /**
* Result codes for {@link #checkProvisioningPreCondition} indicating all the provisioning pre
* conditions.
*
@@ -1963,7 +2003,7 @@
CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED,
CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE,
CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED,
- CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, CODE_ADD_MANAGED_PROFILE_DISALLOWED
+ CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER
})
public @interface ProvisioningPreCondition {}
@@ -4289,6 +4329,60 @@
}
/**
+ * Callable by device owner or profile owner of an organization-owned device, to set a
+ * factory reset protection (FRP) policy. When a new policy is set, the system
+ * notifies the FRP management agent of a policy change by broadcasting
+ * {@code ACTION_RESET_PROTECTION_POLICY_CHANGED}.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param policy the new FRP policy, or {@code null} to clear the current policy.
+ * @throws SecurityException if {@code admin} is not a device owner or a profile owner of
+ * an organization-owned device.
+ * @throws UnsupportedOperationException if factory reset protection is not
+ * supported on the device.
+ */
+ public void setFactoryResetProtectionPolicy(@NonNull ComponentName admin,
+ @Nullable FactoryResetProtectionPolicy policy) {
+ throwIfParentInstance("setFactoryResetProtectionPolicy");
+ if (mService != null) {
+ try {
+ mService.setFactoryResetProtectionPolicy(admin, policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Callable by device owner or profile owner of an organization-owned device, to retrieve
+ * the current factory reset protection (FRP) policy set previously by
+ * {@link #setFactoryResetProtectionPolicy}.
+ * <p>
+ * This method can also be called by the FRP management agent on device, in which case,
+ * it can pass {@code null} as the ComponentName.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with or
+ * {@code null} if called by the FRP management agent on device.
+ * @return The current FRP policy object or {@code null} if no policy is set.
+ * @throws SecurityException if {@code admin} is not a device owner, a profile owner of
+ * an organization-owned device or the FRP management agent.
+ * @throws UnsupportedOperationException if factory reset protection is not
+ * supported on the device.
+ */
+ public @Nullable FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+ @Nullable ComponentName admin) {
+ throwIfParentInstance("getFactoryResetProtectionPolicy");
+ if (mService != null) {
+ try {
+ return mService.getFactoryResetProtectionPolicy(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Called by an application that is administering the device to set the
* global proxy and exclusion list.
* <p>
@@ -6756,21 +6850,18 @@
}
/**
- * @hide
- * Privileged apps can use this method to find out if the device was provisioned as
+ * Apps can use this method to find out if the device was provisioned as
* organization-owend device with a managed profile.
*
* This, together with checking whether the device has a device owner (by calling
- * {@link #isDeviceManaged()}), could be used to learn whether the device is owned by an
+ * {@link #isDeviceOwnerApp}), could be used to learn whether the device is owned by an
* organization or an individual:
- * If this method returns true OR {@link #isDeviceManaged()} returns true, then
- * the device is owned by an organization. Otherwise, it's owned by an individual.
+ * If this method returns true OR {@link #isDeviceOwnerApp} returns true (for any package),
+ * then the device is owned by an organization. Otherwise, it's owned by an individual.
*
* @return {@code true} if the device was provisioned as organization-owned device,
* {@code false} otherwise.
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public boolean isOrganizationOwnedDeviceWithManagedProfile() {
throwIfParentInstance("isOrganizationOwnedDeviceWithManagedProfile");
if (mService != null) {
@@ -7324,7 +7415,9 @@
* @param userHandle The user for whom to check the caller-id permission
* @hide
*/
- public boolean getBluetoothContactSharingDisabled(UserHandle userHandle) {
+ @SystemApi
+ @RequiresPermission(permission.INTERACT_ACROSS_USERS)
+ public boolean getBluetoothContactSharingDisabled(@NonNull UserHandle userHandle) {
if (mService != null) {
try {
return mService.getBluetoothContactSharingDisabledForUser(userHandle
diff --git a/media/java/android/media/RouteDiscoveryRequest.aidl b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
similarity index 81%
copy from media/java/android/media/RouteDiscoveryRequest.aidl
copy to core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
index 744f656..72e639a 100644
--- a/media/java/android/media/RouteDiscoveryRequest.aidl
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.media;
+package android.app.admin;
-parcelable RouteDiscoveryRequest;
+parcelable FactoryResetProtectionPolicy;
diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
new file mode 100644
index 0000000..ed74779
--- /dev/null
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The factory reset protection policy determines which accounts can unlock a device that
+ * has gone through untrusted factory reset.
+ * <p>
+ * Only a device owner or profile owner of an organization-owned device can set a factory
+ * reset protection policy for the device by calling the {@code DevicePolicyManager} method
+ * {@link DevicePolicyManager#setFactoryResetProtectionPolicy(ComponentName,
+ * FactoryResetProtectionPolicy)}}.
+ *
+ * @see DevicePolicyManager#setFactoryResetProtectionPolicy
+ * @see DevicePolicyManager#getFactoryResetProtectionPolicy
+ */
+public final class FactoryResetProtectionPolicy implements Parcelable {
+
+ private static final String LOG_TAG = "FactoryResetProtectionPolicy";
+
+ private static final String KEY_FACTORY_RESET_PROTECTION_ACCOUNT =
+ "factory_reset_protection_account";
+ private static final String KEY_FACTORY_RESET_PROTECTION_DISABLED =
+ "factory_reset_protection_disabled";
+ private static final String ATTR_VALUE = "value";
+
+ private final List<String> mFactoryResetProtectionAccounts;
+ private final boolean mFactoryResetProtectionDisabled;
+
+ private FactoryResetProtectionPolicy(List<String> factoryResetProtectionAccounts,
+ boolean factoryResetProtectionDisabled) {
+ mFactoryResetProtectionAccounts = factoryResetProtectionAccounts;
+ mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+ }
+
+ /**
+ * Get the list of accounts that can provision a device which has been factory reset.
+ */
+ public @NonNull List<String> getFactoryResetProtectionAccounts() {
+ return mFactoryResetProtectionAccounts;
+ }
+
+ /**
+ * Return whether factory reset protection for the device is disabled or not.
+ */
+ public boolean isFactoryResetProtectionDisabled() {
+ return mFactoryResetProtectionDisabled;
+ }
+
+ /**
+ * Builder class for {@link FactoryResetProtectionPolicy} objects.
+ */
+ public static class Builder {
+ private List<String> mFactoryResetProtectionAccounts;
+ private boolean mFactoryResetProtectionDisabled;
+
+ /**
+ * Initialize a new Builder to construct a {@link FactoryResetProtectionPolicy}.
+ */
+ public Builder() {
+ };
+
+ /**
+ * Sets which accounts can unlock a device that has been factory reset.
+ * <p>
+ * Once set, the consumer unlock flow will be disabled and only accounts in this list
+ * can unlock factory reset protection after untrusted factory reset.
+ * <p>
+ * It's up to the FRP management agent to interpret the {@code String} as account it
+ * supports. Please consult their relevant documentation for details.
+ *
+ * @param factoryResetProtectionAccounts list of accounts.
+ * @return the same Builder instance.
+ */
+ @NonNull
+ public Builder setFactoryResetProtectionAccounts(
+ @NonNull List<String> factoryResetProtectionAccounts) {
+ mFactoryResetProtectionAccounts = new ArrayList<>(factoryResetProtectionAccounts);
+ return this;
+ }
+
+ /**
+ * Sets whether factory reset protection is disabled or not.
+ * <p>
+ * Once disabled, factory reset protection will not kick in all together when the device
+ * goes through untrusted factory reset. This applies to both the consumer unlock flow and
+ * the admin account overrides via {@link #setFactoryResetProtectionAccounts}
+ *
+ * @param factoryResetProtectionDisabled Whether the policy is disabled or not.
+ * @return the same Builder instance.
+ */
+ @NonNull
+ public Builder setFactoryResetProtectionDisabled(boolean factoryResetProtectionDisabled) {
+ mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+ return this;
+ }
+
+ /**
+ * Combines all of the attributes that have been set on this {@code Builder}
+ *
+ * @return a new {@link FactoryResetProtectionPolicy} object.
+ */
+ @NonNull
+ public FactoryResetProtectionPolicy build() {
+ return new FactoryResetProtectionPolicy(mFactoryResetProtectionAccounts,
+ mFactoryResetProtectionDisabled);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "FactoryResetProtectionPolicy{"
+ + "mFactoryResetProtectionAccounts=" + mFactoryResetProtectionAccounts
+ + ", mFactoryResetProtectionDisabled=" + mFactoryResetProtectionDisabled
+ + '}';
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, @Nullable int flags) {
+ int accountsCount = mFactoryResetProtectionAccounts.size();
+ dest.writeInt(accountsCount);
+ for (String account: mFactoryResetProtectionAccounts) {
+ dest.writeString(account);
+ }
+ dest.writeBoolean(mFactoryResetProtectionDisabled);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<FactoryResetProtectionPolicy> CREATOR =
+ new Creator<FactoryResetProtectionPolicy>() {
+
+ @Override
+ public FactoryResetProtectionPolicy createFromParcel(Parcel in) {
+ List<String> factoryResetProtectionAccounts = new ArrayList<>();
+ int accountsCount = in.readInt();
+ for (int i = 0; i < accountsCount; i++) {
+ factoryResetProtectionAccounts.add(in.readString());
+ }
+ boolean factoryResetProtectionDisabled = in.readBoolean();
+
+ return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+ factoryResetProtectionDisabled);
+ }
+
+ @Override
+ public FactoryResetProtectionPolicy[] newArray(int size) {
+ return new FactoryResetProtectionPolicy[size];
+ }
+ };
+
+ /**
+ * Restore a previously saved FactoryResetProtectionPolicy from XML.
+ * <p>
+ * No validation is required on the reconstructed policy since the XML was previously
+ * created by the system server from a validated policy.
+ * @hide
+ */
+ @Nullable
+ public static FactoryResetProtectionPolicy readFromXml(@NonNull XmlPullParser parser) {
+ try {
+ boolean factoryResetProtectionDisabled = Boolean.parseBoolean(
+ parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_DISABLED));
+
+ List<String> factoryResetProtectionAccounts = new ArrayList<>();
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != END_DOCUMENT
+ && (type != END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == END_TAG || type == TEXT) {
+ continue;
+ }
+ if (!parser.getName().equals(KEY_FACTORY_RESET_PROTECTION_ACCOUNT)) {
+ continue;
+ }
+ factoryResetProtectionAccounts.add(
+ parser.getAttributeValue(null, ATTR_VALUE));
+ }
+
+ return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+ factoryResetProtectionDisabled);
+ } catch (XmlPullParserException | IOException e) {
+ Log.w(LOG_TAG, "Reading from xml failed", e);
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void writeToXml(@NonNull XmlSerializer out) throws IOException {
+ out.attribute(null, KEY_FACTORY_RESET_PROTECTION_DISABLED,
+ Boolean.toString(mFactoryResetProtectionDisabled));
+ for (String account : mFactoryResetProtectionAccounts) {
+ out.startTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+ out.attribute(null, ATTR_VALUE, account);
+ out.endTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+ }
+ }
+
+}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3eec46b..21c9eb5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -24,6 +24,7 @@
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.PasswordMetrics;
+import android.app.admin.FactoryResetProtectionPolicy;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -104,6 +105,9 @@
void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent);
+ void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy);
+ FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
+
ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
ComponentName getGlobalProxyAdmin(int userHandle);
void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index c8f2ff3..567eb4a 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -85,6 +85,15 @@
public static final int FLAG_NON_INCREMENTAL = 1 << 2;
/**
+ * For key value backup, indicates that the backup contains no new data since the last backup
+ * attempt completed without any errors. The transport should use this to record that
+ * a successful backup attempt has been completed but no backup data has been changed.
+ *
+ * @see #performBackup(PackageInfo, ParcelFileDescriptor, int)
+ */
+ public static final int FLAG_DATA_NOT_CHANGED = 1 << 3;
+
+ /**
* Used as a boolean extra in the binding intent of transports. We pass {@code true} to
* notify transports that the current connection is used for registering the transport.
*/
@@ -302,7 +311,8 @@
* BackupService.doBackup() method. This may be a pipe rather than a file on
* persistent media, so it may not be seekable.
* @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link
- * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0.
+ * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL},
+ * {@link BackupTransport#FLAG_DATA_NOT_CHANGED},or 0.
* @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
* {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
* specific package, but allow others to proceed),
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
index 479e4b4..bd649f8 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.TimestampedValue;
@@ -28,17 +29,23 @@
import java.util.Objects;
/**
- * A time signal from a telephony source. The value can be {@code null} to indicate that the
- * telephony source has entered an "un-opinionated" state and any previously sent suggestions are
- * being withdrawn. When not {@code null}, the value consists of the number of milliseconds elapsed
- * since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime clock when that number
- * was established. The elapsed realtime clock is considered accurate but volatile, so time signals
- * must not be persisted across device resets.
+ * A time suggestion from an identified telephony source. e.g. from NITZ information from a specific
+ * radio.
+ *
+ * <p>The time value can be {@code null} to indicate that the telephony source has entered an
+ * "un-opinionated" state and any previous suggestions from the source are being withdrawn. When not
+ * {@code null}, the value consists of the number of milliseconds elapsed since 1/1/1970 00:00:00
+ * UTC and the time according to the elapsed realtime clock when that number was established. The
+ * elapsed realtime clock is considered accurate but volatile, so time suggestions must not be
+ * persisted across device resets.
*
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class PhoneTimeSuggestion implements Parcelable {
+ /** @hide */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final @NonNull Parcelable.Creator<PhoneTimeSuggestion> CREATOR =
new Parcelable.Creator<PhoneTimeSuggestion>() {
public PhoneTimeSuggestion createFromParcel(Parcel in) {
@@ -85,15 +92,27 @@
dest.writeList(mDebugInfo);
}
+ /**
+ * Returns an identifier for the source of this suggestion. When a device has several "phones",
+ * i.e. sim slots or equivalent, it is used to identify which one.
+ */
public int getPhoneId() {
return mPhoneId;
}
+ /**
+ * Returns the suggestion. {@code null} means that the caller is no longer sure what time it
+ * is.
+ */
@Nullable
public TimestampedValue<Long> getUtcTime() {
return mUtcTime;
}
+ /**
+ * Returns debug metadata for the suggestion. The information is present in {@link #toString()}
+ * but is not considered for {@link #equals(Object)} and {@link #hashCode()}.
+ */
@NonNull
public List<String> getDebugInfo() {
return mDebugInfo == null
@@ -105,7 +124,7 @@
* information is present in {@link #toString()} but is not considered for
* {@link #equals(Object)} and {@link #hashCode()}.
*/
- public void addDebugInfo(String debugInfo) {
+ public void addDebugInfo(@NonNull String debugInfo) {
if (mDebugInfo == null) {
mDebugInfo = new ArrayList<>();
}
@@ -156,16 +175,19 @@
*
* @hide
*/
- public static class Builder {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final class Builder {
private final int mPhoneId;
- private TimestampedValue<Long> mUtcTime;
- private List<String> mDebugInfo;
+ @Nullable private TimestampedValue<Long> mUtcTime;
+ @Nullable private List<String> mDebugInfo;
+ /** Creates a builder with the specified {@code phoneId}. */
public Builder(int phoneId) {
mPhoneId = phoneId;
}
/** Returns the builder for call chaining. */
+ @NonNull
public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) {
if (utcTime != null) {
// utcTime can be null, but the value it holds cannot.
@@ -177,6 +199,7 @@
}
/** Returns the builder for call chaining. */
+ @NonNull
public Builder addDebugInfo(@NonNull String debugInfo) {
if (mDebugInfo == null) {
mDebugInfo = new ArrayList<>();
@@ -186,6 +209,7 @@
}
/** Returns the {@link PhoneTimeSuggestion}. */
+ @NonNull
public PhoneTimeSuggestion build() {
return new PhoneTimeSuggestion(this);
}
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 54dd1be..7c29f01 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.RemoteException;
@@ -29,8 +30,11 @@
/**
* The interface through which system components can send signals to the TimeDetectorService.
+ *
+ * <p>This class is marked non-final for mockito.
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@SystemService(Context.TIME_DETECTOR_SERVICE)
public class TimeDetector {
private static final String TAG = "timedetector.TimeDetector";
@@ -38,6 +42,7 @@
private final ITimeDetectorService mITimeDetectorService;
+ /** @hide */
public TimeDetector() throws ServiceNotFoundException {
mITimeDetectorService = ITimeDetectorService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.TIME_DETECTOR_SERVICE));
@@ -62,6 +67,8 @@
/**
* Suggests the user's manually entered current time to the detector.
+ *
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE)
public void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion) {
@@ -77,6 +84,8 @@
/**
* A shared utility method to create a {@link ManualTimeSuggestion}.
+ *
+ * @hide
*/
public static ManualTimeSuggestion createManualTimeSuggestion(long when, String why) {
TimestampedValue<Long> utcTime =
@@ -88,6 +97,8 @@
/**
* Suggests the time according to a network time source like NTP.
+ *
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.SET_TIME)
public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) {
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
index e8162488..d71ffcb 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,12 +31,27 @@
import java.util.Objects;
/**
- * A suggested time zone from a Phone-based signal, e.g. from MCC and NITZ information.
+ * A time zone suggestion from an identified telephony source, e.g. from MCC and NITZ information
+ * associated with a specific radio.
+ *
+ * <p>The time zone ID can be {@code null} to indicate that the telephony source has entered an
+ * "un-opinionated" state and any previous suggestions from that source are being withdrawn.
+ * When not {@code null}, the value consists of a suggested time zone ID and metadata that can be
+ * used to judge quality / certainty of the suggestion.
+ *
+ * <p>{@code matchType} must be set to {@link #MATCH_TYPE_NA} when {@code zoneId} is {@code null},
+ * and one of the other {@code MATCH_TYPE_} values when it is not {@code null}.
+ *
+ * <p>{@code quality} must be set to {@link #QUALITY_NA} when {@code zoneId} is {@code null},
+ * and one of the other {@code QUALITY_} values when it is not {@code null}.
*
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class PhoneTimeZoneSuggestion implements Parcelable {
+ /** @hide */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@NonNull
public static final Creator<PhoneTimeZoneSuggestion> CREATOR =
new Creator<PhoneTimeZoneSuggestion>() {
@@ -58,6 +74,7 @@
return new Builder(phoneId).addDebugInfo(debugInfo).build();
}
+ /** @hide */
@IntDef({ MATCH_TYPE_NA, MATCH_TYPE_NETWORK_COUNTRY_ONLY, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET,
MATCH_TYPE_EMULATOR_ZONE_ID, MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY })
@Retention(RetentionPolicy.SOURCE)
@@ -90,6 +107,7 @@
*/
public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5;
+ /** @hide */
@IntDef({ QUALITY_NA, QUALITY_SINGLE_ZONE, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS })
@Retention(RetentionPolicy.SOURCE)
@@ -115,7 +133,7 @@
/**
* The ID of the phone this suggestion is associated with. For multiple-sim devices this
- * helps to establish origin so filtering / stickiness can be implemented.
+ * helps to establish source so filtering / stickiness can be implemented.
*/
private final int mPhoneId;
@@ -123,6 +141,7 @@
* The suggestion. {@code null} means there is no current suggestion and any previous suggestion
* should be forgotten.
*/
+ @Nullable
private final String mZoneId;
/**
@@ -139,9 +158,10 @@
private final int mQuality;
/**
- * Free-form debug information about how the signal was derived. Used for debug only,
+ * Free-form debug information about how the suggestion was derived. Used for debug only,
* intentionally not used in equals(), etc.
*/
+ @Nullable
private List<String> mDebugInfo;
private PhoneTimeZoneSuggestion(Builder builder) {
@@ -182,25 +202,47 @@
return 0;
}
+ /**
+ * Returns an identifier for the source of this suggestion. When a device has several "phones",
+ * i.e. sim slots or equivalent, it is used to identify which one.
+ */
public int getPhoneId() {
return mPhoneId;
}
+ /**
+ * Returns the suggested time zone Olson ID, e.g. "America/Los_Angeles". {@code null} means that
+ * the caller is no longer sure what the current time zone is. See
+ * {@link PhoneTimeZoneSuggestion} for the associated {@code matchType} / {@code quality} rules.
+ */
@Nullable
public String getZoneId() {
return mZoneId;
}
+ /**
+ * Returns information about how the suggestion was determined which could be used to rank
+ * suggestions when several are available from different sources. See
+ * {@link PhoneTimeZoneSuggestion} for the associated rules.
+ */
@MatchType
public int getMatchType() {
return mMatchType;
}
+ /**
+ * Returns information about the likelihood of the suggested zone being correct. See
+ * {@link PhoneTimeZoneSuggestion} for the associated rules.
+ */
@Quality
public int getQuality() {
return mQuality;
}
+ /**
+ * Returns debug metadata for the suggestion. The information is present in {@link #toString()}
+ * but is not considered for {@link #equals(Object)} and {@link #hashCode()}.
+ */
@NonNull
public List<String> getDebugInfo() {
return mDebugInfo == null
@@ -267,36 +309,43 @@
*
* @hide
*/
- public static class Builder {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final class Builder {
private final int mPhoneId;
- private String mZoneId;
+ @Nullable private String mZoneId;
@MatchType private int mMatchType;
@Quality private int mQuality;
- private List<String> mDebugInfo;
+ @Nullable private List<String> mDebugInfo;
public Builder(int phoneId) {
mPhoneId = phoneId;
}
- /** Returns the builder for call chaining. */
- public Builder setZoneId(String zoneId) {
+ /**
+ * Returns the builder for call chaining.
+ */
+ @NonNull
+ public Builder setZoneId(@Nullable String zoneId) {
mZoneId = zoneId;
return this;
}
/** Returns the builder for call chaining. */
+ @NonNull
public Builder setMatchType(@MatchType int matchType) {
mMatchType = matchType;
return this;
}
/** Returns the builder for call chaining. */
+ @NonNull
public Builder setQuality(@Quality int quality) {
mQuality = quality;
return this;
}
/** Returns the builder for call chaining. */
+ @NonNull
public Builder addDebugInfo(@NonNull String debugInfo) {
if (mDebugInfo == null) {
mDebugInfo = new ArrayList<>();
@@ -333,6 +382,7 @@
}
/** Returns the {@link PhoneTimeZoneSuggestion}. */
+ @NonNull
public PhoneTimeZoneSuggestion build() {
validate();
return new PhoneTimeZoneSuggestion(this);
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index e165d8a..5b5f311 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.RemoteException;
@@ -28,8 +29,10 @@
/**
* The interface through which system components can send signals to the TimeZoneDetectorService.
*
+ * <p>This class is non-final for mockito.
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
public class TimeZoneDetector {
private static final String TAG = "timezonedetector.TimeZoneDetector";
@@ -37,6 +40,7 @@
private final ITimeZoneDetectorService mITimeZoneDetectorService;
+ /** @hide */
public TimeZoneDetector() throws ServiceNotFoundException {
mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
@@ -46,7 +50,10 @@
* Suggests the current time zone, determined using telephony signals, to the detector. The
* detector may ignore the signal based on system settings, whether better information is
* available, and so on.
+ *
+ * @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE)
public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) {
if (DEBUG) {
@@ -62,6 +69,8 @@
/**
* Suggests the current time zone, determined for the user's manually information, to the
* detector. The detector may ignore the signal based on system settings.
+ *
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE)
public void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
@@ -77,6 +86,8 @@
/**
* A shared utility method to create a {@link ManualTimeZoneSuggestion}.
+ *
+ * @hide
*/
public static ManualTimeZoneSuggestion createManualTimeZoneSuggestion(String tzId, String why) {
ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(tzId);
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 4346d97..9c4a8f4 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -16,7 +16,10 @@
package android.app.usage;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.usage.NetworkStats.Bucket;
@@ -27,6 +30,9 @@
import android.net.INetworkStatsService;
import android.net.NetworkIdentity;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProviderWrapper;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
@@ -519,6 +525,34 @@
private DataUsageRequest request;
}
+ /**
+ * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
+ * statistics that cannot be seen by the kernel to system. To unregister, invoke
+ * {@link NetworkStatsProviderCallback#unregister()}.
+ *
+ * @param tag a human readable identifier of the custom network stats provider.
+ * @param provider a custom implementation of {@link AbstractNetworkStatsProvider} that needs to
+ * be registered to the system.
+ * @return a {@link NetworkStatsProviderCallback}, which can be used to report events to the
+ * system.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ @NonNull public NetworkStatsProviderCallback registerNetworkStatsProvider(
+ @NonNull String tag,
+ @NonNull AbstractNetworkStatsProvider provider) {
+ try {
+ final NetworkStatsProviderWrapper wrapper = new NetworkStatsProviderWrapper(provider);
+ return new NetworkStatsProviderCallback(
+ mService.registerNetworkStatsProvider(tag, wrapper));
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ // Unreachable code, but compiler doesn't know about it.
+ return null;
+ }
+
private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
final NetworkTemplate template;
switch (networkType) {
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index d840c1c..6ab880d 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -451,21 +451,7 @@
/** @hide */
public Event(Event orig) {
- mPackage = orig.mPackage;
- mClass = orig.mClass;
- mInstanceId = orig.mInstanceId;
- mTaskRootPackage = orig.mTaskRootPackage;
- mTaskRootClass = orig.mTaskRootClass;
- mTimeStamp = orig.mTimeStamp;
- mEventType = orig.mEventType;
- mConfiguration = orig.mConfiguration;
- mShortcutId = orig.mShortcutId;
- mAction = orig.mAction;
- mContentType = orig.mContentType;
- mContentAnnotations = orig.mContentAnnotations;
- mFlags = orig.mFlags;
- mBucketAndReason = orig.mBucketAndReason;
- mNotificationChannelId = orig.mNotificationChannelId;
+ copyFrom(orig);
}
/**
@@ -622,6 +608,24 @@
// which instant apps can't use anyway, so there's no need to hide them.
return ret;
}
+
+ private void copyFrom(Event orig) {
+ mPackage = orig.mPackage;
+ mClass = orig.mClass;
+ mInstanceId = orig.mInstanceId;
+ mTaskRootPackage = orig.mTaskRootPackage;
+ mTaskRootClass = orig.mTaskRootClass;
+ mTimeStamp = orig.mTimeStamp;
+ mEventType = orig.mEventType;
+ mConfiguration = orig.mConfiguration;
+ mShortcutId = orig.mShortcutId;
+ mAction = orig.mAction;
+ mContentType = orig.mContentType;
+ mContentAnnotations = orig.mContentAnnotations;
+ mFlags = orig.mFlags;
+ mBucketAndReason = orig.mBucketAndReason;
+ mNotificationChannelId = orig.mNotificationChannelId;
+ }
}
// Only used when creating the resulting events. Not used for reading/unparceling.
@@ -725,10 +729,14 @@
return false;
}
- readEventFromParcel(mParcel, eventOut);
+ if (mParcel != null) {
+ readEventFromParcel(mParcel, eventOut);
+ } else {
+ eventOut.copyFrom(mEventsToWrite.get(mIndex));
+ }
mIndex++;
- if (mIndex >= mEventCount) {
+ if (mIndex >= mEventCount && mParcel != null) {
mParcel.recycle();
mParcel = null;
}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 176a181..a60e591 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -163,7 +163,6 @@
/**
* The app spent sufficient time in the old bucket without any substantial event so it reached
* the timeout threshold to have its bucket lowered.
- *
* @hide
*/
public static final int REASON_MAIN_TIMEOUT = 0x0200;
@@ -173,15 +172,25 @@
*/
public static final int REASON_MAIN_USAGE = 0x0300;
/**
- * Forced by a core UID.
+ * Forced by the user/developer, either explicitly or implicitly through some action. If user
+ * action was not involved and this is purely due to the system,
+ * {@link #REASON_MAIN_FORCED_BY_SYSTEM} should be used instead.
* @hide
*/
- public static final int REASON_MAIN_FORCED = 0x0400;
+ public static final int REASON_MAIN_FORCED_BY_USER = 0x0400;
/**
- * Set by a privileged system app.
+ * Set by a privileged system app. This may be overridden by
+ * {@link #REASON_MAIN_FORCED_BY_SYSTEM} or user action.
* @hide
*/
public static final int REASON_MAIN_PREDICTED = 0x0500;
+ /**
+ * Forced by the system, independent of user action. If user action is involved,
+ * {@link #REASON_MAIN_FORCED_BY_USER} should be used instead. When this is used, only
+ * {@link #REASON_MAIN_FORCED_BY_SYSTEM} or user action can change the bucket.
+ * @hide
+ */
+ public static final int REASON_MAIN_FORCED_BY_SYSTEM = 0x0600;
/** @hide */
public static final int REASON_SUB_MASK = 0x00FF;
@@ -1016,7 +1025,10 @@
case REASON_MAIN_DEFAULT:
sb.append("d");
break;
- case REASON_MAIN_FORCED:
+ case REASON_MAIN_FORCED_BY_SYSTEM:
+ sb.append("s");
+ break;
+ case REASON_MAIN_FORCED_BY_USER:
sb.append("f");
break;
case REASON_MAIN_PREDICTED:
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index e9b0be2..a923be6 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -16,8 +16,12 @@
package android.bluetooth;
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -36,6 +40,7 @@
*/
public final class BluetoothHidDevice implements BluetoothProfile {
private static final String TAG = BluetoothHidDevice.class.getSimpleName();
+ private static final boolean DBG = false;
/**
* Intent used to broadcast the change in connection state of the Input Host profile.
@@ -682,4 +687,62 @@
return result;
}
+
+ /**
+ * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}
+ * and disconnects Hid device if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}.
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy determines whether hid device should be connected or disconnected
+ * @return true if hid device is connected or disconnected, false otherwise
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+ @ConnectionPolicy int connectionPolicy) {
+ log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+ try {
+ final IBluetoothHidDevice service = getService();
+ if (service != null && isEnabled()
+ && isValidDevice(device)) {
+ if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ return false;
+ }
+ return service.setConnectionPolicy(device, connectionPolicy);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ private boolean isEnabled() {
+ if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+ return false;
+ }
+
+ private boolean isValidDevice(BluetoothDevice device) {
+ if (device == null) return false;
+
+ if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+ return false;
+ }
+
+ private static void log(String msg) {
+ if (DBG) {
+ Log.d(TAG, msg);
+ }
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 917e7fa..4674706 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -17,7 +17,10 @@
package android.bluetooth;
import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -35,21 +38,35 @@
*
* @hide
*/
+@SystemApi
public final class BluetoothMap implements BluetoothProfile {
private static final String TAG = "BluetoothMap";
private static final boolean DBG = true;
private static final boolean VDBG = false;
+ /** @hide */
+ @SuppressLint("ActionValue")
+ @SystemApi
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
- /** There was an error trying to obtain the state */
+ /**
+ * There was an error trying to obtain the state
+ *
+ * @hide
+ */
public static final int STATE_ERROR = -1;
+ /** @hide */
public static final int RESULT_FAILURE = 0;
+ /** @hide */
public static final int RESULT_SUCCESS = 1;
- /** Connection canceled before completion. */
+ /**
+ * Connection canceled before completion.
+ *
+ * @hide
+ */
public static final int RESULT_CANCELED = 2;
private BluetoothAdapter mAdapter;
@@ -71,6 +88,7 @@
mProfileConnector.connect(context, listener);
}
+ @SuppressLint("GenericException")
protected void finalize() throws Throwable {
try {
close();
@@ -84,6 +102,8 @@
* Other public functions of BluetoothMap will return default error
* results once close() has been called. Multiple invocations of close()
* are ok.
+ *
+ * @hide
*/
public synchronized void close() {
mProfileConnector.disconnect();
@@ -98,6 +118,8 @@
*
* @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
* connected to the Map service.
+ *
+ * @hide
*/
public int getState() {
if (VDBG) log("getState()");
@@ -120,6 +142,8 @@
*
* @return The remote Bluetooth device, or null if not in connected or connecting state, or if
* this proxy object is not connected to the Map service.
+ *
+ * @hide
*/
public BluetoothDevice getClient() {
if (VDBG) log("getClient()");
@@ -141,6 +165,8 @@
* Returns true if the specified Bluetooth device is connected.
* Returns false if not connected, or if this proxy object is not
* currently connected to the Map service.
+ *
+ * @hide
*/
public boolean isConnected(BluetoothDevice device) {
if (VDBG) log("isConnected(" + device + ")");
@@ -161,6 +187,8 @@
/**
* Initiate connection. Initiation of outgoing connections is not
* supported for MAP server.
+ *
+ * @hide
*/
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")" + "not supported for MAPS");
@@ -172,6 +200,8 @@
*
* @param device Remote Bluetooth Device
* @return false on error, true otherwise
+ *
+ * @hide
*/
@UnsupportedAppUsage
public boolean disconnect(BluetoothDevice device) {
@@ -196,6 +226,8 @@
* devices. It tries to err on the side of false positives.
*
* @return True if this device might support Map.
+ *
+ * @hide
*/
public static boolean doesClassMatchSink(BluetoothClass btClass) {
// TODO optimize the rule
@@ -214,8 +246,11 @@
* Get the list of connected devices. Currently at most one.
*
* @return list of connected devices
+ *
+ * @hide
*/
- public List<BluetoothDevice> getConnectedDevices() {
+ @SystemApi
+ public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (DBG) log("getConnectedDevices()");
final IBluetoothMap service = getService();
if (service != null && isEnabled()) {
@@ -234,6 +269,8 @@
* Get the list of devices matching specified states. Currently at most one.
*
* @return list of matching devices
+ *
+ * @hide
*/
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) log("getDevicesMatchingStates()");
@@ -254,6 +291,8 @@
* Get connection state of device
*
* @return device connection state
+ *
+ * @hide
*/
public int getConnectionState(BluetoothDevice device) {
if (DBG) log("getConnectionState(" + device + ")");
@@ -301,7 +340,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
- public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+ public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothMap service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -349,7 +388,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH)
- public int getConnectionPolicy(BluetoothDevice device) {
+ public int getConnectionPolicy(@Nullable BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothMap service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 42f27f2..024bb06 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -16,9 +16,11 @@
package android.bluetooth;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
@@ -256,6 +258,41 @@
}
/**
+ * Set connection policy of the profile
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+ * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+ @ConnectionPolicy int connectionPolicy) {
+ if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+ try {
+ final IBluetoothPan service = getService();
+ if (service != null && isEnabled()
+ && isValidDevice(device)) {
+ if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ return false;
+ }
+ return service.setConnectionPolicy(device, connectionPolicy);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 948885e..e07ca52 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -274,15 +274,15 @@
}
/**
- * Pbap does not store connection policy, so this function only disconnects Pbap if
- * connectionPolicy is CONNECTION_POLICY_FORBIDDEN.
+ * Pbap does not store connection policy, so this function only disconnects pbap if
+ * connectionPolicy is {@link #CONNECTION_POLICY_FORBIDDEN}.
*
* <p> The device should already be paired.
* Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
* {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
*
* @param device Paired bluetooth device
- * @param connectionPolicy is the connection policy to set to for this profile
+ * @param connectionPolicy determines whether to disconnect the device
* @return true if pbap is successfully disconnected, false otherwise
* @hide
*/
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5cb2907..3860508 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1825,7 +1825,7 @@
* @throws ActivityNotFoundException
* @hide
*/
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
@SystemApi
public void startActivityAsUser(@RequiresPermission @NonNull Intent intent,
@NonNull UserHandle user) {
@@ -1873,7 +1873,7 @@
* @throws ActivityNotFoundException
* @hide
*/
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
@UnsupportedAppUsage
public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options,
UserHandle userId) {
@@ -1979,7 +1979,7 @@
* @see #startActivities(Intent[])
* @see PackageManager#resolveActivity
*/
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
@@ -3240,15 +3240,40 @@
}
/**
- * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
- * argument for use by system server and other multi-user aware code.
- * @hide
+ * Binds to a service in the given {@code user} in the same manner as
+ * {@link #bindService(Intent, ServiceConnection, int)}.
+ *
+ * <p>If the given {@code user} is in the same profile group and the target package is the
+ * same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is
+ * sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS}
+ * for interacting with other users.
+ *
+ * @param service Identifies the service to connect to. The Intent must
+ * specify an explicit component name.
+ * @param conn Receives information as the service is started and stopped.
+ * This must be a valid ServiceConnection object; it must not be null.
+ * @param flags Operation options for the binding. May be 0,
+ * {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
+ * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
+ * {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
+ * {@link #BIND_IMPORTANT}, or
+ * {@link #BIND_ADJUST_WITH_ACTIVITY}.
+ * @return {@code true} if the system is in the process of bringing up a
+ * service that your client has permission to bind to; {@code false}
+ * if the system couldn't find the service. If this value is {@code true}, you
+ * should later call {@link #unbindService} to release the
+ * connection.
+ *
+ * @throws SecurityException if the client does not have the required permission to bind.
*/
- @SystemApi
@SuppressWarnings("unused")
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
- public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn,
- int flags, UserHandle user) {
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES
+ })
+ public boolean bindServiceAsUser(
+ @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags,
+ @NonNull UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
@@ -3928,10 +3953,12 @@
/**
* Use with {@link android.os.ServiceManager.getService()} to retrieve a
- * {@link NetworkStackClient} IBinder for communicating with the network stack
+ * {@link INetworkStackConnector} IBinder for communicating with the network stack
* @hide
* @see NetworkStackClient
*/
+ @SystemApi
+ @TestApi
public static final String NETWORK_STACK_SERVICE = "network_stack";
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3bb0f92..ee75802 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -752,6 +752,22 @@
public static final String ACTION_PICK = "android.intent.action.PICK";
/**
+ * Activity Action: Creates a reminder.
+ * <p>Input: {@link #EXTRA_TITLE} The title of the reminder that will be shown to the user.
+ * {@link #EXTRA_TEXT} The reminder text that will be shown to the user. The intent should at
+ * least specify a title or a text. {@link #EXTRA_TIME} The time when the reminder will be shown
+ * to the user. The time is specified in milliseconds since the Epoch (optional).
+ * </p>
+ * <p>Output: Nothing.</p>
+ *
+ * @see #EXTRA_TITLE
+ * @see #EXTRA_TEXT
+ * @see #EXTRA_TIME
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
+
+ /**
* Activity Action: Creates a shortcut.
* <p>Input: Nothing.</p>
* <p>Output: An Intent representing the {@link android.content.pm.ShortcutInfo} result.</p>
@@ -5726,6 +5742,15 @@
= "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
/**
+ * Optional extra specifying a time in milliseconds since the Epoch. The value must be
+ * non-negative.
+ * <p>
+ * Type: long
+ * </p>
+ */
+ public static final String EXTRA_TIME = "android.intent.extra.TIME";
+
+ /**
* Optional int extra for {@link #ACTION_TIME_CHANGED} that indicates the
* user has set their time format preference. See {@link #EXTRA_TIME_PREF_VALUE_USE_12_HOUR},
* {@link #EXTRA_TIME_PREF_VALUE_USE_24_HOUR} and
@@ -6440,19 +6465,22 @@
*/
public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000;
/**
- * If set, when sending a broadcast <i>before boot has completed</i> only
+ * If set, when sending a broadcast <i>before the system has fully booted up
+ * (which is even before {@link #ACTION_LOCKED_BOOT_COMPLETED} has been sent)"</i> only
* registered receivers will be called -- no BroadcastReceiver components
* will be launched. Sticky intent state will be recorded properly even
* if no receivers wind up being called. If {@link #FLAG_RECEIVER_REGISTERED_ONLY}
* is specified in the broadcast intent, this flag is unnecessary.
*
- * <p>This flag is only for use by system sevices as a convenience to
- * avoid having to implement a more complex mechanism around detection
+ * <p>This flag is only for use by system services (even services from mainline modules) as a
+ * convenience to avoid having to implement a more complex mechanism around detection
* of boot completion.
*
+ * <p>This is useful to system server mainline modules
+ *
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000;
/**
* Set when this broadcast is for a boot upgrade, a special mode that
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index abf32c5..9d57514 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -16,15 +16,19 @@
package android.content.pm;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import com.android.internal.R;
import com.android.internal.util.UserIcons;
@@ -226,6 +230,30 @@
}
}
+ /**
+ * Returns an {@link Intent} to open the settings page that allows the user to decide whether
+ * the calling app can interact across profiles. The current state is given by
+ * {@link #canInteractAcrossProfiles()}.
+ *
+ * <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}.
+ *
+ * @return an {@link Intent} to open the settings page that allows the user to decide whether
+ * the app can interact across profiles
+ *
+ * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+ * calling UID.
+ */
+ public @Nullable Intent createRequestInteractAcrossProfilesIntent() {
+ if (!canRequestInteractAcrossProfiles()) {
+ return null;
+ }
+ final Intent settingsIntent = new Intent();
+ settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS);
+ final Uri packageUri = Uri.parse("package:" + mContext.getPackageName());
+ settingsIntent.setData(packageUri);
+ return settingsIntent;
+ }
+
private void verifyCanAccessUser(UserHandle userHandle) {
if (!getTargetUserProfiles().contains(userHandle)) {
throw new SecurityException("Not allowed to access " + userHandle);
diff --git a/core/java/android/content/pm/InstallSourceInfo.java b/core/java/android/content/pm/InstallSourceInfo.java
index 4d235f1..c0fdcc9 100644
--- a/core/java/android/content/pm/InstallSourceInfo.java
+++ b/core/java/android/content/pm/InstallSourceInfo.java
@@ -29,32 +29,39 @@
@Nullable private final String mInitiatingPackageName;
+ @Nullable private final SigningInfo mInitiatingPackageSigningInfo;
+
@Nullable private final String mOriginatingPackageName;
@Nullable private final String mInstallingPackageName;
/** @hide */
public InstallSourceInfo(@Nullable String initiatingPackageName,
+ @Nullable SigningInfo initiatingPackageSigningInfo,
@Nullable String originatingPackageName, @Nullable String installingPackageName) {
- this.mInitiatingPackageName = initiatingPackageName;
- this.mOriginatingPackageName = originatingPackageName;
- this.mInstallingPackageName = installingPackageName;
+ mInitiatingPackageName = initiatingPackageName;
+ mInitiatingPackageSigningInfo = initiatingPackageSigningInfo;
+ mOriginatingPackageName = originatingPackageName;
+ mInstallingPackageName = installingPackageName;
}
@Override
public int describeContents() {
- return 0;
+ return mInitiatingPackageSigningInfo == null
+ ? 0 : mInitiatingPackageSigningInfo.describeContents();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mInitiatingPackageName);
+ dest.writeParcelable(mInitiatingPackageSigningInfo, flags);
dest.writeString(mOriginatingPackageName);
dest.writeString(mInstallingPackageName);
}
private InstallSourceInfo(Parcel source) {
mInitiatingPackageName = source.readString();
+ mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader());
mOriginatingPackageName = source.readString();
mInstallingPackageName = source.readString();
}
@@ -66,6 +73,14 @@
}
/**
+ * Information about the signing certificates used to sign the initiating package, if available.
+ */
+ @Nullable
+ public SigningInfo getInitiatingPackageSigningInfo() {
+ return mInitiatingPackageSigningInfo;
+ }
+
+ /**
* The name of the package on behalf of which the initiating package requested the installation,
* or null if not available.
* <p>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4bfc40e..5ca50c2 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4389,6 +4389,18 @@
public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permName);
/**
+ * Gets the string that is displayed on the button which corresponds to granting background
+ * location in settings. The intended use for this is to help apps instruct users how to
+ * grant a background permission by providing the string that users will see.
+ *
+ * @return the string shown on the button for granting background location
+ */
+ @NonNull
+ public CharSequence getBackgroundPermissionButtonLabel() {
+ return "";
+ }
+
+ /**
* Returns an {@link android.content.Intent} suitable for passing to
* {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
* which prompts the user to grant permissions to this application.
@@ -6053,6 +6065,11 @@
* If the calling application does not hold the INSTALL_PACKAGES permission then
* the result will always return {@code null} from
* {@link InstallSourceInfo#getOriginatingPackageName()}.
+ * <p>
+ * If the package that requested the install has been uninstalled, then information about it
+ * will only be returned from {@link InstallSourceInfo#getInitiatingPackageName()} and
+ * {@link InstallSourceInfo#getInitiatingPackageSigningInfo()} if the calling package is
+ * requesting its own install information and is not an instant app.
*
* @param packageName The name of the package to query
* @throws NameNotFoundException if the given package name is not installed
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 32803ab..87acbc1 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -7803,7 +7803,7 @@
ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
}
ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
- ai.resourceDirs = state.overlayPaths;
+ ai.resourceDirs = state.getAllOverlayPaths();
ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes;
}
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index f0f6753..da7623a 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -46,6 +46,8 @@
import java.io.IOException;
import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Objects;
/**
@@ -77,7 +79,9 @@
public ArraySet<String> disabledComponents;
public ArraySet<String> enabledComponents;
- public String[] overlayPaths;
+ private String[] overlayPaths;
+ private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths
+ private String[] cachedOverlayPaths;
@UnsupportedAppUsage
public PackageUserState() {
@@ -112,9 +116,33 @@
enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
overlayPaths =
o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
+ if (o.sharedLibraryOverlayPaths != null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths);
+ }
harmfulAppWarning = o.harmfulAppWarning;
}
+ public String[] getOverlayPaths() {
+ return overlayPaths;
+ }
+
+ public void setOverlayPaths(String[] paths) {
+ overlayPaths = paths;
+ cachedOverlayPaths = null;
+ }
+
+ public Map<String, String[]> getSharedLibraryOverlayPaths() {
+ return sharedLibraryOverlayPaths;
+ }
+
+ public void setSharedLibraryOverlayPaths(String library, String[] paths) {
+ if (sharedLibraryOverlayPaths == null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>();
+ }
+ sharedLibraryOverlayPaths.put(library, paths);
+ cachedOverlayPaths = null;
+ }
+
/**
* Test if this package is installed.
*/
@@ -235,6 +263,38 @@
return isComponentEnabled;
}
+ public String[] getAllOverlayPaths() {
+ if (overlayPaths == null && sharedLibraryOverlayPaths == null) {
+ return null;
+ }
+
+ if (cachedOverlayPaths != null) {
+ return cachedOverlayPaths;
+ }
+
+ final LinkedHashSet<String> paths = new LinkedHashSet<>();
+ if (overlayPaths != null) {
+ final int N = overlayPaths.length;
+ for (int i = 0; i < N; i++) {
+ paths.add(overlayPaths[i]);
+ }
+ }
+
+ if (sharedLibraryOverlayPaths != null) {
+ for (String[] libOverlayPaths : sharedLibraryOverlayPaths.values()) {
+ if (libOverlayPaths != null) {
+ final int N = libOverlayPaths.length;
+ for (int i = 0; i < N; i++) {
+ paths.add(libOverlayPaths[i]);
+ }
+ }
+ }
+ }
+
+ cachedOverlayPaths = paths.toArray(new String[0]);
+ return cachedOverlayPaths;
+ }
+
@Override
final public boolean equals(Object obj) {
if (!(obj instanceof PackageUserState)) {
diff --git a/core/java/android/content/pm/parsing/ComponentParseUtils.java b/core/java/android/content/pm/parsing/ComponentParseUtils.java
index f04a30c..56ace5e 100644
--- a/core/java/android/content/pm/parsing/ComponentParseUtils.java
+++ b/core/java/android/content/pm/parsing/ComponentParseUtils.java
@@ -58,6 +58,7 @@
import android.view.Gravity;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
import com.android.internal.util.XmlUtils;
@@ -814,6 +815,11 @@
return exported;
}
+ @VisibleForTesting
+ public void setExported(boolean exported) {
+ this.exported = exported;
+ }
+
public List<ParsedProviderIntentInfo> getIntents() {
return intents;
}
diff --git a/core/java/android/content/pm/parsing/PackageInfoUtils.java b/core/java/android/content/pm/parsing/PackageInfoUtils.java
index f2cf9a4..73a8d2a 100644
--- a/core/java/android/content/pm/parsing/PackageInfoUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoUtils.java
@@ -41,9 +41,11 @@
import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation;
import android.content.pm.parsing.ComponentParseUtils.ParsedPermission;
import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup;
+import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
+import java.util.LinkedHashSet;
import java.util.Set;
/** @hide */
@@ -545,7 +547,7 @@
ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
}
ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
- ai.resourceDirs = state.overlayPaths;
+ ai.resourceDirs = state.getAllOverlayPaths();
ai.icon = (PackageParser.sUseRoundIcon && ai.roundIconRes != 0)
? ai.roundIconRes : ai.iconRes;
}
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
new file mode 100644
index 0000000..daf9a14
--- /dev/null
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksResourceLoaderTests"
+ }
+ ]
+}
diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java
index 6378db0..b273cd6 100644
--- a/core/java/android/content/rollback/PackageRollbackInfo.java
+++ b/core/java/android/content/rollback/PackageRollbackInfo.java
@@ -76,6 +76,11 @@
*/
private final boolean mIsApex;
+ /**
+ * Whether this instance represents the PackageRollbackInfo for an APK in APEX.
+ */
+ private final boolean mIsApkInApex;
+
/*
* The list of users for which snapshots have been saved.
*/
@@ -157,6 +162,10 @@
public @PackageManager.RollbackDataPolicy int getRollbackDataPolicy() {
return mRollbackDataPolicy;
}
+ /** @hide */
+ public boolean isApkInApex() {
+ return mIsApkInApex;
+ }
/** @hide */
public IntArray getSnapshottedUsers() {
@@ -190,17 +199,18 @@
public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
VersionedPackage packageRolledBackTo,
@NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
- boolean isApex, @NonNull IntArray snapshottedUsers,
+ boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
@NonNull SparseLongArray ceSnapshotInodes) {
this(packageRolledBackFrom, packageRolledBackTo, pendingBackups, pendingRestores, isApex,
- snapshottedUsers, ceSnapshotInodes, PackageManager.RollbackDataPolicy.RESTORE);
+ isApkInApex, snapshottedUsers, ceSnapshotInodes,
+ PackageManager.RollbackDataPolicy.RESTORE);
}
/** @hide */
public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
VersionedPackage packageRolledBackTo,
@NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
- boolean isApex, @NonNull IntArray snapshottedUsers,
+ boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
@NonNull SparseLongArray ceSnapshotInodes,
@PackageManager.RollbackDataPolicy int rollbackDataPolicy) {
this.mVersionRolledBackFrom = packageRolledBackFrom;
@@ -209,6 +219,7 @@
this.mPendingRestores = pendingRestores;
this.mIsApex = isApex;
this.mRollbackDataPolicy = rollbackDataPolicy;
+ this.mIsApkInApex = isApkInApex;
this.mSnapshottedUsers = snapshottedUsers;
this.mCeSnapshotInodes = ceSnapshotInodes;
}
@@ -217,6 +228,7 @@
this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in);
this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in);
this.mIsApex = in.readBoolean();
+ this.mIsApkInApex = in.readBoolean();
this.mPendingRestores = null;
this.mPendingBackups = null;
this.mSnapshottedUsers = null;
@@ -234,6 +246,7 @@
mVersionRolledBackFrom.writeToParcel(out, flags);
mVersionRolledBackTo.writeToParcel(out, flags);
out.writeBoolean(mIsApex);
+ out.writeBoolean(mIsApkInApex);
}
public static final @NonNull Parcelable.Creator<PackageRollbackInfo> CREATOR =
diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
index 698876b..11cf2d6 100644
--- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java
+++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
@@ -18,6 +18,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
import android.os.CancellationSignal;
import android.os.Parcelable;
@@ -119,6 +120,7 @@
class AuthenticationResult {
private Identifier mIdentifier;
private CryptoObject mCryptoObject;
+ private @AuthenticationResultType int mAuthenticationType;
private int mUserId;
/**
@@ -129,27 +131,41 @@
/**
* Authentication result
* @param crypto
+ * @param authenticationType
* @param identifier
* @param userId
* @hide
*/
- public AuthenticationResult(CryptoObject crypto, Identifier identifier,
+ public AuthenticationResult(CryptoObject crypto,
+ @AuthenticationResultType int authenticationType, Identifier identifier,
int userId) {
mCryptoObject = crypto;
+ mAuthenticationType = authenticationType;
mIdentifier = identifier;
mUserId = userId;
}
/**
- * Obtain the crypto object associated with this transaction
- * @return crypto object provided to {@link BiometricAuthenticator#authenticate(
- * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)}
+ * Provides the crypto object associated with this transaction.
+ * @return The crypto object provided to {@link BiometricPrompt#authenticate(
+ * BiometricPrompt.CryptoObject, CancellationSignal, Executor,
+ * BiometricPrompt.AuthenticationCallback)}
*/
public CryptoObject getCryptoObject() {
return mCryptoObject;
}
/**
+ * Provides the type of authentication (e.g. device credential or biometric) that was
+ * requested from and successfully provided by the user.
+ *
+ * @return An integer value representing the authentication method used.
+ */
+ public @AuthenticationResultType int getAuthenticationType() {
+ return mAuthenticationType;
+ }
+
+ /**
* Obtain the biometric identifier associated with this operation. Applications are strongly
* discouraged from associating specific identifiers with specific applications or
* operations.
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index cb8fc8b..a695ce8 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -21,6 +21,7 @@
import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -40,6 +41,8 @@
import com.android.internal.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.security.Signature;
import java.util.concurrent.Executor;
@@ -397,9 +400,11 @@
new IBiometricServiceReceiver.Stub() {
@Override
- public void onAuthenticationSucceeded() throws RemoteException {
+ public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType)
+ throws RemoteException {
mExecutor.execute(() -> {
- final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
+ final AuthenticationResult result =
+ new AuthenticationResult(mCryptoObject, authenticationType);
mAuthenticationCallback.onAuthenticationSucceeded(result);
});
}
@@ -576,28 +581,62 @@
}
/**
- * Container for callback data from {@link #authenticate( CancellationSignal, Executor,
+ * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
+ * entering their device PIN, pattern, or password.
+ */
+ public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1;
+
+ /**
+ * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
+ * presenting some form of biometric (e.g. fingerprint or face).
+ */
+ public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2;
+
+ /**
+ * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC})
+ public @interface AuthenticationResultType {
+ }
+
+ /**
+ * Container for callback data from {@link #authenticate(CancellationSignal, Executor,
* AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor,
- * AuthenticationCallback)}
+ * AuthenticationCallback)}.
*/
public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult {
/**
* Authentication result
* @param crypto
+ * @param authenticationType
* @hide
*/
- public AuthenticationResult(CryptoObject crypto) {
+ public AuthenticationResult(CryptoObject crypto,
+ @AuthenticationResultType int authenticationType) {
// Identifier and userId is not used for BiometricPrompt.
- super(crypto, null /* identifier */, 0 /* userId */);
+ super(crypto, authenticationType, null /* identifier */, 0 /* userId */);
}
+
/**
- * Obtain the crypto object associated with this transaction
- * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal,
- * Executor, AuthenticationCallback)}
+ * Provides the crypto object associated with this transaction.
+ * @return The crypto object provided to {@link #authenticate(CryptoObject,
+ * CancellationSignal, Executor, AuthenticationCallback)}
*/
public CryptoObject getCryptoObject() {
return (CryptoObject) super.getCryptoObject();
}
+
+ /**
+ * Provides the type of authentication (e.g. device credential or biometric) that was
+ * requested from and successfully provided by the user.
+ *
+ * @return An integer value representing the authentication method used.
+ */
+ public @AuthenticationResultType int getAuthenticationType() {
+ return super.getAuthenticationType();
+ }
}
/**
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
index c960049..1d43aa6 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
@@ -20,8 +20,8 @@
* @hide
*/
oneway interface IBiometricServiceReceiver {
- // Notify BiometricPrompt that authentication was successful
- void onAuthenticationSucceeded();
+ // Notify BiometricPrompt that authentication was successful.
+ void onAuthenticationSucceeded(int authenticationType);
// Noties that authentication failed.
void onAuthenticationFailed();
// Notify BiometricPrompt that an error has occurred.
diff --git a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
index 07d2443..6ead64c 100644
--- a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
@@ -25,9 +25,12 @@
* A constrained high speed capture session for a {@link CameraDevice}, used for capturing high
* speed images from the {@link CameraDevice} for high speed video recording use case.
* <p>
- * A CameraHighSpeedCaptureSession is created by providing a set of target output surfaces to
- * {@link CameraDevice#createConstrainedHighSpeedCaptureSession}, Once created, the session is
- * active until a new session is created by the camera device, or the camera device is closed.
+ * A CameraConstrainedHighSpeedCaptureSession is created by providing a session configuration to
+ * {@link CameraDevice#createCaptureSession(SessionConfiguration)} with a type of
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED}. The
+ * CameraCaptureSession returned from {@link CameraCaptureSession.StateCallback} can then be cast to
+ * a CameraConstrainedHighSpeedCaptureSession. Once created, the session is active until a new
+ * session is created by the camera device, or the camera device is closed.
* </p>
* <p>
* An active high speed capture session is a specialized capture session that is only targeted at
@@ -37,8 +40,8 @@
* accepts request lists created via {@link #createHighSpeedRequestList}, and the request list can
* only be submitted to this session via {@link CameraCaptureSession#captureBurst captureBurst}, or
* {@link CameraCaptureSession#setRepeatingBurst setRepeatingBurst}. See
- * {@link CameraDevice#createConstrainedHighSpeedCaptureSession} for more details of the
- * limitations.
+ * {@link CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)}
+ * for more details of the limitations.
* </p>
* <p>
* Creating a session is an expensive operation and can take several hundred milliseconds, since it
@@ -50,13 +53,6 @@
* completed, then the {@link CameraCaptureSession.StateCallback#onConfigureFailed} is called, and
* the session will not become active.
* </p>
- * <!--
- * <p>
- * Any capture requests (repeating or non-repeating) submitted before the session is ready will be
- * queued up and will begin capture once the session becomes ready. In case the session cannot be
- * configured and {@link CameraCaptureSession.StateCallback#onConfigureFailed onConfigureFailed} is
- * called, all queued capture requests are discarded. </p>
- * -->
* <p>
* If a new session is created by the camera device, then the previous session is closed, and its
* associated {@link CameraCaptureSession.StateCallback#onClosed onClosed} callback will be
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index fb1ece2..cc06681 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -16,24 +16,22 @@
package android.hardware.camera2;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import static android.hardware.camera2.ICameraDeviceUser.NORMAL_MODE;
-import static android.hardware.camera2.ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Handler;
import android.view.Surface;
-import java.util.List;
-import java.util.Set;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Set;
/**
* <p>The CameraDevice class is a representation of a single camera connected to an
@@ -220,6 +218,224 @@
* <p>Create a new camera capture session by providing the target output set of Surfaces to the
* camera device.</p>
*
+ * @param outputs The new set of Surfaces that should be made available as
+ * targets for captured image data.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
+ * the callback is null, or the handler is null but the current
+ * thread has no looper.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see CameraCaptureSession
+ * @see StreamConfigurationMap#getOutputFormats()
+ * @see StreamConfigurationMap#getOutputSizes(int)
+ * @see StreamConfigurationMap#getOutputSizes(Class)
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createCaptureSession(@NonNull List<Surface> outputs,
+ @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * <p>Create a new camera capture session by providing the target output set of Surfaces and
+ * its corresponding surface configuration to the camera device.</p>
+ *
+ * @see #createCaptureSession
+ * @see OutputConfiguration
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createCaptureSessionByOutputConfigurations(
+ List<OutputConfiguration> outputConfigurations,
+ CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
+ throws CameraAccessException;
+ /**
+ * Create a new reprocessable camera capture session by providing the desired reprocessing
+ * input Surface configuration and the target output set of Surfaces to the camera device.
+ *
+ * @param inputConfig The configuration for the input {@link Surface}
+ * @param outputs The new set of Surfaces that should be made available as
+ * targets for captured image data.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the input configuration is null or not supported, the set
+ * of output Surfaces do not meet the requirements, the
+ * callback is null, or the handler is null but the current
+ * thread has no looper.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see #createCaptureSession
+ * @see CameraCaptureSession
+ * @see StreamConfigurationMap#getInputFormats
+ * @see StreamConfigurationMap#getInputSizes
+ * @see StreamConfigurationMap#getValidOutputFormatsForInput
+ * @see StreamConfigurationMap#getOutputSizes
+ * @see android.media.ImageWriter
+ * @see android.media.ImageReader
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig,
+ @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * Create a new reprocessable camera capture session by providing the desired reprocessing
+ * input configuration and output {@link OutputConfiguration}
+ * to the camera device.
+ *
+ * @see #createReprocessableCaptureSession
+ * @see OutputConfiguration
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createReprocessableCaptureSessionByConfigurations(
+ @NonNull InputConfiguration inputConfig,
+ @NonNull List<OutputConfiguration> outputs,
+ @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * <p>Create a new constrained high speed capture session.</p>
+ *
+ * @param outputs The new set of Surfaces that should be made available as
+ * targets for captured high speed image data.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
+ * the callback is null, or the handler is null but the current
+ * thread has no looper, or the camera device doesn't support
+ * high speed video capability.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see #createCaptureSession
+ * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
+ * @see StreamConfigurationMap#getHighSpeedVideoSizes
+ * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
+ * @see CameraCaptureSession#captureBurst
+ * @see CameraCaptureSession#setRepeatingBurst
+ * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs,
+ @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * Standard camera operation mode.
+ *
+ * @see #createCustomCaptureSession
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int SESSION_OPERATION_MODE_NORMAL =
+ 0; // ICameraDeviceUser.NORMAL_MODE;
+
+ /**
+ * Constrained high-speed operation mode.
+ *
+ * @see #createCustomCaptureSession
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
+ 1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
+
+ /**
+ * First vendor-specific operating mode
+ *
+ * @see #createCustomCaptureSession
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int SESSION_OPERATION_MODE_VENDOR_START =
+ 0x8000; // ICameraDeviceUser.VENDOR_MODE_START;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
+ {SESSION_OPERATION_MODE_NORMAL,
+ SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
+ SESSION_OPERATION_MODE_VENDOR_START})
+ public @interface SessionOperatingMode {};
+
+ /**
+ * Create a new camera capture session with a custom operating mode.
+ *
+ * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session
+ * is desired, or {@code null} otherwise.
+ * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be
+ * made available as targets for captured image data.
+ * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom
+ * vendor value or one of the SESSION_OPERATION_MODE_* values.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the input configuration is null or not supported, the set
+ * of output Surfaces do not meet the requirements, the
+ * callback is null, or the handler is null but the current
+ * thread has no looper.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see #createCaptureSession
+ * @see #createReprocessableCaptureSession
+ * @see CameraCaptureSession
+ * @see OutputConfiguration
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @Deprecated
+ public abstract void createCustomCaptureSession(
+ InputConfiguration inputConfig,
+ @NonNull List<OutputConfiguration> outputs,
+ @SessionOperatingMode int operatingMode,
+ @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
+ * object that aggregates all supported parameters.</p>
* <p>The active capture session determines the set of potential output Surfaces for
* the camera device for each capture request. A given request may use all
* or only some of the outputs. Once the CameraCaptureSession is created, requests can be
@@ -308,11 +524,15 @@
* <p>Configuring a session with an empty or null list will close the current session, if
* any. This can be used to release the current session's target surfaces for another use.</p>
*
+ * <h3>Regular capture</h3>
+ *
* <p>While any of the sizes from {@link StreamConfigurationMap#getOutputSizes} can be used when
* a single output stream is configured, a given camera device may not be able to support all
* combination of sizes, formats, and targets when multiple outputs are configured at once. The
* tables below list the maximum guaranteed resolutions for combinations of streams and targets,
- * given the capabilities of the camera device.</p>
+ * given the capabilities of the camera device. These are valid for when the
+ * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration
+ * input configuration} is not set and therefore no reprocessing is active.</p>
*
* <p>If an application tries to create a session using a set of targets that exceed the limits
* described in the below tables, one of three possibilities may occur. First, the session may
@@ -488,56 +708,22 @@
* (either width or height) might not be supported, and capture session creation will fail if it
* is not.</p>
*
- * @param outputs The new set of Surfaces that should be made available as
- * targets for captured image data.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
- * the callback is null, or the handler is null but the current
- * thread has no looper.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see CameraCaptureSession
- * @see StreamConfigurationMap#getOutputFormats()
- * @see StreamConfigurationMap#getOutputSizes(int)
- * @see StreamConfigurationMap#getOutputSizes(Class)
- */
- public abstract void createCaptureSession(@NonNull List<Surface> outputs,
- @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * <p>Create a new camera capture session by providing the target output set of Surfaces and
- * its corresponding surface configuration to the camera device.</p>
- *
- * @see #createCaptureSession
- * @see OutputConfiguration
- */
- public abstract void createCaptureSessionByOutputConfigurations(
- List<OutputConfiguration> outputConfigurations,
- CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
- throws CameraAccessException;
- /**
- * Create a new reprocessable camera capture session by providing the desired reprocessing
- * input Surface configuration and the target output set of Surfaces to the camera device.
+ * <h3>Reprocessing</h3>
*
* <p>If a camera device supports YUV reprocessing
* ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING}) or PRIVATE
* reprocessing
- * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), besides
- * the capture session created via {@link #createCaptureSession createCaptureSession}, the
+ * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), the
* application can also create a reprocessable capture session to submit reprocess capture
- * requests in addition to regular capture requests. A reprocess capture request takes the next
- * available buffer from the session's input Surface, and sends it through the camera device's
- * processing pipeline again, to produce buffers for the request's target output Surfaces. No
- * new image data is captured for a reprocess request. However the input buffer provided by
- * the application must be captured previously by the same camera device in the same session
- * directly (e.g. for Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output
- * images).</p>
+ * requests in addition to regular capture requests, by setting an
+ * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration
+ * input configuration} for the session. A reprocess capture request takes the next available
+ * buffer from the
+ * session's input Surface, and sends it through the camera device's processing pipeline again,
+ * to produce buffers for the request's target output Surfaces. No new image data is captured
+ * for a reprocess request. However the input buffer provided by the application must be
+ * captured previously by the same camera device in the same session directly (e.g. for
+ * Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output images).</p>
*
* <p>The active reprocessable capture session determines an input {@link Surface} and the set
* of potential output Surfaces for the camera devices for each capture request. The application
@@ -570,10 +756,7 @@
* <p>Starting from API level 30, recreating a reprocessable capture session will flush all the
* queued but not yet processed buffers from the input surface.</p>
*
- * <p>The guaranteed stream configurations listed in
- * {@link #createCaptureSession createCaptureSession} are also guaranteed to work for
- * {@link #createReprocessableCaptureSession createReprocessableCaptureSession}. In addition,
- * the configurations in the tables below are also guaranteed for creating a reprocessable
+ * <p>The configurations in the tables below are guaranteed for creating a reprocessable
* capture session if the camera device supports YUV reprocessing or PRIVATE reprocessing.
* However, not all output targets used to create a reprocessable session may be used in a
* {@link CaptureRequest} simultaneously. For devices that support only 1 output target in a
@@ -602,7 +785,7 @@
* <p>LIMITED-level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}
* {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED}) devices
* support at least the following stream combinations for creating a reprocessable capture
- * session in addition to those listed in {@link #createCaptureSession createCaptureSession} for
+ * session in addition to those listed earlier for regular captures for
* {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices:
*
* <table>
@@ -671,74 +854,30 @@
* </table><br>
* </p>
*
- * <p>Clients can access the above mandatory stream combination tables via
- * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p>
+ * <h3>Constrained high-speed recording</h3>
*
- * @param inputConfig The configuration for the input {@link Surface}
- * @param outputs The new set of Surfaces that should be made available as
- * targets for captured image data.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the input configuration is null or not supported, the set
- * of output Surfaces do not meet the requirements, the
- * callback is null, or the handler is null but the current
- * thread has no looper.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see #createCaptureSession
- * @see CameraCaptureSession
- * @see StreamConfigurationMap#getInputFormats
- * @see StreamConfigurationMap#getInputSizes
- * @see StreamConfigurationMap#getValidOutputFormatsForInput
- * @see StreamConfigurationMap#getOutputSizes
- * @see android.media.ImageWriter
- * @see android.media.ImageReader
- */
- public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig,
- @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * Create a new reprocessable camera capture session by providing the desired reprocessing
- * input configuration and output {@link OutputConfiguration}
- * to the camera device.
- *
- * @see #createReprocessableCaptureSession
- * @see OutputConfiguration
- *
- */
- public abstract void createReprocessableCaptureSessionByConfigurations(
- @NonNull InputConfiguration inputConfig,
- @NonNull List<OutputConfiguration> outputs,
- @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * <p>Create a new constrained high speed capture session.</p>
- *
- * <p>The application can use normal capture session (created via {@link #createCaptureSession})
+ * <p>The application can use a
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_REGULAR
+ * normal capture session}
* for high speed capture if the desired high speed FPS ranges are advertised by
* {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES}, in which case all API
* semantics associated with normal capture sessions applies.</p>
*
- * <p>The method creates a specialized capture session that is only targeted at high speed
- * video recording (>=120fps) use case if the camera device supports high speed video
- * capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} contains
- * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}).
- * Therefore, it has special characteristics compared with a normal capture session:</p>
+ * <p>A
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED
+ * high-speed capture session}
+ * can be use for high speed video recording (>=120fps) when the camera device supports high
+ * speed video capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES}
+ * contains {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}).
+ * A constrained high-speed capture session has special limitations compared with a normal
+ * capture session:</p>
*
* <ul>
*
- * <li>In addition to the output target Surface requirements specified by the
- * {@link #createCaptureSession} method, an active high speed capture session will support up
- * to 2 output Surfaces, though the application might choose to configure just one Surface
- * (e.g., preview only). All Surfaces must be either video encoder surfaces (acquired by
+ * <li>In addition to the output target Surface requirements specified above for regular
+ * captures, a high speed capture session will only support up to 2 output Surfaces, though
+ * the application might choose to configure just one Surface (e.g., preview only). All
+ * Surfaces must be either video encoder surfaces (acquired by
* {@link android.media.MediaRecorder#getSurface} or
* {@link android.media.MediaCodec#createInputSurface}) or preview surfaces (obtained from
* {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture} via
@@ -774,116 +913,6 @@
*
* </ul>
*
- * @param outputs The new set of Surfaces that should be made available as
- * targets for captured high speed image data.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
- * the callback is null, or the handler is null but the current
- * thread has no looper, or the camera device doesn't support
- * high speed video capability.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see #createCaptureSession
- * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
- * @see StreamConfigurationMap#getHighSpeedVideoSizes
- * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor
- * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
- * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
- * @see CameraCaptureSession#captureBurst
- * @see CameraCaptureSession#setRepeatingBurst
- * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList
- */
- public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs,
- @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * Standard camera operation mode.
- *
- * @see #createCustomCaptureSession
- * @hide
- */
- @SystemApi
- @TestApi
- public static final int SESSION_OPERATION_MODE_NORMAL =
- 0; // ICameraDeviceUser.NORMAL_MODE;
-
- /**
- * Constrained high-speed operation mode.
- *
- * @see #createCustomCaptureSession
- * @hide
- */
- @SystemApi
- @TestApi
- public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
- 1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
-
- /**
- * First vendor-specific operating mode
- *
- * @see #createCustomCaptureSession
- * @hide
- */
- @SystemApi
- @TestApi
- public static final int SESSION_OPERATION_MODE_VENDOR_START =
- 0x8000; // ICameraDeviceUser.VENDOR_MODE_START;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
- {SESSION_OPERATION_MODE_NORMAL,
- SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
- SESSION_OPERATION_MODE_VENDOR_START})
- public @interface SessionOperatingMode {};
-
- /**
- * Create a new camera capture session with a custom operating mode.
- *
- * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session
- * is desired, or {@code null} otherwise.
- * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be
- * made available as targets for captured image data.
- * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom
- * vendor value or one of the SESSION_OPERATION_MODE_* values.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the input configuration is null or not supported, the set
- * of output Surfaces do not meet the requirements, the
- * callback is null, or the handler is null but the current
- * thread has no looper.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see #createCaptureSession
- * @see #createReprocessableCaptureSession
- * @see CameraCaptureSession
- * @see OutputConfiguration
- * @hide
- */
- @SystemApi
- @TestApi
- public abstract void createCustomCaptureSession(
- InputConfiguration inputConfig,
- @NonNull List<OutputConfiguration> outputs,
- @SessionOperatingMode int operatingMode,
- @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
- * object that aggregates all supported parameters.</p>
*
* @param config A session configuration (see {@link SessionConfiguration}).
*
@@ -997,7 +1026,7 @@
*
* @see CaptureRequest.Builder
* @see TotalCaptureResult
- * @see CameraDevice#createReprocessableCaptureSession
+ * @see CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)
* @see android.media.ImageWriter
*/
@NonNull
@@ -1028,7 +1057,8 @@
* <p>This method performs a runtime check of a given {@link SessionConfiguration}. The result
* confirms whether or not the passed session configuration can be successfully used to
* create a camera capture session using
- * {@link CameraDevice#createCaptureSession(SessionConfiguration)}.
+ * {@link CameraDevice#createCaptureSession(
+ * android.hardware.camera2.params.SessionConfiguration)}.
* </p>
*
* <p>The method can be called at any point before, during and after active capture session.
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 8e0a46d..ec13a36 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1121,12 +1121,16 @@
//
/**
- * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic,
- * but can not be compared to timestamps from other subsystems
- * (e.g. accelerometer, gyro etc.), or other instances of the same or different
- * camera devices in the same system. Timestamps between streams and results for
- * a single camera instance are comparable, and the timestamps for all buffers
- * and the result metadata generated by a single capture are identical.</p>
+ * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic, but can
+ * not be compared to timestamps from other subsystems (e.g. accelerometer, gyro etc.),
+ * or other instances of the same or different camera devices in the same system with
+ * accuracy. However, the timestamps are roughly in the same timebase as
+ * {@link android.os.SystemClock#uptimeMillis }. The accuracy is sufficient for tasks
+ * like A/V synchronization for video recording, at least, and the timestamps can be
+ * directly used together with timestamps from the audio subsystem for that task.</p>
+ * <p>Timestamps between streams and results for a single camera instance are comparable,
+ * and the timestamps for all buffers and the result metadata generated by a single
+ * capture are identical.</p>
*
* @see CaptureResult#SENSOR_TIMESTAMP
* @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE
@@ -1137,6 +1141,14 @@
* <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in the same timebase as
* {@link android.os.SystemClock#elapsedRealtimeNanos },
* and they can be compared to other timestamps using that base.</p>
+ * <p>When buffers from a REALTIME device are passed directly to a video encoder from the
+ * camera, automatic compensation is done to account for differing timebases of the
+ * audio and camera subsystems. If the application is receiving buffers and then later
+ * sending them to a video encoder or other application where they are compared with
+ * audio subsystem timestamps or similar, this compensation is not present. In those
+ * cases, applications need to adjust the timestamps themselves. Since {@link android.os.SystemClock#elapsedRealtimeNanos } and {@link android.os.SystemClock#uptimeMillis } only diverge while the device is asleep, an
+ * offset between the two sources can be measured once per active session and applied
+ * to timestamps for sufficient accuracy for A/V sync.</p>
*
* @see CaptureResult#SENSOR_TIMESTAMP
* @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 555ff9a..47a897c 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -17,10 +17,11 @@
package android.hardware.camera2.params;
+import static com.android.internal.util.Preconditions.*;
+
import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
@@ -32,14 +33,12 @@
import android.os.Parcelable;
import android.util.Log;
-import java.util.Collections;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-
-import static com.android.internal.util.Preconditions.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
/**
* A helper class that aggregates all supported arguments for capture session initialization.
@@ -61,6 +60,12 @@
* A high speed session type that can only contain instances of {@link OutputConfiguration}.
* The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration}
* are not supported.
+ * <p>
+ * When using this type, the CameraCaptureSession returned by
+ * {@link android.hardware.camera2.CameraCaptureSession.StateCallback} can be cast to a
+ * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession} to access the extra
+ * methods for constrained high speed recording.
+ * </p>
*
* @see CameraDevice#createConstrainedHighSpeedCaptureSession
*/
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 8231c58..d43a619 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -16,9 +16,11 @@
package android.hardware.soundtrigger;
+import android.annotation.Nullable;
import android.hardware.soundtrigger.ModelParams;
import android.media.AudioFormat;
import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.AudioCapabilities;
import android.media.soundtrigger_middleware.ConfidenceLevel;
import android.media.soundtrigger_middleware.ModelParameterRange;
import android.media.soundtrigger_middleware.Phrase;
@@ -32,8 +34,6 @@
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
-import android.annotation.Nullable;
-
import java.util.Arrays;
import java.util.UUID;
@@ -48,6 +48,7 @@
properties.description,
properties.uuid,
properties.version,
+ properties.supportedModelArch,
properties.maxSoundModels,
properties.maxKeyPhrases,
properties.maxUsers,
@@ -56,7 +57,8 @@
properties.maxBufferMs,
properties.concurrentCapture,
properties.powerConsumptionMw,
- properties.triggerInEvent
+ properties.triggerInEvent,
+ aidl2apiAudioCapabilities(properties.audioCapabilities)
);
}
@@ -145,6 +147,7 @@
apiConfig.keyphrases[i]);
}
aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
+ aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities);
return aidlConfig;
}
@@ -326,4 +329,26 @@
}
return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
}
+
+ public static int aidl2apiAudioCapabilities(int aidlCapabilities) {
+ int result = 0;
+ if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) {
+ result |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+ }
+ if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+ result |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+ }
+ return result;
+ }
+
+ public static int api2aidlAudioCapabilities(int apiCapabilities) {
+ int result = 0;
+ if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION) != 0) {
+ result |= AudioCapabilities.ECHO_CANCELLATION;
+ }
+ if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION) != 0) {
+ result |= AudioCapabilities.NOISE_SUPPRESSION;
+ }
+ return result;
+ }
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 55505ba..1932f46 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -24,6 +24,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -33,6 +34,7 @@
import android.media.AudioFormat;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.Status;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -40,8 +42,11 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.UUID;
@@ -83,6 +88,30 @@
*
****************************************************************************/
public static final class ModuleProperties implements Parcelable {
+
+ /**
+ * Bit field values of AudioCapabilities supported by the implemented HAL
+ * driver.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+ CAPABILITY_ECHO_CANCELLATION,
+ CAPABILITY_NOISE_SUPPRESSION
+ })
+ public @interface AudioCapabilities {}
+
+ /**
+ * If set the underlying module supports AEC.
+ * Describes bit field {@link ModuleProperties#audioCapabilities}
+ */
+ public static final int CAPABILITY_ECHO_CANCELLATION = 0x1;
+ /**
+ * If set, the underlying module supports noise suppression.
+ * Describes bit field {@link ModuleProperties#audioCapabilities}
+ */
+ public static final int CAPABILITY_NOISE_SUPPRESSION = 0x2;
+
/** Unique module ID provided by the native service */
public final int id;
@@ -101,6 +130,14 @@
/** Voice detection engine version */
public final int version;
+ /**
+ * String naming the architecture used for running the supported models.
+ * (eg. a platform running models on a DSP could implement this string to convey the DSP
+ * architecture used)
+ */
+ @NonNull
+ public final String supportedModelArch;
+
/** Maximum number of active sound models */
public final int maxSoundModels;
@@ -129,16 +166,25 @@
* recognition callback event */
public final boolean returnsTriggerInEvent;
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ */
+ @AudioCapabilities
+ public final int audioCapabilities;
+
ModuleProperties(int id, @NonNull String implementor, @NonNull String description,
- @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases,
- int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
- int maxBufferMs, boolean supportsConcurrentCapture,
- int powerConsumptionMw, boolean returnsTriggerInEvent) {
+ @NonNull String uuid, int version, @NonNull String supportedModelArch,
+ int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes,
+ boolean supportsCaptureTransition, int maxBufferMs,
+ boolean supportsConcurrentCapture, int powerConsumptionMw,
+ boolean returnsTriggerInEvent, int audioCapabilities) {
this.id = id;
this.implementor = requireNonNull(implementor);
this.description = requireNonNull(description);
this.uuid = UUID.fromString(requireNonNull(uuid));
this.version = version;
+ this.supportedModelArch = requireNonNull(supportedModelArch);
this.maxSoundModels = maxSoundModels;
this.maxKeyphrases = maxKeyphrases;
this.maxUsers = maxUsers;
@@ -148,6 +194,7 @@
this.supportsConcurrentCapture = supportsConcurrentCapture;
this.powerConsumptionMw = powerConsumptionMw;
this.returnsTriggerInEvent = returnsTriggerInEvent;
+ this.audioCapabilities = audioCapabilities;
}
public static final @android.annotation.NonNull Parcelable.Creator<ModuleProperties> CREATOR
@@ -167,6 +214,7 @@
String description = in.readString();
String uuid = in.readString();
int version = in.readInt();
+ String supportedModelArch = in.readString();
int maxSoundModels = in.readInt();
int maxKeyphrases = in.readInt();
int maxUsers = in.readInt();
@@ -176,10 +224,11 @@
boolean supportsConcurrentCapture = in.readByte() == 1;
int powerConsumptionMw = in.readInt();
boolean returnsTriggerInEvent = in.readByte() == 1;
+ int audioCapabilities = in.readInt();
return new ModuleProperties(id, implementor, description, uuid, version,
- maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
+ supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture,
- powerConsumptionMw, returnsTriggerInEvent);
+ powerConsumptionMw, returnsTriggerInEvent, audioCapabilities);
}
@Override
@@ -189,6 +238,7 @@
dest.writeString(description);
dest.writeString(uuid.toString());
dest.writeInt(version);
+ dest.writeString(supportedModelArch);
dest.writeInt(maxSoundModels);
dest.writeInt(maxKeyphrases);
dest.writeInt(maxUsers);
@@ -198,6 +248,7 @@
dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0));
dest.writeInt(powerConsumptionMw);
dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0));
+ dest.writeInt(audioCapabilities);
}
@Override
@@ -208,13 +259,15 @@
@Override
public String toString() {
return "ModuleProperties [id=" + id + ", implementor=" + implementor + ", description="
- + description + ", uuid=" + uuid + ", version=" + version + ", maxSoundModels="
+ + description + ", uuid=" + uuid + ", version=" + version
+ + " , supportedModelArch=" + supportedModelArch + ", maxSoundModels="
+ maxSoundModels + ", maxKeyphrases=" + maxKeyphrases + ", maxUsers="
+ maxUsers + ", recognitionModes=" + recognitionModes
+ ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs="
+ maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture
+ ", powerConsumptionMw=" + powerConsumptionMw
- + ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]";
+ + ", returnsTriggerInEvent=" + returnsTriggerInEvent
+ + ", audioCapabilities=" + audioCapabilities + "]";
}
}
@@ -252,16 +305,20 @@
@NonNull
public final UUID vendorUuid;
+ /** vendor specific version number of the model */
+ public final int version;
+
/** Opaque data. For use by vendor implementation and enrollment application */
@UnsupportedAppUsage
@NonNull
public final byte[] data;
public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type,
- @Nullable byte[] data) {
+ @Nullable byte[] data, int version) {
this.uuid = requireNonNull(uuid);
this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0);
this.type = type;
+ this.version = version;
this.data = data != null ? data : new byte[0];
}
@@ -269,6 +326,7 @@
public int hashCode() {
final int prime = 31;
int result = 1;
+ result = prime * result + version;
result = prime * result + Arrays.hashCode(data);
result = prime * result + type;
result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
@@ -299,6 +357,8 @@
return false;
if (!Arrays.equals(data, other.data))
return false;
+ if (version != other.version)
+ return false;
return true;
}
}
@@ -448,14 +508,19 @@
@NonNull
public final Keyphrase[] keyphrases; // keyword phrases in model
- @UnsupportedAppUsage
public KeyphraseSoundModel(
@NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data,
- @Nullable Keyphrase[] keyphrases) {
- super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
+ @Nullable Keyphrase[] keyphrases, int version) {
+ super(uuid, vendorUuid, TYPE_KEYPHRASE, data, version);
this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0];
}
+ @UnsupportedAppUsage
+ public KeyphraseSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data, @Nullable Keyphrase[] keyphrases) {
+ this(uuid, vendorUuid, data, keyphrases, -1);
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR
= new Parcelable.Creator<KeyphraseSoundModel>() {
public KeyphraseSoundModel createFromParcel(Parcel in) {
@@ -474,9 +539,10 @@
if (length >= 0) {
vendorUuid = UUID.fromString(in.readString());
}
+ int version = in.readInt();
byte[] data = in.readBlob();
Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR);
- return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases);
+ return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases, version);
}
@Override
@@ -493,6 +559,7 @@
dest.writeInt(vendorUuid.toString().length());
dest.writeString(vendorUuid.toString());
}
+ dest.writeInt(version);
dest.writeBlob(data);
dest.writeTypedArray(keyphrases, flags);
}
@@ -501,7 +568,9 @@
public String toString() {
return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases)
+ ", uuid=" + uuid + ", vendorUuid=" + vendorUuid
- + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
+ + ", type=" + type
+ + ", data=" + (data == null ? 0 : data.length)
+ + ", version=" + version + "]";
}
@Override
@@ -547,10 +616,15 @@
}
};
+ public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data, int version) {
+ super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version);
+ }
+
@UnsupportedAppUsage
public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
@Nullable byte[] data) {
- super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
+ this(uuid, vendorUuid, data, -1);
}
@Override
@@ -566,7 +640,8 @@
vendorUuid = UUID.fromString(in.readString());
}
byte[] data = in.readBlob();
- return new GenericSoundModel(uuid, vendorUuid, data);
+ int version = in.readInt();
+ return new GenericSoundModel(uuid, vendorUuid, data, version);
}
@Override
@@ -579,28 +654,31 @@
dest.writeString(vendorUuid.toString());
}
dest.writeBlob(data);
+ dest.writeInt(version);
}
@Override
public String toString() {
return "GenericSoundModel [uuid=" + uuid + ", vendorUuid=" + vendorUuid
- + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
+ + ", type=" + type
+ + ", data=" + (data == null ? 0 : data.length)
+ + ", version=" + version + "]";
}
}
- /*****************************************************************************
+ /**
* A ModelParamRange is a representation of supported parameter range for a
* given loaded model.
- ****************************************************************************/
+ */
public static final class ModelParamRange implements Parcelable {
/**
- * start of supported range inclusive
+ * The inclusive start of supported range.
*/
public final int start;
/**
- * end of supported range inclusive
+ * The inclusive end of supported range.
*/
public final int end;
@@ -609,31 +687,65 @@
this.end = end;
}
+ /** @hide */
private ModelParamRange(@NonNull Parcel in) {
this.start = in.readInt();
this.end = in.readInt();
}
@NonNull
- public static final Creator<ModelParamRange> CREATOR = new Creator<ModelParamRange>() {
- @Override
- @NonNull
- public ModelParamRange createFromParcel(@NonNull Parcel in) {
- return new ModelParamRange(in);
- }
+ public static final Creator<ModelParamRange> CREATOR =
+ new Creator<ModelParamRange>() {
+ @Override
+ @NonNull
+ public ModelParamRange createFromParcel(@NonNull Parcel in) {
+ return new ModelParamRange(in);
+ }
- @Override
- @NonNull
- public ModelParamRange[] newArray(int size) {
- return new ModelParamRange[size];
- }
- };
+ @Override
+ @NonNull
+ public ModelParamRange[] newArray(int size) {
+ return new ModelParamRange[size];
+ }
+ };
+ /** @hide */
@Override
public int describeContents() {
return 0;
}
+ /** @hide */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (start);
+ result = prime * result + (end);
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ModelParamRange other = (ModelParamRange) obj;
+ if (start != other.start) {
+ return false;
+ }
+ if (end != other.end) {
+ return false;
+ }
+ return true;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(start);
@@ -1002,13 +1114,27 @@
@NonNull
public final byte[] data;
- @UnsupportedAppUsage
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ */
+ @ModuleProperties.AudioCapabilities
+ public final int audioCapabilities;
+
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
+ int audioCapabilities) {
this.captureRequested = captureRequested;
this.allowMultipleTriggers = allowMultipleTriggers;
this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
this.data = data != null ? data : new byte[0];
+ this.audioCapabilities = audioCapabilities;
+ }
+
+ @UnsupportedAppUsage
+ public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+ this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
}
public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1028,7 +1154,9 @@
KeyphraseRecognitionExtra[] keyphrases =
in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
byte[] data = in.readBlob();
- return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data);
+ int audioCapabilities = in.readInt();
+ return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
+ audioCapabilities);
}
@Override
@@ -1037,6 +1165,7 @@
dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
dest.writeTypedArray(keyphrases, flags);
dest.writeBlob(data);
+ dest.writeInt(audioCapabilities);
}
@Override
@@ -1048,7 +1177,8 @@
public String toString() {
return "RecognitionConfig [captureRequested=" + captureRequested
+ ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases="
- + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + "]";
+ + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data)
+ + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]";
}
}
@@ -1548,6 +1678,45 @@
}
/**
+ * Translate an exception thrown from interaction with the underlying service to an error code.
+ * Throws a runtime exception for unexpected conditions.
+ * @param e The caught exception.
+ * @return The error code.
+ *
+ * @hide
+ */
+ static int handleException(Exception e) {
+ Log.w(TAG, "Exception caught", e);
+ if (e instanceof RemoteException) {
+ return STATUS_DEAD_OBJECT;
+ }
+ if (e instanceof ServiceSpecificException) {
+ switch (((ServiceSpecificException) e).errorCode) {
+ case Status.OPERATION_NOT_SUPPORTED:
+ return STATUS_INVALID_OPERATION;
+ case Status.TEMPORARY_PERMISSION_DENIED:
+ return STATUS_PERMISSION_DENIED;
+ case Status.DEAD_OBJECT:
+ return STATUS_DEAD_OBJECT;
+ }
+ return STATUS_ERROR;
+ }
+ if (e instanceof SecurityException) {
+ return STATUS_PERMISSION_DENIED;
+ }
+ if (e instanceof IllegalStateException) {
+ return STATUS_INVALID_OPERATION;
+ }
+ if (e instanceof IllegalArgumentException || e instanceof NullPointerException) {
+ return STATUS_BAD_VALUE;
+ }
+ // This is not one of the conditions represented by our error code, escalate to a
+ // RuntimeException.
+ Log.e(TAG, "Escalating unexpected exception: ", e);
+ throw new RuntimeException(e);
+ }
+
+ /**
* Returns a list of descriptors for all hardware modules loaded.
* @param modules A ModuleProperties array where the list will be returned.
* @return - {@link #STATUS_OK} in case of success
@@ -1569,9 +1738,8 @@
modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
}
return STATUS_OK;
- } catch (RemoteException e) {
- Log.e(TAG, "Exception caught", e);
- return STATUS_DEAD_OBJECT;
+ } catch (Exception e) {
+ return handleException(e);
}
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 7291419..9bd3992 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -78,7 +78,7 @@
mService = null;
}
} catch (Exception e) {
- handleException(e);
+ SoundTrigger.handleException(e);
}
}
@@ -115,7 +115,7 @@
}
return SoundTrigger.STATUS_BAD_VALUE;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -137,7 +137,7 @@
mService.unloadModel(soundModelHandle);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -166,7 +166,7 @@
ConversionUtil.api2aidlRecognitionConfig(config));
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -189,7 +189,7 @@
mService.stopRecognition(soundModelHandle);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -214,7 +214,7 @@
mService.forceRecognitionEvent(soundModelHandle);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -242,7 +242,7 @@
ConversionUtil.api2aidlModelParameter(modelParam), value);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -296,23 +296,6 @@
}
}
- private int handleException(Exception e) {
- Log.e(TAG, "", e);
- if (e instanceof NullPointerException) {
- return SoundTrigger.STATUS_NO_INIT;
- }
- if (e instanceof RemoteException) {
- return SoundTrigger.STATUS_DEAD_OBJECT;
- }
- if (e instanceof IllegalArgumentException) {
- return SoundTrigger.STATUS_BAD_VALUE;
- }
- if (e instanceof IllegalStateException) {
- return SoundTrigger.STATUS_INVALID_OPERATION;
- }
- return SoundTrigger.STATUS_ERROR;
- }
-
private class EventHandlerDelegate extends ISoundTriggerCallback.Stub implements
IBinder.DeathRecipient {
private final Handler mHandler;
@@ -370,6 +353,12 @@
}
@Override
+ public synchronized void onModuleDied() {
+ Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
+ mHandler.sendMessage(m);
+ }
+
+ @Override
public synchronized void binderDied() {
Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
mHandler.sendMessage(m);
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index a66fcae..fb35b4b 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -60,6 +60,18 @@
@SystemApi
@TestApi
public static final int APP_RETURN_WANTED_AS_IS = 2;
+ /** Event offset of request codes from captive portal application. */
+ private static final int APP_REQUEST_BASE = 100;
+ /**
+ * Request code from the captive portal application, indicating that the network condition may
+ * have changed and the network should be re-validated.
+ * @see ICaptivePortal#appRequest(int)
+ * @see android.net.INetworkMonitor#forceReevaluation(int)
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int APP_REQUEST_REEVALUATION_REQUIRED = APP_REQUEST_BASE + 0;
private final IBinder mBinder;
@@ -136,6 +148,19 @@
}
/**
+ * Request that the system reevaluates the captive portal status.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public void reevaluateNetwork() {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Log a captive portal login event.
* @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto.
* @param packageName captive portal application package name.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 03d4200..8ba3131 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -517,7 +517,7 @@
* The absence of a connection type.
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ @SystemApi
public static final int TYPE_NONE = -1;
/**
@@ -662,7 +662,7 @@
* {@hide}
*/
@Deprecated
- @UnsupportedAppUsage
+ @SystemApi
public static final int TYPE_WIFI_P2P = 13;
/**
@@ -3121,8 +3121,6 @@
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
public int registerNetworkProvider(@NonNull NetworkProvider provider) {
if (provider.getProviderId() != NetworkProvider.ID_NONE) {
- // TODO: Provide a better method for checking this by moving NetworkFactory.SerialNumber
- // out of NetworkFactory.
throw new IllegalStateException("NetworkProviders can only be registered once");
}
@@ -3171,26 +3169,24 @@
/**
* @hide
* Register a NetworkAgent with ConnectivityService.
- * @return NetID corresponding to NetworkAgent.
+ * @return Network corresponding to NetworkAgent.
*/
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
- public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
- NetworkCapabilities nc, int score, NetworkMisc misc) {
- return registerNetworkAgent(messenger, ni, lp, nc, score, misc,
- NetworkFactory.SerialNumber.NONE);
+ public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+ NetworkCapabilities nc, int score, NetworkAgentConfig config) {
+ return registerNetworkAgent(messenger, ni, lp, nc, score, config, NetworkProvider.ID_NONE);
}
/**
* @hide
* Register a NetworkAgent with ConnectivityService.
- * @return NetID corresponding to NetworkAgent.
+ * @return Network corresponding to NetworkAgent.
*/
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
- public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
- NetworkCapabilities nc, int score, NetworkMisc misc, int factorySerialNumber) {
+ public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+ NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) {
try {
- return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc,
- factorySerialNumber);
+ return mService.registerNetworkAgent(messenger, ni, lp, nc, score, config, providerId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3626,14 +3622,26 @@
/**
* Helper function to request a network with a particular legacy type.
*
- * This is temporarily public @hide so it can be called by system code that uses the
- * NetworkRequest API to request networks but relies on CONNECTIVITY_ACTION broadcasts for
- * instead network notifications.
+ * @deprecated This is temporarily public for tethering to backwards compatibility that uses
+ * the NetworkRequest API to request networks with legacy type and relies on
+ * CONNECTIVITY_ACTION broadcasts instead of NetworkCallbacks. New caller should use
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} instead.
*
* TODO: update said system code to rely on NetworkCallbacks and make this method private.
+
+ * @param request {@link NetworkRequest} describing this request.
+ * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+ * the callback must not be shared - it uniquely specifies this request.
+ * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
+ * before {@link NetworkCallback#onUnavailable()} is called. The timeout must
+ * be a positive value (i.e. >0).
+ * @param legacyType to specify the network type(#TYPE_*).
+ * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
*
* @hide
*/
+ @SystemApi
+ @Deprecated
public void requestNetwork(@NonNull NetworkRequest request,
@NonNull NetworkCallback networkCallback, int timeoutMs, int legacyType,
@NonNull Handler handler) {
diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl
index 707b4f6..fe21905 100644
--- a/core/java/android/net/ICaptivePortal.aidl
+++ b/core/java/android/net/ICaptivePortal.aidl
@@ -21,6 +21,7 @@
* @hide
*/
oneway interface ICaptivePortal {
+ void appRequest(int request);
void appResponse(int response);
void logEvent(int eventId, String packageName);
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index e6a0379..186196bd 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -20,9 +20,9 @@
import android.net.ConnectionInfo;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
import android.net.NetworkState;
@@ -152,8 +152,9 @@
void declareNetworkRequestUnfulfillable(in NetworkRequest request);
- int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
- in NetworkCapabilities nc, int score, in NetworkMisc misc, in int factorySerialNumber);
+ Network registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
+ in NetworkCapabilities nc, int score, in NetworkAgentConfig config,
+ in int factorySerialNumber);
NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
in Messenger messenger, int timeoutSec, in IBinder binder, int legacy);
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 385cb1d..72a6b39 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -57,9 +57,6 @@
@UnsupportedAppUsage
boolean getRestrictBackground();
- /** Callback used to change internal state on tethering */
- void onTetheringChanged(String iface, boolean tethering);
-
/** Gets the restrict background status based on the caller's UID:
1 - disabled
2 - whitelisted
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 9994f9f..5fa515a 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -23,6 +23,8 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.os.IBinder;
import android.os.Messenger;
import com.android.internal.net.VpnInfo;
@@ -89,4 +91,7 @@
/** Get the total network stats information since boot */
long getTotalStats(int type);
+ /** Registers a network stats provider */
+ INetworkStatsProviderCallback registerNetworkStatsProvider(String tag,
+ in INetworkStatsProvider provider);
}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 2792c56..be8e561 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -21,6 +21,8 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.LinkPropertiesUtils;
+import android.net.util.LinkPropertiesUtils.CompareResult;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -84,36 +86,6 @@
/**
* @hide
*/
- public static class CompareResult<T> {
- public final List<T> removed = new ArrayList<>();
- public final List<T> added = new ArrayList<>();
-
- public CompareResult() {}
-
- public CompareResult(Collection<T> oldItems, Collection<T> newItems) {
- if (oldItems != null) {
- removed.addAll(oldItems);
- }
- if (newItems != null) {
- for (T newItem : newItems) {
- if (!removed.remove(newItem)) {
- added.add(newItem);
- }
- }
- }
- }
-
- @Override
- public String toString() {
- return "removed=[" + TextUtils.join(",", removed)
- + "] added=[" + TextUtils.join(",", added)
- + "]";
- }
- }
-
- /**
- * @hide
- */
@UnsupportedAppUsage(implicitMember =
"values()[Landroid/net/LinkProperties$ProvisioningChange;")
public enum ProvisioningChange {
@@ -1295,7 +1267,7 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalInterfaceName(@NonNull LinkProperties target) {
- return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
+ return LinkPropertiesUtils.isIdenticalInterfaceName(target, this);
}
/**
@@ -1318,10 +1290,7 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalAddresses(@NonNull LinkProperties target) {
- Collection<InetAddress> targetAddresses = target.getAddresses();
- Collection<InetAddress> sourceAddresses = getAddresses();
- return (sourceAddresses.size() == targetAddresses.size()) ?
- sourceAddresses.containsAll(targetAddresses) : false;
+ return LinkPropertiesUtils.isIdenticalAddresses(target, this);
}
/**
@@ -1333,15 +1302,7 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalDnses(@NonNull LinkProperties target) {
- Collection<InetAddress> targetDnses = target.getDnsServers();
- String targetDomains = target.getDomains();
- if (mDomains == null) {
- if (targetDomains != null) return false;
- } else {
- if (!mDomains.equals(targetDomains)) return false;
- }
- return (mDnses.size() == targetDnses.size()) ?
- mDnses.containsAll(targetDnses) : false;
+ return LinkPropertiesUtils.isIdenticalDnses(target, this);
}
/**
@@ -1394,9 +1355,7 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalRoutes(@NonNull LinkProperties target) {
- Collection<RouteInfo> targetRoutes = target.getRoutes();
- return (mRoutes.size() == targetRoutes.size()) ?
- mRoutes.containsAll(targetRoutes) : false;
+ return LinkPropertiesUtils.isIdenticalRoutes(target, this);
}
/**
@@ -1408,8 +1367,7 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public boolean isIdenticalHttpProxy(@NonNull LinkProperties target) {
- return getHttpProxy() == null ? target.getHttpProxy() == null :
- getHttpProxy().equals(target.getHttpProxy());
+ return LinkPropertiesUtils.isIdenticalHttpProxy(target, this);
}
/**
@@ -1541,26 +1499,6 @@
}
/**
- * Compares the addresses in this LinkProperties with another
- * LinkProperties, examining only addresses on the base link.
- *
- * @param target a LinkProperties with the new list of addresses
- * @return the differences between the addresses.
- * @hide
- */
- public @NonNull CompareResult<LinkAddress> compareAddresses(@Nullable LinkProperties target) {
- /*
- * Duplicate the LinkAddresses into removed, we will be removing
- * address which are common between mLinkAddresses and target
- * leaving the addresses that are different. And address which
- * are in target but not in mLinkAddresses are placed in the
- * addedAddresses.
- */
- return new CompareResult<>(mLinkAddresses,
- target != null ? target.getLinkAddresses() : null);
- }
-
- /**
* Compares the DNS addresses in this LinkProperties with another
* LinkProperties, examining only DNS addresses on the base link.
*
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 74c9aac..0e10c42 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -20,11 +20,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.MacAddressUtils;
import android.net.wifi.WifiInfo;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.BitUtils;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -33,7 +33,6 @@
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.Arrays;
-import java.util.Random;
/**
* Representation of a MAC address.
@@ -109,21 +108,13 @@
if (equals(BROADCAST_ADDRESS)) {
return TYPE_BROADCAST;
}
- if (isMulticastAddress()) {
+ if ((mAddr & MULTICAST_MASK) != 0) {
return TYPE_MULTICAST;
}
return TYPE_UNICAST;
}
/**
- * @return true if this MacAddress is a multicast address.
- * @hide
- */
- public boolean isMulticastAddress() {
- return (mAddr & MULTICAST_MASK) != 0;
- }
-
- /**
* @return true if this MacAddress is a locally assigned address.
*/
public boolean isLocallyAssigned() {
@@ -192,7 +183,7 @@
* @hide
*/
public static boolean isMacAddress(byte[] addr) {
- return addr != null && addr.length == ETHER_ADDR_LEN;
+ return MacAddressUtils.isMacAddress(addr);
}
/**
@@ -261,26 +252,11 @@
}
private static byte[] byteAddrFromLongAddr(long addr) {
- byte[] bytes = new byte[ETHER_ADDR_LEN];
- int index = ETHER_ADDR_LEN;
- while (index-- > 0) {
- bytes[index] = (byte) addr;
- addr = addr >> 8;
- }
- return bytes;
+ return MacAddressUtils.byteAddrFromLongAddr(addr);
}
private static long longAddrFromByteAddr(byte[] addr) {
- Preconditions.checkNotNull(addr);
- if (!isMacAddress(addr)) {
- throw new IllegalArgumentException(
- Arrays.toString(addr) + " was not a valid MAC address");
- }
- long longAddr = 0;
- for (byte b : addr) {
- longAddr = (longAddr << 8) + BitUtils.uint8(b);
- }
- return longAddr;
+ return MacAddressUtils.longAddrFromByteAddr(addr);
}
// Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr))
@@ -350,50 +326,7 @@
* @hide
*/
public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() {
- return createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
- }
-
- /**
- * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the
- * unicast bit, are randomly selected.
- *
- * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
- *
- * @return a random locally assigned, unicast MacAddress.
- *
- * @hide
- */
- public static @NonNull MacAddress createRandomUnicastAddress() {
- return createRandomUnicastAddress(null, new SecureRandom());
- }
-
- /**
- * Returns a randomly generated MAC address using the given Random object and the same
- * OUI values as the given MacAddress.
- *
- * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
- *
- * @param base a base MacAddress whose OUI is used for generating the random address.
- * If base == null then the OUI will also be randomized.
- * @param r a standard Java Random object used for generating the random address.
- * @return a random locally assigned MacAddress.
- *
- * @hide
- */
- public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
- long addr;
- if (base == null) {
- addr = r.nextLong() & VALID_LONG_MASK;
- } else {
- addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
- }
- addr |= LOCALLY_ASSIGNED_MASK;
- addr &= ~MULTICAST_MASK;
- MacAddress mac = new MacAddress(addr);
- if (mac.equals(DEFAULT_MAC_ADDRESS)) {
- return createRandomUnicastAddress(base, r);
- }
- return mac;
+ return MacAddressUtils.createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
}
// Convenience function for working around the lack of byte literals.
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java
index 3fb52f1..bd39c13 100644
--- a/core/java/android/net/NattKeepalivePacketData.java
+++ b/core/java/android/net/NattKeepalivePacketData.java
@@ -16,9 +16,11 @@
package android.net;
-import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
-import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
+import static android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS;
+import static android.net.InvalidPacketException.ERROR_INVALID_PORT;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.net.util.IpUtils;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,20 +32,22 @@
import java.nio.ByteOrder;
/** @hide */
+@SystemApi
public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable {
private static final int IPV4_HEADER_LENGTH = 20;
private static final int UDP_HEADER_LENGTH = 8;
// This should only be constructed via static factory methods, such as
// nattKeepalivePacket
- private NattKeepalivePacketData(InetAddress srcAddress, int srcPort,
- InetAddress dstAddress, int dstPort, byte[] data) throws
+ public NattKeepalivePacketData(@NonNull InetAddress srcAddress, int srcPort,
+ @NonNull InetAddress dstAddress, int dstPort, @NonNull byte[] data) throws
InvalidPacketException {
super(srcAddress, srcPort, dstAddress, dstPort, data);
}
/**
* Factory method to create Nat-T keepalive packet structure.
+ * @hide
*/
public static NattKeepalivePacketData nattKeepalivePacket(
InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort)
@@ -87,7 +91,7 @@
}
/** Write to parcel */
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeString(srcAddress.getHostAddress());
out.writeString(dstAddress.getHostAddress());
out.writeInt(srcPort);
@@ -95,7 +99,7 @@
}
/** Parcelable Creator */
- public static final Parcelable.Creator<NattKeepalivePacketData> CREATOR =
+ public static final @NonNull Parcelable.Creator<NattKeepalivePacketData> CREATOR =
new Parcelable.Creator<NattKeepalivePacketData>() {
public NattKeepalivePacketData createFromParcel(Parcel in) {
final InetAddress srcAddress =
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 60bd573..aae9fd4 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -17,6 +17,8 @@
package android.net;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
@@ -43,11 +45,15 @@
*
* @hide
*/
-public abstract class NetworkAgent extends Handler {
- // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown
- // an exception.
- public final int netId;
+@SystemApi
+public abstract class NetworkAgent {
+ /**
+ * The {@link Network} corresponding to this object.
+ */
+ @NonNull
+ public final Network network;
+ private final Handler mHandler;
private volatile AsyncChannel mAsyncChannel;
private final String LOG_TAG;
private static final boolean DBG = true;
@@ -56,9 +62,14 @@
private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
private volatile long mLastBwRefreshTime = 0;
private static final long BW_REFRESH_MIN_WIN_MS = 500;
- private boolean mPollLceScheduled = false;
- private AtomicBoolean mPollLcePending = new AtomicBoolean(false);
- public final int mFactorySerialNumber;
+ private boolean mBandwidthUpdateScheduled = false;
+ private AtomicBoolean mBandwidthUpdatePending = new AtomicBoolean(false);
+
+ /**
+ * The ID of the {@link NetworkProvider} that created this object, or
+ * {@link NetworkProvider#ID_NONE} if unknown.
+ */
+ public final int providerId;
private static final int BASE = Protocol.BASE_NETWORK_AGENT;
@@ -66,6 +77,7 @@
* Sent by ConnectivityService to the NetworkAgent to inform it of
* suspected connectivity problems on its network. The NetworkAgent
* should take steps to verify and correct connectivity.
+ * @hide
*/
public static final int CMD_SUSPECT_BAD = BASE;
@@ -74,6 +86,7 @@
* ConnectivityService to pass the current NetworkInfo (connection state).
* Sent when the NetworkInfo changes, mainly due to change of state.
* obj = NetworkInfo
+ * @hide
*/
public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;
@@ -81,6 +94,7 @@
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkCapabilties.
* obj = NetworkCapabilities
+ * @hide
*/
public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;
@@ -88,11 +102,14 @@
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkProperties.
* obj = NetworkProperties
+ * @hide
*/
public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
- /* centralize place where base network score, and network score scaling, will be
+ /**
+ * Centralize the place where base network score, and network score scaling, will be
* stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE
+ * @hide
*/
public static final int WIFI_BASE_SCORE = 60;
@@ -100,6 +117,7 @@
* Sent by the NetworkAgent to ConnectivityService to pass the current
* network score.
* obj = network score Integer
+ * @hide
*/
public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
@@ -112,12 +130,33 @@
* obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String}
* representing URL that Internet probe was redirect to, if it was redirected,
* or mapping to {@code null} otherwise.
+ * @hide
*/
public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7;
+
+ /**
+ * Network validation suceeded.
+ * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}.
+ */
+ public static final int VALIDATION_STATUS_VALID = 1;
+
+ /**
+ * Network validation was attempted and failed. This may be received more than once as
+ * subsequent validation attempts are made.
+ */
+ public static final int VALIDATION_STATUS_NOT_VALID = 2;
+
+ // TODO: remove.
+ /** @hide */
public static final int VALID_NETWORK = 1;
+ /** @hide */
public static final int INVALID_NETWORK = 2;
+ /**
+ * The key for the redirect URL in the Bundle argument of {@code CMD_REPORT_NETWORK_STATUS}.
+ * @hide
+ */
public static String REDIRECT_URL_KEY = "redirect URL";
/**
@@ -126,6 +165,7 @@
* CONNECTED so it can be given special treatment at that time.
*
* obj = boolean indicating whether to use this network even if unvalidated
+ * @hide
*/
public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8;
@@ -136,12 +176,14 @@
* responsibility to remember it.
*
* arg1 = 1 if true, 0 if false
+ * @hide
*/
public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;
/**
* Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
* the underlying network connection for updated bandwidth information.
+ * @hide
*/
public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
@@ -154,6 +196,7 @@
* obj = KeepalivePacketData object describing the data to be sent
*
* Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+ * @hide
*/
public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11;
@@ -163,6 +206,7 @@
* arg1 = slot number of the keepalive to stop.
*
* Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+ * @hide
*/
public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12;
@@ -176,6 +220,7 @@
*
* arg1 = slot number of the keepalive
* arg2 = error code
+ * @hide
*/
public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13;
@@ -184,6 +229,7 @@
* that when crossed should trigger a system wakeup and a NetworkCapabilities update.
*
* obj = int[] describing signal strength thresholds.
+ * @hide
*/
public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14;
@@ -191,6 +237,7 @@
* Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid
* automatically reconnecting to this network (e.g. via autojoin). Happens
* when user selects "No" option on the "Stay connected?" dialog box.
+ * @hide
*/
public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15;
@@ -203,6 +250,7 @@
* This does not happen with UDP, so this message is TCP-specific.
* arg1 = slot number of the keepalive to filter for.
* obj = the keepalive packet to send repeatedly.
+ * @hide
*/
public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16;
@@ -210,6 +258,7 @@
* Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See
* {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}.
* arg1 = slot number of the keepalive packet filter to remove.
+ * @hide
*/
public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;
@@ -217,27 +266,32 @@
// of dependent changes that would conflict throughout the automerger graph. Having these
// temporarily helps with the process of going through with all these dependent changes across
// the entire tree.
+ /** @hide TODO: decide which of these to expose. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score) {
- this(looper, context, logTag, ni, nc, lp, score, null, NetworkFactory.SerialNumber.NONE);
- }
- public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
- NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
- this(looper, context, logTag, ni, nc, lp, score, misc, NetworkFactory.SerialNumber.NONE);
+ this(looper, context, logTag, ni, nc, lp, score, null, NetworkProvider.ID_NONE);
}
+ /** @hide TODO: decide which of these to expose. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
- NetworkCapabilities nc, LinkProperties lp, int score, int factorySerialNumber) {
- this(looper, context, logTag, ni, nc, lp, score, null, factorySerialNumber);
+ NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config) {
+ this(looper, context, logTag, ni, nc, lp, score, config, NetworkProvider.ID_NONE);
}
+ /** @hide TODO: decide which of these to expose. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
- NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc,
- int factorySerialNumber) {
- super(looper);
+ NetworkCapabilities nc, LinkProperties lp, int score, int providerId) {
+ this(looper, context, logTag, ni, nc, lp, score, null, providerId);
+ }
+
+ /** @hide TODO: decide which of these to expose. */
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config,
+ int providerId) {
+ mHandler = new NetworkAgentHandler(looper);
LOG_TAG = logTag;
mContext = context;
- mFactorySerialNumber = factorySerialNumber;
+ this.providerId = providerId;
if (ni == null || nc == null || lp == null) {
throw new IllegalArgumentException();
}
@@ -245,117 +299,126 @@
if (VDBG) log("Registering NetworkAgent");
ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
- netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
- new LinkProperties(lp), new NetworkCapabilities(nc), score, misc,
- factorySerialNumber);
+ network = cm.registerNetworkAgent(new Messenger(mHandler), new NetworkInfo(ni),
+ new LinkProperties(lp), new NetworkCapabilities(nc), score, config,
+ providerId);
}
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
- if (mAsyncChannel != null) {
- log("Received new connection while already connected!");
- } else {
- if (VDBG) log("NetworkAgent fully connected");
- AsyncChannel ac = new AsyncChannel();
- ac.connected(null, this, msg.replyTo);
- ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
- AsyncChannel.STATUS_SUCCESSFUL);
- synchronized (mPreConnectedQueue) {
- mAsyncChannel = ac;
- for (Message m : mPreConnectedQueue) {
- ac.sendMessage(m);
- }
- mPreConnectedQueue.clear();
- }
- }
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
- if (VDBG) log("CMD_CHANNEL_DISCONNECT");
- if (mAsyncChannel != null) mAsyncChannel.disconnect();
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
- if (DBG) log("NetworkAgent channel lost");
- // let the client know CS is done with us.
- unwanted();
- synchronized (mPreConnectedQueue) {
- mAsyncChannel = null;
- }
- break;
- }
- case CMD_SUSPECT_BAD: {
- log("Unhandled Message " + msg);
- break;
- }
- case CMD_REQUEST_BANDWIDTH_UPDATE: {
- long currentTimeMs = System.currentTimeMillis();
- if (VDBG) {
- log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
- }
- if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
- mPollLceScheduled = false;
- if (mPollLcePending.getAndSet(true) == false) {
- pollLceData();
- }
- } else {
- // deliver the request at a later time rather than discard it completely.
- if (!mPollLceScheduled) {
- long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS -
- currentTimeMs + 1;
- mPollLceScheduled = sendEmptyMessageDelayed(
- CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
- }
- }
- break;
- }
- case CMD_REPORT_NETWORK_STATUS: {
- String redirectUrl = ((Bundle)msg.obj).getString(REDIRECT_URL_KEY);
- if (VDBG) {
- log("CMD_REPORT_NETWORK_STATUS(" +
- (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl);
- }
- networkStatus(msg.arg1, redirectUrl);
- break;
- }
- case CMD_SAVE_ACCEPT_UNVALIDATED: {
- saveAcceptUnvalidated(msg.arg1 != 0);
- break;
- }
- case CMD_START_SOCKET_KEEPALIVE: {
- startSocketKeepalive(msg);
- break;
- }
- case CMD_STOP_SOCKET_KEEPALIVE: {
- stopSocketKeepalive(msg);
- break;
- }
+ private class NetworkAgentHandler extends Handler {
+ NetworkAgentHandler(Looper looper) {
+ super(looper);
+ }
- case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
- ArrayList<Integer> thresholds =
- ((Bundle) msg.obj).getIntegerArrayList("thresholds");
- // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
- // rather than convert to int[].
- int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
- for (int i = 0; i < intThresholds.length; i++) {
- intThresholds[i] = thresholds.get(i);
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+ if (mAsyncChannel != null) {
+ log("Received new connection while already connected!");
+ } else {
+ if (VDBG) log("NetworkAgent fully connected");
+ AsyncChannel ac = new AsyncChannel();
+ ac.connected(null, this, msg.replyTo);
+ ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_SUCCESSFUL);
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = ac;
+ for (Message m : mPreConnectedQueue) {
+ ac.sendMessage(m);
+ }
+ mPreConnectedQueue.clear();
+ }
+ }
+ break;
}
- setSignalStrengthThresholds(intThresholds);
- break;
- }
- case CMD_PREVENT_AUTOMATIC_RECONNECT: {
- preventAutomaticReconnect();
- break;
- }
- case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
- addKeepalivePacketFilter(msg);
- break;
- }
- case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
- removeKeepalivePacketFilter(msg);
- break;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+ if (VDBG) log("CMD_CHANNEL_DISCONNECT");
+ if (mAsyncChannel != null) mAsyncChannel.disconnect();
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ if (DBG) log("NetworkAgent channel lost");
+ // let the client know CS is done with us.
+ onNetworkUnwanted();
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = null;
+ }
+ break;
+ }
+ case CMD_SUSPECT_BAD: {
+ log("Unhandled Message " + msg);
+ break;
+ }
+ case CMD_REQUEST_BANDWIDTH_UPDATE: {
+ long currentTimeMs = System.currentTimeMillis();
+ if (VDBG) {
+ log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
+ }
+ if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
+ mBandwidthUpdateScheduled = false;
+ if (!mBandwidthUpdatePending.getAndSet(true)) {
+ onBandwidthUpdateRequested();
+ }
+ } else {
+ // deliver the request at a later time rather than discard it completely.
+ if (!mBandwidthUpdateScheduled) {
+ long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS
+ - currentTimeMs + 1;
+ mBandwidthUpdateScheduled = sendEmptyMessageDelayed(
+ CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
+ }
+ }
+ break;
+ }
+ case CMD_REPORT_NETWORK_STATUS: {
+ String redirectUrl = ((Bundle) msg.obj).getString(REDIRECT_URL_KEY);
+ if (VDBG) {
+ log("CMD_REPORT_NETWORK_STATUS("
+ + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ")
+ + redirectUrl);
+ }
+ onValidationStatus(msg.arg1 /* status */, redirectUrl);
+ break;
+ }
+ case CMD_SAVE_ACCEPT_UNVALIDATED: {
+ onSaveAcceptUnvalidated(msg.arg1 != 0);
+ break;
+ }
+ case CMD_START_SOCKET_KEEPALIVE: {
+ onStartSocketKeepalive(msg.arg1 /* slot */, msg.arg2 /* interval */,
+ (KeepalivePacketData) msg.obj /* packet */);
+ break;
+ }
+ case CMD_STOP_SOCKET_KEEPALIVE: {
+ onStopSocketKeepalive(msg.arg1 /* slot */);
+ break;
+ }
+
+ case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
+ ArrayList<Integer> thresholds =
+ ((Bundle) msg.obj).getIntegerArrayList("thresholds");
+ // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
+ // rather than convert to int[].
+ int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
+ for (int i = 0; i < intThresholds.length; i++) {
+ intThresholds[i] = thresholds.get(i);
+ }
+ onSignalStrengthThresholdsUpdated(intThresholds);
+ break;
+ }
+ case CMD_PREVENT_AUTOMATIC_RECONNECT: {
+ onAutomaticReconnectDisabled();
+ break;
+ }
+ case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
+ onAddKeepalivePacketFilter(msg.arg1 /* slot */,
+ (KeepalivePacketData) msg.obj /* packet */);
+ break;
+ }
+ case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
+ onRemoveKeepalivePacketFilter(msg.arg1 /* slot */);
+ break;
+ }
}
}
}
@@ -388,14 +451,16 @@
}
/**
- * Called by the bearer code when it has new LinkProperties data.
+ * Must be called by the agent when the network's {@link LinkProperties} change.
+ * @param linkProperties the new LinkProperties.
*/
- public void sendLinkProperties(LinkProperties linkProperties) {
+ public void sendLinkProperties(@NonNull LinkProperties linkProperties) {
queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties));
}
/**
- * Called by the bearer code when it has new NetworkInfo data.
+ * Must be called by the agent when it has a new NetworkInfo object.
+ * @hide TODO: expose something better.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public void sendNetworkInfo(NetworkInfo networkInfo) {
@@ -403,17 +468,19 @@
}
/**
- * Called by the bearer code when it has new NetworkCapabilities data.
+ * Must be called by the agent when the network's {@link NetworkCapabilities} change.
+ * @param networkCapabilities the new NetworkCapabilities.
*/
- public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
- mPollLcePending.set(false);
+ public void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+ mBandwidthUpdatePending.set(false);
mLastBwRefreshTime = System.currentTimeMillis();
queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
new NetworkCapabilities(networkCapabilities));
}
/**
- * Called by the bearer code when it has a new score for this network.
+ * Must be called by the agent to update the score of this network.
+ * @param score the new score.
*/
public void sendNetworkScore(int score) {
if (score < 0) {
@@ -425,14 +492,16 @@
}
/**
- * Called by the bearer code when it has a new NetworkScore for this network.
+ * Must be called by the agent when it has a new {@link NetworkScore} for this network.
+ * @param ns the new score.
+ * @hide TODO: unhide the NetworkScore class, and rename to sendNetworkScore.
*/
public void updateScore(@NonNull NetworkScore ns) {
queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new NetworkScore(ns));
}
/**
- * Called by the bearer to indicate this network was manually selected by the user.
+ * Must be called by the agent to indicate this network was manually selected by the user.
* This should be called before the NetworkInfo is marked CONNECTED so that this
* Network can be given special treatment at that time. If {@code acceptUnvalidated} is
* {@code true}, then the system will switch to this network. If it is {@code false} and the
@@ -441,15 +510,16 @@
* {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever
* calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement
* {@link #saveAcceptUnvalidated} to respect the user's choice.
+ * @hide should move to NetworkAgentConfig.
*/
public void explicitlySelected(boolean acceptUnvalidated) {
explicitlySelected(true /* explicitlySelected */, acceptUnvalidated);
}
/**
- * Called by the bearer to indicate whether the network was manually selected by the user.
- * This should be called before the NetworkInfo is marked CONNECTED so that this
- * Network can be given special treatment at that time.
+ * Must be called by the agent to indicate whether the network was manually selected by the
+ * user. This should be called before the network becomes connected, so it can be given
+ * special treatment when it does.
*
* If {@code explicitlySelected} is {@code true}, and {@code acceptUnvalidated} is {@code true},
* then the system will switch to this network. If {@code explicitlySelected} is {@code true}
@@ -464,6 +534,7 @@
* {@code true}, the system will interpret this as the user having accepted partial connectivity
* on this network. Thus, the system will switch to the network and consider it validated even
* if it only provides partial connectivity, but the network is not otherwise treated specially.
+ * @hide should move to NetworkAgentConfig.
*/
public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED,
@@ -477,73 +548,126 @@
* as well, either canceling NetworkRequests or altering their score such that this
* network won't be immediately requested again.
*/
- abstract protected void unwanted();
+ public void onNetworkUnwanted() {
+ unwanted();
+ }
+ /** @hide TODO delete once subclasses have moved to onNetworkUnwanted. */
+ protected void unwanted() {
+ }
/**
* Called when ConnectivityService request a bandwidth update. The parent factory
* shall try to overwrite this method and produce a bandwidth update if capable.
*/
+ public void onBandwidthUpdateRequested() {
+ pollLceData();
+ }
+ /** @hide TODO delete once subclasses have moved to onBandwidthUpdateRequested. */
protected void pollLceData() {
}
/**
* Called when the system determines the usefulness of this network.
*
- * Networks claiming internet connectivity will have their internet
- * connectivity verified.
+ * The system attempts to validate Internet connectivity on networks that provide the
+ * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability.
*
* Currently there are two possible values:
- * {@code VALID_NETWORK} if the system is happy with the connection,
- * {@code INVALID_NETWORK} if the system is not happy.
- * TODO - add indications of captive portal-ness and related success/failure,
- * ie, CAPTIVE_SUCCESS_NETWORK, CAPTIVE_NETWORK for successful login and detection
+ * {@code VALIDATION_STATUS_VALID} if Internet connectivity was validated,
+ * {@code VALIDATION_STATUS_NOT_VALID} if Internet connectivity was not validated.
*
- * This may be called multiple times as the network status changes and may
- * generate false negatives if we lose ip connectivity before the link is torn down.
+ * This may be called multiple times as network status changes, or if there are multiple
+ * subsequent attempts to validate connectivity that fail.
*
- * @param status one of {@code VALID_NETWORK} or {@code INVALID_NETWORK}.
- * @param redirectUrl If the Internet probe was redirected, this is the destination it was
- * redirected to, otherwise {@code null}.
+ * @param status one of {@code VALIDATION_STATUS_VALID} or {@code VALIDATION_STATUS_NOT_VALID}.
+ * @param redirectUrl If Internet connectivity is being redirected (e.g., on a captive portal),
+ * this is the destination the probes are being redirected to, otherwise {@code null}.
*/
+ public void onValidationStatus(int status, @Nullable String redirectUrl) {
+ networkStatus(status, redirectUrl);
+ }
+ /** @hide TODO delete once subclasses have moved to onValidationStatus */
protected void networkStatus(int status, String redirectUrl) {
}
+
/**
* Called when the user asks to remember the choice to use this network even if unvalidated.
* The transport is responsible for remembering the choice, and the next time the user connects
* to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}.
* This method will only be called if {@link #explicitlySelected} was called with
* {@code acceptUnvalidated} set to {@code false}.
+ * @param accept whether the user wants to use the network even if unvalidated.
*/
+ public void onSaveAcceptUnvalidated(boolean accept) {
+ saveAcceptUnvalidated(accept);
+ }
+ /** @hide TODO delete once subclasses have moved to onSaveAcceptUnvalidated */
protected void saveAcceptUnvalidated(boolean accept) {
}
/**
* Requests that the network hardware send the specified packet at the specified interval.
+ *
+ * @param slot the hardware slot on which to start the keepalive.
+ * @param intervalSeconds the interval between packets
+ * @param packet the packet to send.
*/
+ public void onStartSocketKeepalive(int slot, int intervalSeconds,
+ @NonNull KeepalivePacketData packet) {
+ Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot, intervalSeconds,
+ packet);
+ startSocketKeepalive(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onStartSocketKeepalive */
protected void startSocketKeepalive(Message msg) {
onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
- * Requests that the network hardware send the specified packet at the specified interval.
+ * Requests that the network hardware stop a previously-started keepalive.
+ *
+ * @param slot the hardware slot on which to stop the keepalive.
*/
+ public void onStopSocketKeepalive(int slot) {
+ Message msg = mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0, null);
+ stopSocketKeepalive(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onStopSocketKeepalive */
protected void stopSocketKeepalive(Message msg) {
onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
- * Called by the network when a socket keepalive event occurs.
+ * Must be called by the agent when a socket keepalive event occurs.
+ *
+ * @param slot the hardware slot on which the event occurred.
+ * @param event the event that occurred.
*/
+ public void sendSocketKeepaliveEvent(int slot, int event) {
+ queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, event);
+ }
+ /** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */
public void onSocketKeepaliveEvent(int slot, int reason) {
- queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, reason);
+ sendSocketKeepaliveEvent(slot, reason);
}
/**
* Called by ConnectivityService to add specific packet filter to network hardware to block
- * ACKs matching the sent keepalive packets. Implementations that support this feature must
- * override this method.
+ * replies (e.g., TCP ACKs) matching the sent keepalive packets. Implementations that support
+ * this feature must override this method.
+ *
+ * @param slot the hardware slot on which the keepalive should be sent.
+ * @param packet the packet that is being sent.
*/
+ public void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet) {
+ Message msg = mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0, packet);
+ addKeepalivePacketFilter(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onAddKeepalivePacketFilter */
protected void addKeepalivePacketFilter(Message msg) {
}
@@ -551,14 +675,28 @@
* Called by ConnectivityService to remove a packet filter installed with
* {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature
* must override this method.
+ *
+ * @param slot the hardware slot on which the keepalive is being sent.
*/
+ public void onRemoveKeepalivePacketFilter(int slot) {
+ Message msg = mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, slot, 0, null);
+ removeKeepalivePacketFilter(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onRemoveKeepalivePacketFilter */
protected void removeKeepalivePacketFilter(Message msg) {
}
/**
* Called by ConnectivityService to inform this network transport of signal strength thresholds
* that when crossed should trigger a system wakeup and a NetworkCapabilities update.
+ *
+ * @param thresholds the array of thresholds that should trigger wakeups.
*/
+ public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) {
+ setSignalStrengthThresholds(thresholds);
+ }
+ /** @hide TODO delete once subclasses have moved to onSetSignalStrengthThresholds */
protected void setSignalStrengthThresholds(int[] thresholds) {
}
@@ -568,9 +706,14 @@
* responsible for making sure the device does not automatically reconnect to the same network
* after the {@code unwanted} call.
*/
+ public void onAutomaticReconnectDisabled() {
+ preventAutomaticReconnect();
+ }
+ /** @hide TODO delete once subclasses have moved to onAutomaticReconnectDisabled */
protected void preventAutomaticReconnect() {
}
+ /** @hide */
protected void log(String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
}
diff --git a/core/java/android/net/NetworkMisc.aidl b/core/java/android/net/NetworkAgentConfig.aidl
similarity index 95%
rename from core/java/android/net/NetworkMisc.aidl
rename to core/java/android/net/NetworkAgentConfig.aidl
index c65583f..cb70bdd 100644
--- a/core/java/android/net/NetworkMisc.aidl
+++ b/core/java/android/net/NetworkAgentConfig.aidl
@@ -16,4 +16,4 @@
package android.net;
-parcelable NetworkMisc;
+parcelable NetworkAgentConfig;
diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java
new file mode 100644
index 0000000..abc6b67
--- /dev/null
+++ b/core/java/android/net/NetworkAgentConfig.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Allows a network transport to provide the system with policy and configuration information about
+ * a particular network when registering a {@link NetworkAgent}. This information cannot change once
+ * the agent is registered.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NetworkAgentConfig implements Parcelable {
+
+ /**
+ * If the {@link Network} is a VPN, whether apps are allowed to bypass the
+ * VPN. This is set by a {@link VpnService} and used by
+ * {@link ConnectivityManager} when creating a VPN.
+ *
+ * @hide
+ */
+ public boolean allowBypass;
+
+ /**
+ * Set if the network was manually/explicitly connected to by the user either from settings
+ * or a 3rd party app. For example, turning on cell data is not explicit but tapping on a wifi
+ * ap in the wifi settings to trigger a connection is explicit. A 3rd party app asking to
+ * connect to a particular access point is also explicit, though this may change in the future
+ * as we want apps to use the multinetwork apis.
+ *
+ * @hide
+ */
+ public boolean explicitlySelected;
+
+ /**
+ * Set if the user desires to use this network even if it is unvalidated. This field has meaning
+ * only if {@link explicitlySelected} is true. If it is, this field must also be set to the
+ * appropriate value based on previous user choice.
+ *
+ * @hide
+ */
+ public boolean acceptUnvalidated;
+
+ /**
+ * Whether the user explicitly set that this network should be validated even if presence of
+ * only partial internet connectivity.
+ *
+ * @hide
+ */
+ public boolean acceptPartialConnectivity;
+
+ /**
+ * Set to avoid surfacing the "Sign in to network" notification.
+ * if carrier receivers/apps are registered to handle the carrier-specific provisioning
+ * procedure, a carrier specific provisioning notification will be placed.
+ * only one notification should be displayed. This field is set based on
+ * which notification should be used for provisioning.
+ *
+ * @hide
+ */
+ public boolean provisioningNotificationDisabled;
+
+ /**
+ *
+ * @return whether the sign in to network notification is enabled by this configuration.
+ */
+ public boolean isProvisioningNotificationEnabled() {
+ return !provisioningNotificationDisabled;
+ }
+
+ /**
+ * For mobile networks, this is the subscriber ID (such as IMSI).
+ *
+ * @hide
+ */
+ public String subscriberId;
+
+ /**
+ * @return the subscriber ID, or null if none.
+ */
+ @Nullable
+ public String getSubscriberId() {
+ return subscriberId;
+ }
+
+ /**
+ * Set to skip 464xlat. This means the device will treat the network as IPv6-only and
+ * will not attempt to detect a NAT64 via RFC 7050 DNS lookups.
+ *
+ * @hide
+ */
+ public boolean skip464xlat;
+
+ /**
+ * @return whether NAT64 prefix detection is enabled.
+ */
+ public boolean isNat64DetectionEnabled() {
+ return !skip464xlat;
+ }
+
+ /**
+ * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
+ * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
+ *
+ * @hide
+ */
+ public boolean hasShownBroken;
+
+ /** @hide */
+ public NetworkAgentConfig() {
+ }
+
+ /** @hide */
+ public NetworkAgentConfig(@Nullable NetworkAgentConfig nac) {
+ if (nac != null) {
+ allowBypass = nac.allowBypass;
+ explicitlySelected = nac.explicitlySelected;
+ acceptUnvalidated = nac.acceptUnvalidated;
+ subscriberId = nac.subscriberId;
+ provisioningNotificationDisabled = nac.provisioningNotificationDisabled;
+ skip464xlat = nac.skip464xlat;
+ }
+ }
+
+ /**
+ * Builder class to facilitate constructing {@link NetworkAgentConfig} objects.
+ */
+ public static class Builder {
+ private final NetworkAgentConfig mConfig = new NetworkAgentConfig();
+
+ /**
+ * Sets the subscriber ID for this network.
+ *
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setSubscriberId(@Nullable String subscriberId) {
+ mConfig.subscriberId = subscriberId;
+ return this;
+ }
+
+ /**
+ * Disables active detection of NAT64 (e.g., via RFC 7050 DNS lookups). Used to save power
+ * and reduce idle traffic on networks that are known to be IPv6-only without a NAT64.
+ *
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder disableNat64Detection() {
+ mConfig.skip464xlat = true;
+ return this;
+ }
+
+ /**
+ * Disables the "Sign in to network" notification. Used if the network transport will
+ * perform its own carrier-specific provisioning procedure.
+ *
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder disableProvisioningNotification() {
+ mConfig.provisioningNotificationDisabled = true;
+ return this;
+ }
+
+ /**
+ * Returns the constructed {@link NetworkAgentConfig} object.
+ */
+ @NonNull
+ public NetworkAgentConfig build() {
+ return mConfig;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(allowBypass ? 1 : 0);
+ out.writeInt(explicitlySelected ? 1 : 0);
+ out.writeInt(acceptUnvalidated ? 1 : 0);
+ out.writeString(subscriberId);
+ out.writeInt(provisioningNotificationDisabled ? 1 : 0);
+ out.writeInt(skip464xlat ? 1 : 0);
+ }
+
+ public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
+ new Creator<NetworkAgentConfig>() {
+ @Override
+ public NetworkAgentConfig createFromParcel(Parcel in) {
+ NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+ networkAgentConfig.allowBypass = in.readInt() != 0;
+ networkAgentConfig.explicitlySelected = in.readInt() != 0;
+ networkAgentConfig.acceptUnvalidated = in.readInt() != 0;
+ networkAgentConfig.subscriberId = in.readString();
+ networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0;
+ networkAgentConfig.skip464xlat = in.readInt() != 0;
+ return networkAgentConfig;
+ }
+
+ @Override
+ public NetworkAgentConfig[] newArray(int size) {
+ return new NetworkAgentConfig[size];
+ }
+ };
+}
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
index 42ab925..e271037 100644
--- a/core/java/android/net/NetworkFactory.java
+++ b/core/java/android/net/NetworkFactory.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
@@ -27,7 +28,6 @@
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Protocol;
@@ -52,7 +52,7 @@
* @hide
**/
public class NetworkFactory extends Handler {
- /** @hide */
+ /* TODO: delete when all callers have migrated to NetworkProvider IDs. */
public static class SerialNumber {
// Guard used by no network factory.
public static final int NONE = -1;
@@ -91,8 +91,8 @@
* with the same NetworkRequest but an updated score.
* Also, network conditions may change for this bearer
* allowing for a better score in the future.
- * msg.arg2 = the serial number of the factory currently responsible for the
- * NetworkAgent handling this request, or SerialNumber.NONE if none.
+ * msg.arg2 = the ID of the NetworkProvider currently responsible for the
+ * NetworkAgent handling this request, or NetworkProvider.ID_NONE if none.
*/
public static final int CMD_REQUEST_NETWORK = BASE;
@@ -115,16 +115,8 @@
*/
private static final int CMD_SET_FILTER = BASE + 3;
- /**
- * Sent by NetworkFactory to ConnectivityService to indicate that a request is
- * unfulfillable.
- * @see #releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest).
- */
- public static final int EVENT_UNFULFILLABLE_REQUEST = BASE + 4;
-
private final Context mContext;
private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>();
- private AsyncChannel mAsyncChannel;
private final String LOG_TAG;
private final SparseArray<NetworkRequestInfo> mNetworkRequests =
@@ -135,7 +127,8 @@
private int mRefCount = 0;
private Messenger mMessenger = null;
- private int mSerialNumber;
+ private NetworkProvider mProvider = null;
+ private int mProviderId;
@UnsupportedAppUsage
public NetworkFactory(Looper looper, Context context, String logTag,
@@ -147,55 +140,43 @@
}
public void register() {
- if (DBG) log("Registering NetworkFactory");
- if (mMessenger == null) {
- mMessenger = new Messenger(this);
- mSerialNumber = ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger,
- LOG_TAG);
+ if (mProvider != null) {
+ Log.e(LOG_TAG, "Ignoring attempt to register already-registered NetworkFactory");
+ return;
}
+ if (DBG) log("Registering NetworkFactory");
+
+ mProvider = new NetworkProvider(mContext, NetworkFactory.this.getLooper(), LOG_TAG) {
+ @Override
+ public void onNetworkRequested(@NonNull NetworkRequest request, int score,
+ int servingProviderId) {
+ handleAddRequest((NetworkRequest) request, score, servingProviderId);
+ }
+
+ @Override
+ public void onRequestWithdrawn(@NonNull NetworkRequest request) {
+ handleRemoveRequest(request);
+ }
+ };
+
+ mMessenger = new Messenger(this);
+ mProviderId = ConnectivityManager.from(mContext).registerNetworkProvider(mProvider);
}
public void unregister() {
- if (DBG) log("Unregistering NetworkFactory");
- if (mMessenger != null) {
- ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger);
- mMessenger = null;
+ if (mProvider == null) {
+ Log.e(LOG_TAG, "Ignoring attempt to unregister unregistered NetworkFactory");
+ return;
}
+ if (DBG) log("Unregistering NetworkFactory");
+
+ ConnectivityManager.from(mContext).unregisterNetworkProvider(mProvider);
+ mProvider = null;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
- if (mAsyncChannel != null) {
- log("Received new connection while already connected!");
- break;
- }
- if (VDBG) log("NetworkFactory fully connected");
- AsyncChannel ac = new AsyncChannel();
- ac.connected(null, this, msg.replyTo);
- ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
- AsyncChannel.STATUS_SUCCESSFUL);
- mAsyncChannel = ac;
- for (Message m : mPreConnectedQueue) {
- ac.sendMessage(m);
- }
- mPreConnectedQueue.clear();
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
- if (VDBG) log("CMD_CHANNEL_DISCONNECT");
- if (mAsyncChannel != null) {
- mAsyncChannel.disconnect();
- mAsyncChannel = null;
- }
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
- if (DBG) log("NetworkFactory channel lost");
- mAsyncChannel = null;
- break;
- }
case CMD_REQUEST_NETWORK: {
handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
break;
@@ -219,13 +200,13 @@
public final NetworkRequest request;
public int score;
public boolean requested; // do we have a request outstanding, limited by score
- public int factorySerialNumber;
+ public int providerId;
- NetworkRequestInfo(NetworkRequest request, int score, int factorySerialNumber) {
+ NetworkRequestInfo(NetworkRequest request, int score, int providerId) {
this.request = request;
this.score = score;
this.requested = false;
- this.factorySerialNumber = factorySerialNumber;
+ this.providerId = providerId;
}
@Override
@@ -240,8 +221,6 @@
*
* @param request the request to handle.
* @param score the score of the NetworkAgent currently satisfying this request.
- * @param servingFactorySerialNumber the serial number of the NetworkFactory that
- * created the NetworkAgent currently satisfying this request.
*/
// TODO : remove this method. It is a stopgap measure to help sheperding a number
// of dependent changes that would conflict throughout the automerger graph. Having this
@@ -249,7 +228,7 @@
// the entire tree.
@VisibleForTesting
protected void handleAddRequest(NetworkRequest request, int score) {
- handleAddRequest(request, score, SerialNumber.NONE);
+ handleAddRequest(request, score, NetworkProvider.ID_NONE);
}
/**
@@ -258,27 +237,26 @@
*
* @param request the request to handle.
* @param score the score of the NetworkAgent currently satisfying this request.
- * @param servingFactorySerialNumber the serial number of the NetworkFactory that
- * created the NetworkAgent currently satisfying this request.
+ * @param servingProviderId the ID of the NetworkProvider that created the NetworkAgent
+ * currently satisfying this request.
*/
@VisibleForTesting
- protected void handleAddRequest(NetworkRequest request, int score,
- int servingFactorySerialNumber) {
+ protected void handleAddRequest(NetworkRequest request, int score, int servingProviderId) {
NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
if (n == null) {
if (DBG) {
log("got request " + request + " with score " + score
- + " and serial " + servingFactorySerialNumber);
+ + " and providerId " + servingProviderId);
}
- n = new NetworkRequestInfo(request, score, servingFactorySerialNumber);
+ n = new NetworkRequestInfo(request, score, servingProviderId);
mNetworkRequests.put(n.request.requestId, n);
} else {
if (VDBG) {
log("new score " + score + " for exisiting request " + request
- + " with serial " + servingFactorySerialNumber);
+ + " and providerId " + servingProviderId);
}
n.score = score;
- n.factorySerialNumber = servingFactorySerialNumber;
+ n.providerId = servingProviderId;
}
if (VDBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter);
@@ -333,8 +311,8 @@
log(" n.requests = " + n.requested);
log(" n.score = " + n.score);
log(" mScore = " + mScore);
- log(" n.factorySerialNumber = " + n.factorySerialNumber);
- log(" mSerialNumber = " + mSerialNumber);
+ log(" n.providerId = " + n.providerId);
+ log(" mProviderId = " + mProviderId);
}
if (shouldNeedNetworkFor(n)) {
if (VDBG) log(" needNetworkFor");
@@ -355,7 +333,7 @@
// If the score of this request is higher or equal to that of this factory and some
// other factory is responsible for it, then this factory should not track the request
// because it has no hope of satisfying it.
- && (n.score < mScore || n.factorySerialNumber == mSerialNumber)
+ && (n.score < mScore || n.providerId == mProviderId)
// If this factory can't satisfy the capability needs of this request, then it
// should not be tracked.
&& n.request.networkCapabilities.satisfiedByNetworkCapabilities(mCapabilityFilter)
@@ -373,7 +351,7 @@
// assigned to the factory
// - This factory can't satisfy the capability needs of the request
// - The concrete implementation of the factory rejects the request
- && ((n.score > mScore && n.factorySerialNumber != mSerialNumber)
+ && ((n.score > mScore && n.providerId != mProviderId)
|| !n.request.networkCapabilities.satisfiedByNetworkCapabilities(
mCapabilityFilter)
|| !acceptRequest(n.request, n.score));
@@ -408,12 +386,7 @@
protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) {
post(() -> {
if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r);
- Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r);
- if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(msg);
- } else {
- mPreConnectedQueue.add(msg);
- }
+ ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(r);
});
}
@@ -444,8 +417,13 @@
return mNetworkRequests.size();
}
+ /* TODO: delete when all callers have migrated to NetworkProvider IDs. */
public int getSerialNumber() {
- return mSerialNumber;
+ return mProviderId;
+ }
+
+ public int getProviderId() {
+ return mProviderId;
}
protected void log(String s) {
@@ -465,8 +443,8 @@
@Override
public String toString() {
- StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mSerialNumber=")
- .append(mSerialNumber).append(", ScoreFilter=")
+ StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mProviderId=")
+ .append(mProviderId).append(", ScoreFilter=")
.append(mScore).append(", Filter=").append(mCapabilityFilter).append(", requests=")
.append(mNetworkRequests.size()).append(", refCount=").append(mRefCount)
.append("}");
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
deleted file mode 100644
index 4ad52d5..0000000
--- a/core/java/android/net/NetworkMisc.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * A grab-bag of information (metadata, policies, properties, etc) about a
- * {@link Network}. Since this contains PII, it should not be sent outside the
- * system.
- *
- * @hide
- */
-public class NetworkMisc implements Parcelable {
-
- /**
- * If the {@link Network} is a VPN, whether apps are allowed to bypass the
- * VPN. This is set by a {@link VpnService} and used by
- * {@link ConnectivityManager} when creating a VPN.
- */
- public boolean allowBypass;
-
- /**
- * Set if the network was manually/explicitly connected to by the user either from settings
- * or a 3rd party app. For example, turning on cell data is not explicit but tapping on a wifi
- * ap in the wifi settings to trigger a connection is explicit. A 3rd party app asking to
- * connect to a particular access point is also explicit, though this may change in the future
- * as we want apps to use the multinetwork apis.
- */
- public boolean explicitlySelected;
-
- /**
- * Set if the user desires to use this network even if it is unvalidated. This field has meaning
- * only if {@link explicitlySelected} is true. If it is, this field must also be set to the
- * appropriate value based on previous user choice.
- */
- public boolean acceptUnvalidated;
-
- /**
- * Whether the user explicitly set that this network should be validated even if presence of
- * only partial internet connectivity.
- */
- public boolean acceptPartialConnectivity;
-
- /**
- * Set to avoid surfacing the "Sign in to network" notification.
- * if carrier receivers/apps are registered to handle the carrier-specific provisioning
- * procedure, a carrier specific provisioning notification will be placed.
- * only one notification should be displayed. This field is set based on
- * which notification should be used for provisioning.
- */
- public boolean provisioningNotificationDisabled;
-
- /**
- * For mobile networks, this is the subscriber ID (such as IMSI).
- */
- public String subscriberId;
-
- /**
- * Set to skip 464xlat. This means the device will treat the network as IPv6-only and
- * will not attempt to detect a NAT64 via RFC 7050 DNS lookups.
- */
- public boolean skip464xlat;
-
- /**
- * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
- * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
- */
- public boolean hasShownBroken;
-
- public NetworkMisc() {
- }
-
- public NetworkMisc(NetworkMisc nm) {
- if (nm != null) {
- allowBypass = nm.allowBypass;
- explicitlySelected = nm.explicitlySelected;
- acceptUnvalidated = nm.acceptUnvalidated;
- subscriberId = nm.subscriberId;
- provisioningNotificationDisabled = nm.provisioningNotificationDisabled;
- skip464xlat = nm.skip464xlat;
- }
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(allowBypass ? 1 : 0);
- out.writeInt(explicitlySelected ? 1 : 0);
- out.writeInt(acceptUnvalidated ? 1 : 0);
- out.writeString(subscriberId);
- out.writeInt(provisioningNotificationDisabled ? 1 : 0);
- out.writeInt(skip464xlat ? 1 : 0);
- }
-
- public static final @android.annotation.NonNull Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
- @Override
- public NetworkMisc createFromParcel(Parcel in) {
- NetworkMisc networkMisc = new NetworkMisc();
- networkMisc.allowBypass = in.readInt() != 0;
- networkMisc.explicitlySelected = in.readInt() != 0;
- networkMisc.acceptUnvalidated = in.readInt() != 0;
- networkMisc.subscriberId = in.readString();
- networkMisc.provisioningNotificationDisabled = in.readInt() != 0;
- networkMisc.skip464xlat = in.readInt() != 0;
- return networkMisc;
- }
-
- @Override
- public NetworkMisc[] newArray(int size) {
- return new NetworkMisc[size];
- }
- };
-}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 9731f3c..301d203 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -300,22 +300,34 @@
* this without a single transport set will generate an exception, as will
* subsequently adding or removing transports after this is set.
* </p>
- * The interpretation of this {@code String} is bearer specific and bearers that use
- * it should document their particulars. For example, Bluetooth may use some sort of
- * device id while WiFi could used ssid and/or bssid. Cellular may use carrier spn.
+ * If the {@code networkSpecifier} is provided, it shall be interpreted as follows:
+ * <ul>
+ * <li>If the specifier can be parsed as an integer, it will be treated as a
+ * {@link android.net TelephonyNetworkSpecifier}, and the provided integer will be
+ * interpreted as a SubscriptionId.
+ * <li>If the value is an ethernet interface name, it will be treated as such.
+ * <li>For all other cases, the behavior is undefined.
+ * </ul>
*
- * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
- * specific network specifier where the bearer has a choice of
- * networks.
+ * @param networkSpecifier A {@code String} of either a SubscriptionId in cellular
+ * network request or an ethernet interface name in ethernet
+ * network request.
+ *
+ * @deprecated Use {@link #setNetworkSpecifier(NetworkSpecifier)} instead.
*/
+ @Deprecated
public Builder setNetworkSpecifier(String networkSpecifier) {
- /*
- * A StringNetworkSpecifier does not accept null or empty ("") strings. When network
- * specifiers were strings a null string and an empty string were considered equivalent.
- * Hence no meaning is attached to a null or empty ("") string.
- */
- return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
- : new StringNetworkSpecifier(networkSpecifier));
+ try {
+ int subId = Integer.parseInt(networkSpecifier);
+ return setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(subId).build());
+ } catch (NumberFormatException nfe) {
+ // A StringNetworkSpecifier does not accept null or empty ("") strings. When network
+ // specifiers were strings a null string and an empty string were considered
+ // equivalent. Hence no meaning is attached to a null or empty ("") string.
+ return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
+ : new StringNetworkSpecifier(networkSpecifier));
+ }
}
/**
@@ -455,6 +467,19 @@
}
/**
+ * Returns true iff. the capabilities requested in this NetworkRequest are satisfied by the
+ * provided {@link NetworkCapabilities}.
+ *
+ * @param nc Capabilities that should satisfy this NetworkRequest. null capabilities do not
+ * satisfy any request.
+ * @hide
+ */
+ @SystemApi
+ public boolean satisfiedBy(@Nullable NetworkCapabilities nc) {
+ return networkCapabilities.satisfiedByNetworkCapabilities(nc);
+ }
+
+ /**
* @see Builder#addTransportType(int)
*/
public boolean hasTransport(@Transport int transportType) {
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index f6dc525..c233ec0 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -163,27 +163,26 @@
public static final String EXTRA_NEW_SCORER = "newScorer";
/** @hide */
- @IntDef({CACHE_FILTER_NONE, CACHE_FILTER_CURRENT_NETWORK, CACHE_FILTER_SCAN_RESULTS})
+ @IntDef({SCORE_FILTER_NONE, SCORE_FILTER_CURRENT_NETWORK, SCORE_FILTER_SCAN_RESULTS})
@Retention(RetentionPolicy.SOURCE)
- public @interface CacheUpdateFilter {}
+ public @interface ScoreUpdateFilter {}
/**
- * Do not filter updates sent to the cache.
- * @hide
+ * Do not filter updates sent to the {@link NetworkScoreCallback}].
*/
- public static final int CACHE_FILTER_NONE = 0;
+ public static final int SCORE_FILTER_NONE = 0;
/**
- * Only send cache updates when the network matches the connected network.
- * @hide
+ * Only send updates to the {@link NetworkScoreCallback} when the network matches the connected
+ * network.
*/
- public static final int CACHE_FILTER_CURRENT_NETWORK = 1;
+ public static final int SCORE_FILTER_CURRENT_NETWORK = 1;
/**
- * Only send cache updates when the network is part of the current scan result set.
- * @hide
+ * Only send updates to the {@link NetworkScoreCallback} when the network is part of the
+ * current scan result set.
*/
- public static final int CACHE_FILTER_SCAN_RESULTS = 2;
+ public static final int SCORE_FILTER_SCAN_RESULTS = 2;
/** @hide */
@IntDef({RECOMMENDATIONS_ENABLED_FORCED_OFF, RECOMMENDATIONS_ENABLED_OFF,
@@ -404,13 +403,13 @@
* @throws SecurityException if the caller does not hold the
* {@link permission#REQUEST_NETWORK_SCORES} permission.
* @throws IllegalArgumentException if a score cache is already registered for this type.
- * @deprecated equivalent to registering for cache updates with CACHE_FILTER_NONE.
+ * @deprecated equivalent to registering for cache updates with {@link #SCORE_FILTER_NONE}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
@Deprecated // migrate to registerNetworkScoreCache(int, INetworkScoreCache, int)
public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
- registerNetworkScoreCache(networkType, scoreCache, CACHE_FILTER_NONE);
+ registerNetworkScoreCache(networkType, scoreCache, SCORE_FILTER_NONE);
}
/**
@@ -418,7 +417,7 @@
*
* @param networkType the type of network this cache can handle. See {@link NetworkKey#type}
* @param scoreCache implementation of {@link INetworkScoreCache} to store the scores
- * @param filterType the {@link CacheUpdateFilter} to apply
+ * @param filterType the {@link ScoreUpdateFilter} to apply
* @throws SecurityException if the caller does not hold the
* {@link permission#REQUEST_NETWORK_SCORES} permission.
* @throws IllegalArgumentException if a score cache is already registered for this type.
@@ -426,7 +425,7 @@
*/
@RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache,
- @CacheUpdateFilter int filterType) {
+ @ScoreUpdateFilter int filterType) {
try {
mService.registerNetworkScoreCache(networkType, scoreCache, filterType);
} catch (RemoteException e) {
@@ -510,7 +509,7 @@
* Register a network score callback.
*
* @param networkType the type of network this cache can handle. See {@link NetworkKey#type}
- * @param filterType the {@link CacheUpdateFilter} to apply
+ * @param filterType the {@link ScoreUpdateFilter} to apply
* @param callback implementation of {@link NetworkScoreCallback} that will be invoked when the
* scores change.
* @param executor The executor on which to execute the callbacks.
@@ -522,7 +521,7 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
public void registerNetworkScoreCallback(@NetworkKey.NetworkType int networkType,
- @CacheUpdateFilter int filterType,
+ @ScoreUpdateFilter int filterType,
@NonNull @CallbackExecutor Executor executor,
@NonNull NetworkScoreCallback callback) throws SecurityException {
if (callback == null || executor == null) {
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 08cc4e2..779f7bc 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -31,7 +31,6 @@
import java.io.FileDescriptor;
import java.math.BigInteger;
import java.net.Inet4Address;
-import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
@@ -313,15 +312,6 @@
}
/**
- * Check if IP address type is consistent between two InetAddress.
- * @return true if both are the same type. False otherwise.
- */
- public static boolean addressTypeMatches(InetAddress left, InetAddress right) {
- return (((left instanceof Inet4Address) && (right instanceof Inet4Address)) ||
- ((left instanceof Inet6Address) && (right instanceof Inet6Address)));
- }
-
- /**
* Convert a 32 char hex string into a Inet6Address.
* throws a runtime exception if the string isn't 32 chars, isn't hex or can't be
* made into an Inet6Address
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index ea6002c..e088094 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -22,6 +22,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.NetUtils;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -441,21 +442,7 @@
@UnsupportedAppUsage
@Nullable
public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) {
- if ((routes == null) || (dest == null)) return null;
-
- RouteInfo bestRoute = null;
- // pick a longest prefix match under same address type
- for (RouteInfo route : routes) {
- if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) {
- if ((bestRoute != null) &&
- (bestRoute.mDestination.getPrefixLength() >=
- route.mDestination.getPrefixLength())) {
- continue;
- }
- if (route.matches(dest)) bestRoute = route;
- }
- }
- return bestRoute;
+ return NetUtils.selectBestRoute(routes, dest);
}
/**
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index fb224fb..fc9a8f6 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -20,6 +20,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -53,7 +54,11 @@
public abstract class SocketKeepalive implements AutoCloseable {
static final String TAG = "SocketKeepalive";
- /** @hide */
+ /**
+ * No errors.
+ * @hide
+ */
+ @SystemApi
public static final int SUCCESS = 0;
/** @hide */
diff --git a/core/java/android/net/TelephonyNetworkSpecifier.java b/core/java/android/net/TelephonyNetworkSpecifier.java
new file mode 100644
index 0000000..726f770
--- /dev/null
+++ b/core/java/android/net/TelephonyNetworkSpecifier.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * NetworkSpecifier object for cellular network request. Apps should use the
+ * {@link TelephonyNetworkSpecifier.Builder} class to create an instance.
+ */
+public final class TelephonyNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+
+ private final int mSubId;
+
+ /**
+ * Return the subscription Id of current TelephonyNetworkSpecifier object.
+ *
+ * @return The subscription id.
+ */
+ public int getSubscriptionId() {
+ return mSubId;
+ }
+
+ /**
+ * @hide
+ */
+ public TelephonyNetworkSpecifier(int subId) {
+ this.mSubId = subId;
+ }
+
+ public static final @NonNull Creator<TelephonyNetworkSpecifier> CREATOR =
+ new Creator<TelephonyNetworkSpecifier>() {
+ @Override
+ public TelephonyNetworkSpecifier createFromParcel(Parcel in) {
+ int subId = in.readInt();
+ return new TelephonyNetworkSpecifier(subId);
+ }
+
+ @Override
+ public TelephonyNetworkSpecifier[] newArray(int size) {
+ return new TelephonyNetworkSpecifier[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSubId);
+ }
+
+ @Override
+ public int hashCode() {
+ return mSubId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof TelephonyNetworkSpecifier)) {
+ return false;
+ }
+
+ TelephonyNetworkSpecifier lhs = (TelephonyNetworkSpecifier) obj;
+ return mSubId == lhs.mSubId;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("TelephonyNetworkSpecifier [")
+ .append("mSubId = ").append(mSubId)
+ .append("]")
+ .toString();
+ }
+
+ /** @hide */
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ // Any generic requests should be satisfied by a specific telephony network.
+ // For simplicity, we treat null same as MatchAllNetworkSpecifier
+ return equals(other) || other == null || other instanceof MatchAllNetworkSpecifier;
+ }
+
+
+ /**
+ * Builder to create {@link TelephonyNetworkSpecifier} object.
+ */
+ public static final class Builder {
+ // Integer.MIN_VALUE which is not a valid subId, services as the sentinel to check if
+ // subId was set
+ private static final int SENTINEL_SUB_ID = Integer.MIN_VALUE;
+
+ private int mSubId;
+
+ public Builder() {
+ mSubId = SENTINEL_SUB_ID;
+ }
+
+ /**
+ * Set the subscription id.
+ *
+ * @param subId The subscription Id.
+ * @return Instance of {@link Builder} to enable the chaining of the builder method.
+ */
+ public @NonNull Builder setSubscriptionId(int subId) {
+ mSubId = subId;
+ return this;
+ }
+
+ /**
+ * Create a NetworkSpecifier for the cellular network request.
+ *
+ * @return TelephonyNetworkSpecifier object.
+ * @throws IllegalArgumentException when subscription Id is not provided through
+ * {@link #setSubscriptionId(int)}.
+ */
+ public @NonNull TelephonyNetworkSpecifier build() {
+ if (mSubId == SENTINEL_SUB_ID) {
+ throw new IllegalArgumentException("Subscription Id is not provided.");
+ }
+ return new TelephonyNetworkSpecifier(mSubId);
+ }
+ }
+}
diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java
deleted file mode 100644
index e4a91c5..0000000
--- a/core/java/android/net/nsd/DnsSdTxtRecord.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/* -*- Mode: Java; tab-width: 4 -*-
- *
- * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
-
- To do:
- - implement remove()
- - fix set() to replace existing values
- */
-
-package android.net.nsd;
-
-import android.os.Parcelable;
-import android.os.Parcel;
-
-import java.util.Arrays;
-
-/**
- * This class handles TXT record data for DNS based service discovery as specified at
- * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
- *
- * DNS-SD specifies that a TXT record corresponding to an SRV record consist of
- * a packed array of bytes, each preceded by a length byte. Each string
- * is an attribute-value pair.
- *
- * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it
- * as need be to implement its various methods.
- * @hide
- *
- */
-public class DnsSdTxtRecord implements Parcelable {
- private static final byte mSeperator = '=';
-
- private byte[] mData;
-
- /** Constructs a new, empty TXT record. */
- public DnsSdTxtRecord() {
- mData = new byte[0];
- }
-
- /** Constructs a new TXT record from a byte array in the standard format. */
- public DnsSdTxtRecord(byte[] data) {
- mData = (byte[]) data.clone();
- }
-
- /** Copy constructor */
- public DnsSdTxtRecord(DnsSdTxtRecord src) {
- if (src != null && src.mData != null) {
- mData = (byte[]) src.mData.clone();
- }
- }
-
- /**
- * Set a key/value pair. Setting an existing key will replace its value.
- * @param key Must be ascii with no '='
- * @param value matching value to key
- */
- public void set(String key, String value) {
- byte[] keyBytes;
- byte[] valBytes;
- int valLen;
-
- if (value != null) {
- valBytes = value.getBytes();
- valLen = valBytes.length;
- } else {
- valBytes = null;
- valLen = 0;
- }
-
- try {
- keyBytes = key.getBytes("US-ASCII");
- }
- catch (java.io.UnsupportedEncodingException e) {
- throw new IllegalArgumentException("key should be US-ASCII");
- }
-
- for (int i = 0; i < keyBytes.length; i++) {
- if (keyBytes[i] == '=') {
- throw new IllegalArgumentException("= is not a valid character in key");
- }
- }
-
- if (keyBytes.length + valLen >= 255) {
- throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes");
- }
-
- int currentLoc = remove(key);
- if (currentLoc == -1)
- currentLoc = keyCount();
-
- insert(keyBytes, valBytes, currentLoc);
- }
-
- /**
- * Get a value for a key
- *
- * @param key
- * @return The value associated with the key
- */
- public String get(String key) {
- byte[] val = this.getValue(key);
- return val != null ? new String(val) : null;
- }
-
- /** Remove a key/value pair. If found, returns the index or -1 if not found */
- public int remove(String key) {
- int avStart = 0;
-
- for (int i=0; avStart < mData.length; i++) {
- int avLen = mData[avStart];
- if (key.length() <= avLen &&
- (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) {
- String s = new String(mData, avStart + 1, key.length());
- if (0 == key.compareToIgnoreCase(s)) {
- byte[] oldBytes = mData;
- mData = new byte[oldBytes.length - avLen - 1];
- System.arraycopy(oldBytes, 0, mData, 0, avStart);
- System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart,
- oldBytes.length - avStart - avLen - 1);
- return i;
- }
- }
- avStart += (0xFF & (avLen + 1));
- }
- return -1;
- }
-
- /** Return the count of keys */
- public int keyCount() {
- int count = 0, nextKey;
- for (nextKey = 0; nextKey < mData.length; count++) {
- nextKey += (0xFF & (mData[nextKey] + 1));
- }
- return count;
- }
-
- /** Return true if key is present, false if not. */
- public boolean contains(String key) {
- String s = null;
- for (int i = 0; null != (s = this.getKey(i)); i++) {
- if (0 == key.compareToIgnoreCase(s)) return true;
- }
- return false;
- }
-
- /* Gets the size in bytes */
- public int size() {
- return mData.length;
- }
-
- /* Gets the raw data in bytes */
- public byte[] getRawData() {
- return (byte[]) mData.clone();
- }
-
- private void insert(byte[] keyBytes, byte[] value, int index) {
- byte[] oldBytes = mData;
- int valLen = (value != null) ? value.length : 0;
- int insertion = 0;
- int newLen, avLen;
-
- for (int i = 0; i < index && insertion < mData.length; i++) {
- insertion += (0xFF & (mData[insertion] + 1));
- }
-
- avLen = keyBytes.length + valLen + (value != null ? 1 : 0);
- newLen = avLen + oldBytes.length + 1;
-
- mData = new byte[newLen];
- System.arraycopy(oldBytes, 0, mData, 0, insertion);
- int secondHalfLen = oldBytes.length - insertion;
- System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen);
- mData[insertion] = (byte) avLen;
- System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length);
- if (value != null) {
- mData[insertion + 1 + keyBytes.length] = mSeperator;
- System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen);
- }
- }
-
- /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */
- private String getKey(int index) {
- int avStart = 0;
-
- for (int i=0; i < index && avStart < mData.length; i++) {
- avStart += mData[avStart] + 1;
- }
-
- if (avStart < mData.length) {
- int avLen = mData[avStart];
- int aLen = 0;
-
- for (aLen=0; aLen < avLen; aLen++) {
- if (mData[avStart + aLen + 1] == mSeperator) break;
- }
- return new String(mData, avStart + 1, aLen);
- }
- return null;
- }
-
- /**
- * Look up a key in the TXT record by zero-based index and return its value.
- * Returns null if index exceeds the total number of keys.
- * Returns null if the key is present with no value.
- */
- private byte[] getValue(int index) {
- int avStart = 0;
- byte[] value = null;
-
- for (int i=0; i < index && avStart < mData.length; i++) {
- avStart += mData[avStart] + 1;
- }
-
- if (avStart < mData.length) {
- int avLen = mData[avStart];
- int aLen = 0;
-
- for (aLen=0; aLen < avLen; aLen++) {
- if (mData[avStart + aLen + 1] == mSeperator) {
- value = new byte[avLen - aLen - 1];
- System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1);
- break;
- }
- }
- }
- return value;
- }
-
- private String getValueAsString(int index) {
- byte[] value = this.getValue(index);
- return value != null ? new String(value) : null;
- }
-
- private byte[] getValue(String forKey) {
- String s = null;
- int i;
-
- for (i = 0; null != (s = this.getKey(i)); i++) {
- if (0 == forKey.compareToIgnoreCase(s)) {
- return this.getValue(i);
- }
- }
-
- return null;
- }
-
- /**
- * Return a string representation.
- * Example : {key1=value1},{key2=value2}..
- *
- * For a key say like "key3" with null value
- * {key1=value1},{key2=value2}{key3}
- */
- public String toString() {
- String a, result = null;
-
- for (int i = 0; null != (a = this.getKey(i)); i++) {
- String av = "{" + a;
- String val = this.getValueAsString(i);
- if (val != null)
- av += "=" + val + "}";
- else
- av += "}";
- if (result == null)
- result = av;
- else
- result = result + ", " + av;
- }
- return result != null ? result : "";
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (!(o instanceof DnsSdTxtRecord)) {
- return false;
- }
-
- DnsSdTxtRecord record = (DnsSdTxtRecord)o;
- return Arrays.equals(record.mData, mData);
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(mData);
- }
-
- /** Implement the Parcelable interface */
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface */
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeByteArray(mData);
- }
-
- /** Implement the Parcelable interface */
- public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR =
- new Creator<DnsSdTxtRecord>() {
- public DnsSdTxtRecord createFromParcel(Parcel in) {
- DnsSdTxtRecord info = new DnsSdTxtRecord();
- in.readByteArray(info.mData);
- return info;
- }
-
- public DnsSdTxtRecord[] newArray(int size) {
- return new DnsSdTxtRecord[size];
- }
- };
-}
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index b0c2546..ac70b52 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -631,10 +631,12 @@
}
}
- private static void sendDeathNotice(DeathRecipient recipient) {
- if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
+ private static void sendDeathNotice(DeathRecipient recipient, IBinder binderProxy) {
+ if (false) {
+ Log.v("JavaBinder", "sendDeathNotice to " + recipient + " for " + binderProxy);
+ }
try {
- recipient.binderDied();
+ recipient.binderDied(binderProxy);
} catch (RuntimeException exc) {
Log.w("BinderNative", "Uncaught exception from death notification",
exc);
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index f336fda..f5fe9c3 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -285,6 +285,13 @@
*/
public interface DeathRecipient {
public void binderDied();
+
+ /**
+ * @hide
+ */
+ default void binderDied(IBinder who) {
+ binderDied();
+ }
}
/**
diff --git a/core/java/android/os/IIncidentDumpCallback.aidl b/core/java/android/os/IIncidentDumpCallback.aidl
new file mode 100644
index 0000000..09b5b01
--- /dev/null
+++ b/core/java/android/os/IIncidentDumpCallback.aidl
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Callback from IIncidentManager to dump an extended section.
+ *
+ * @hide
+ */
+oneway interface IIncidentDumpCallback {
+ /**
+ * Dumps section data to the given ParcelFileDescriptor.
+ */
+ void onDumpSection(in ParcelFileDescriptor fd);
+}
diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl
index 7e1b1e0..923234e 100644
--- a/core/java/android/os/IIncidentManager.aidl
+++ b/core/java/android/os/IIncidentManager.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.os.IIncidentReportStatusListener;
+import android.os.IIncidentDumpCallback;
import android.os.IncidentManager;
import android.os.IncidentReportArgs;
@@ -52,6 +53,19 @@
@nullable IIncidentReportStatusListener listener);
/**
+ * Register a section callback with the given id and name. The callback function
+ * will be invoked when an incident report with all sections or sections matching
+ * the given id is being taken.
+ */
+ oneway void registerSection(int id, String name, IIncidentDumpCallback callback);
+
+ /**
+ * Unregister a section callback associated with the given id. The section must be
+ * previously registered with registerSection(int, String, IIncidentDumpCallback).
+ */
+ oneway void unregisterSection(int id);
+
+ /**
* Tell the incident daemon that the android system server is up and running.
*/
oneway void systemRunning();
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 7b2d148..416d692 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -24,7 +24,7 @@
{
boolean hasVibrator();
boolean hasAmplitudeControl();
- boolean setAlwaysOnEffect(int id, in VibrationEffect effect,
+ boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in VibrationEffect effect,
in VibrationAttributes attributes);
void vibrate(int uid, String opPkg, in VibrationEffect effect,
in VibrationAttributes attributes, String reason, IBinder token);
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index 09e1c0f..f6563eb 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -31,6 +31,7 @@
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -421,6 +422,39 @@
}
/**
+ * Callback for dumping an extended (usually vendor-supplied) incident report section
+ *
+ * @see #registerSection
+ * @see #unregisterSection
+ *
+ * @hide
+ */
+ public static class DumpCallback {
+ private Executor mExecutor;
+
+ IIncidentDumpCallback.Stub mBinder = new IIncidentDumpCallback.Stub() {
+ @Override
+ public void onDumpSection(ParcelFileDescriptor pfd) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ DumpCallback.this.onDumpSection(
+ new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+ });
+ } else {
+ DumpCallback.this.onDumpSection(
+ new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+ }
+ }
+ };
+
+ /**
+ * Called when incidentd requests to dump this section.
+ */
+ public void onDumpSection(OutputStream out) {
+ }
+ }
+
+ /**
* @hide
*/
public IncidentManager(Context context) {
@@ -528,6 +562,61 @@
}
/**
+ * Register a callback to dump an extended incident report section with the given id and name.
+ * The callback function will be invoked when an incident report with all sections or sections
+ * matching the given id is being taken.
+ *
+ * @hide
+ */
+ public void registerSection(int id, String name, @NonNull DumpCallback callback) {
+ registerSection(id, name, mContext.getMainExecutor(), callback);
+ }
+
+ /**
+ * Register a callback to dump an extended incident report section with the given id and name,
+ * running on the supplied executor.
+ *
+ * @hide
+ */
+ public void registerSection(int id, String name, @NonNull @CallbackExecutor Executor executor,
+ @NonNull DumpCallback callback) {
+ try {
+ if (callback.mExecutor != null) {
+ throw new RuntimeException("Do not reuse DumpCallback objects when calling"
+ + " registerSection");
+ }
+ callback.mExecutor = executor;
+ final IIncidentManager service = getIIncidentManagerLocked();
+ if (service == null) {
+ Slog.e(TAG, "registerSection can't find incident binder service");
+ return;
+ }
+ service.registerSection(id, name, callback.mBinder);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "registerSection failed", ex);
+ }
+ }
+
+ /**
+ * Unregister an extended section dump function. The section must be previously registered with
+ * {@link #registerSection(int, String, DumpCallback)}
+ *
+ * @hide
+ */
+ public void unregisterSection(int id) {
+ try {
+ final IIncidentManager service = getIIncidentManagerLocked();
+ if (service == null) {
+ Slog.e(TAG, "unregisterSection can't find incident binder service");
+ return;
+ }
+ service.unregisterSection(id);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unregisterSection failed", ex);
+ }
+ }
+
+ /**
* Get the incident reports that are available for upload for the supplied
* broadcast recevier.
*
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index c1542c7..8050454 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -70,14 +70,15 @@
}
@Override
- public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attributes) {
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
+ AudioAttributes attributes) {
if (mService == null) {
Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
return false;
}
try {
VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
- return mService.setAlwaysOnEffect(id, effect, atr);
+ return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, atr);
} catch (RemoteException e) {
Log.w(TAG, "Failed to set always-on effect.", e);
}
diff --git a/core/java/android/os/TimestampedValue.java b/core/java/android/os/TimestampedValue.java
index 348574e..f4c87ac 100644
--- a/core/java/android/os/TimestampedValue.java
+++ b/core/java/android/os/TimestampedValue.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import java.util.Objects;
@@ -35,19 +36,27 @@
* @param <T> the type of the value with an associated timestamp
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class TimestampedValue<T> implements Parcelable {
private final long mReferenceTimeMillis;
+ @Nullable
private final T mValue;
- public TimestampedValue(long referenceTimeMillis, T value) {
+ public TimestampedValue(long referenceTimeMillis, @Nullable T value) {
mReferenceTimeMillis = referenceTimeMillis;
mValue = value;
}
+ /** Returns the reference time value. See {@link TimestampedValue} for more information. */
public long getReferenceTimeMillis() {
return mReferenceTimeMillis;
}
+ /**
+ * Returns the value associated with the timestamp. See {@link TimestampedValue} for more
+ * information.
+ */
+ @Nullable
public T getValue() {
return mValue;
}
@@ -86,6 +95,8 @@
return one.mReferenceTimeMillis - two.mReferenceTimeMillis;
}
+ /** @hide */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR =
new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 6e199ce3..2eaefca 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -192,7 +192,11 @@
/**
* Specifies if a user is disallowed from changing Wi-Fi
* access points. The default value is <code>false</code>.
- * <p>This restriction has no effect in a managed profile.
+ * <p>
+ * Device owner and profile owner can set this restriction, although the restriction has no
+ * effect in a managed profile. When it is set by the profile owner of an organization-owned
+ * managed profile on the parent profile, it will disallow the personal user from changing
+ * Wi-Fi access points.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -242,8 +246,13 @@
/**
* Specifies if a user is disallowed from turning on location sharing.
* The default value is <code>false</code>.
- * <p>In a managed profile, location sharing always reflects the primary user's setting, but
+ * <p>
+ * In a managed profile, location sharing always reflects the primary user's setting, but
* can be overridden and forced off by setting this restriction to true in the managed profile.
+ * <p>
+ * Device owner and profile owner can set this restriction. When it is set by the profile
+ * owner of an organization-owned managed profile on the parent profile, it will prevent the
+ * user from turning on location sharing in the personal profile.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -349,9 +358,14 @@
* Specifies if a user is disallowed from configuring bluetooth.
* This does <em>not</em> restrict the user from turning bluetooth on or off.
* The default value is <code>false</code>.
- * <p>This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
+ * <p>
+ * This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
* bluetooth completely on the device, use {@link #DISALLOW_BLUETOOTH}.
- * <p>This restriction has no effect in a managed profile.
+ * <p>
+ * Device owner and profile owner can set this restriction, although the restriction has no
+ * effect in a managed profile. When it is set by the profile owner of an organization-owned
+ * managed profile on the parent profile, it will disallow the personal user from configuring
+ * bluetooth.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -364,8 +378,10 @@
/**
* Specifies if bluetooth is disallowed on the device.
*
- * <p> This restriction can only be set by the device owner and the profile owner on the
- * primary user and it applies globally - i.e. it disables bluetooth on the entire device.
+ * <p> This restriction can only be set by the device owner, the profile owner on the
+ * primary user or the profile owner of an organization-owned managed profile on the
+ * parent profile and it applies globally - i.e. it disables bluetooth on the entire
+ * device.
* <p>The default value is <code>false</code>.
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -377,8 +393,9 @@
/**
* Specifies if outgoing bluetooth sharing is disallowed on the device. Device owner and profile
- * owner can set this restriction. When it is set by device owner, all users on this device will
- * be affected.
+ * owner can set this restriction. When it is set by device owner or the profile owner of an
+ * organization-owned managed profile on the parent profile, all users on this device will be
+ * affected.
*
* <p>Default is <code>true</code> for managed profiles and false for otherwise. When a device
* upgrades to {@link android.os.Build.VERSION_CODES#O}, the system sets it for all existing
@@ -394,7 +411,8 @@
/**
* Specifies if a user is disallowed from transferring files over
- * USB. This can only be set by device owners and profile owners on the primary user.
+ * USB. This can only be set by device owners, profile owners on the primary user or
+ * profile owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -453,8 +471,9 @@
/**
* Specifies if a user is disallowed from enabling or accessing debugging features. When set on
- * the primary user, disables debugging features altogether, including USB debugging. When set
- * on a managed profile or a secondary user, blocks debugging for that user only, including
+ * the primary user or by the profile owner of an organization-owned managed profile on the
+ * parent profile, disables debugging features altogether, including USB debugging. When set on
+ * a managed profile or a secondary user, blocks debugging for that user only, including
* starting activities, making service calls, accessing content providers, sending broadcasts,
* installing/uninstalling packages, clearing user data, etc.
* The default value is <code>false</code>.
@@ -485,18 +504,19 @@
/**
* Specifies if a user is disallowed from enabling or disabling location providers. As a
- * result, user is disallowed from turning on or off location. Device owner and profile owners
- * can set this restriction and it only applies on the managed user.
+ * result, user is disallowed from turning on or off location.
*
- * <p>In a managed profile, location sharing is forced off when it's off on primary user, so
- * user can still turn off location sharing on managed profile when the restriction is set by
- * profile owner on managed profile.
- *
- * <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
+ * <p>
+ * In a managed profile, location sharing is forced off when it is turned off on the primary
+ * user or by the profile owner of an organization-owned managed profile on the parent profile.
+ * The user can still turn off location sharing on a managed profile when the restriction is
+ * set by the profile owner on a managed profile.
+ * <p>
+ * This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
* as the device owner or profile owner can still enable or disable location mode via
* {@link DevicePolicyManager#setLocationEnabled} when this restriction is on.
- *
- * <p>The default value is <code>false</code>.
+ * <p>
+ * The default value is <code>false</code>.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -510,7 +530,8 @@
/**
* Specifies if date, time and timezone configuring is disallowed.
*
- * <p>When restriction is set by device owners, it applies globally - i.e., it disables date,
+ * <p>When restriction is set by device owners or profile owners of organization-owned
+ * managed profiles on the parent profile, it applies globally - i.e., it disables date,
* time and timezone setting on the entire device and all users will be affected. When it's set
* by profile owners, it's only applied to the managed user.
* <p>The default value is <code>false</code>.
@@ -526,8 +547,9 @@
/**
* Specifies if a user is disallowed from configuring Tethering
- * & portable hotspots. This can only be set by device owners and profile owners on the
- * primary user. The default value is <code>false</code>.
+ * & portable hotspots. This can only be set by device owners, profile owners on the
+ * primary user or profile owners of organization-owned managed profiles on the parent profile.
+ * The default value is <code>false</code>.
* <p>In Android 9.0 or higher, if tethering is enabled when this restriction is set,
* tethering will be automatically turned off.
*
@@ -571,8 +593,8 @@
/**
* Specifies if a user is disallowed from adding new users. This can only be set by device
- * owners and profile owners on the primary user.
- * The default value is <code>false</code>.
+ * owners, profile owners on the primary user or profile owners of organization-owned managed
+ * profiles on the parent profile. The default value is <code>false</code>.
* <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can add other users.
*
@@ -621,7 +643,8 @@
/**
* Specifies if a user is disallowed from configuring cell
- * broadcasts. This can only be set by device owners and profile owners on the primary user.
+ * broadcasts. This can only be set by device owners, profile owners on the primary user or
+ * profile owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
* <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can configure cell broadcasts.
@@ -636,7 +659,8 @@
/**
* Specifies if a user is disallowed from configuring mobile
- * networks. This can only be set by device owners and profile owners on the primary user.
+ * networks. This can only be set by device owners, profile owners on the primary user or
+ * profile owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
* <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can configure mobile networks.
@@ -739,6 +763,10 @@
/**
* Specifies that the user is not allowed to send or receive
* SMS messages. The default value is <code>false</code>.
+ * <p>
+ * Device owner and profile owner can set this restriction. When it is set by the
+ * profile owner of an organization-owned managed profile on the parent profile,
+ * it will disable SMS in the personal profile.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -857,7 +885,8 @@
/**
* Specifies if the user is not allowed to reboot the device into safe boot mode.
- * This can only be set by device owners and profile owners on the primary user.
+ * This can only be set by device owners, profile owners on the primary user or profile
+ * owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -896,6 +925,12 @@
/**
* Specifies if a user is not allowed to use the camera.
+ * <p>
+ * Device owner and profile owner can set this restriction. When the restriction is set by
+ * the device owner or the profile owner of an organization-owned managed profile on the
+ * parent profile, it is applied globally.
+ * <p>
+ * The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
@@ -916,7 +951,8 @@
/**
* Specifies if a user is not allowed to use cellular data when roaming. This can only be set by
- * device owners. The default value is <code>false</code>.
+ * device owners or profile owners of organization-owned managed profiles on the parent profile.
+ * The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
@@ -1011,8 +1047,9 @@
* Specifies if the contents of a user's screen is not allowed to be captured for artificial
* intelligence purposes.
*
- * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
- * only the target user will be affected.
+ * <p>Device owner and profile owner can set this restriction. When it is set by the
+ * device owner or the profile owner of an organization-owned managed profile on the parent
+ * profile, only the target user will be affected.
*
* <p>The default value is <code>false</code>.
*
@@ -1026,8 +1063,9 @@
* Specifies if the current user is able to receive content suggestions for selections based on
* the contents of their screen.
*
- * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
- * only the target user will be affected.
+ * <p>Device owner and profile owner can set this restriction. When it is set by the
+ * device owner or the profile owner of an organization-owned managed profile on the parent
+ * profile, only the target user will be affected.
*
* <p>The default value is <code>false</code>.
*
@@ -1093,7 +1131,9 @@
*
* <p>The default value is <code>false</code>.
*
- * <p>This user restriction can only be applied by the Device Owner.
+ * <p>This user restriction can only be applied by the device owner or the profile owner
+ * of an organization-owned managed profile on the parent profile.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1850,10 +1890,7 @@
* Checks if the calling app is running in a managed profile.
*
* @return whether the caller is in a managed profile.
- * @hide
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public boolean isManagedProfile() {
// No need for synchronization. Once it becomes non-null, it'll be non-null forever.
// Worst case we might end up calling the AIDL method multiple times but that's fine.
@@ -2416,6 +2453,13 @@
* by {@link #createUser(String, String, int)} or {@link #createGuest(Context, String)}), it
* takes less time.
*
+ * <p>This method completes the majority of work necessary for user creation: it
+ * creates user data, CE and DE encryption keys, app data directories, initializes the user and
+ * grants default permissions. When pre-created users become "real" users, only then are
+ * components notified of new user creation by firing user creation broadcasts.
+ *
+ * <p>All pre-created users are removed during system upgrade.
+ *
* <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
*
* @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
@@ -2427,6 +2471,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public @Nullable UserInfo preCreateUser(@NonNull String userType) {
try {
return mService.preCreateUser(userType);
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index ccbb0f1..ae75f3d 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -155,7 +155,7 @@
/**
* Configure an always-on haptics effect.
*
- * @param id The board-specific always-on ID to configure.
+ * @param alwaysOnId The board-specific always-on ID to configure.
* @param effect Vibration effect to assign to always-on id. Passing null will disable it.
* @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
* specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
@@ -164,8 +164,17 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
- public boolean setAlwaysOnEffect(int id, @Nullable VibrationEffect effect,
- @Nullable AudioAttributes attributes) {
+ public boolean setAlwaysOnEffect(int alwaysOnId, @Nullable VibrationEffect effect,
+ @Nullable AudioAttributes attributes) {
+ return setAlwaysOnEffect(Process.myUid(), mPackageName, alwaysOnId, effect, attributes);
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) {
Log.w(TAG, "Always-on effects aren't supported");
return false;
}
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index 4c92c28..cbf531c 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -106,9 +106,9 @@
* @return true if the call succeeds
*/
@RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
- public boolean startInstallation() {
+ public boolean startInstallation(String dsuSlot) {
try {
- return mService.startInstallation();
+ return mService.startInstallation(dsuSlot);
} catch (RemoteException e) {
throw new RuntimeException(e.toString());
}
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index 69cbab2..cc32f99 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -22,9 +22,10 @@
{
/**
* Start DynamicSystem installation.
+ * @param dsuSlot Name used to identify this installation
* @return true if the call succeeds
*/
- boolean startInstallation();
+ boolean startInstallation(@utf8InCpp String dsuSlot);
/**
* Create a DSU partition. This call may take 60~90 seconds. The caller
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0680523..4f84183 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -389,6 +389,21 @@
"android.settings.MANAGE_UNKNOWN_APP_SOURCES";
/**
+ * Activity Action: Show settings to allow configuration of cross-profile access for apps
+ *
+ * Input: Optionally, the Intent's data URI can specify the application package name to
+ * directly invoke the management GUI specific to the package name. For example
+ * "package:com.my.app".
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_CROSS_PROFILE_ACCESS =
+ "android.settings.MANAGE_CROSS_PROFILE_ACCESS";
+
+ /**
* Activity Action: Show the "Open by Default" page in a particular application's details page.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard against this.
@@ -495,28 +510,33 @@
"android.settings.WIFI_IP_SETTINGS";
/**
- * Activity Action: Show setting page to process an Easy Connect (Wi-Fi DPP) QR code and start
+ * Activity Action: Show setting page to process a Wi-Fi Easy Connect (aka DPP) URI and start
* configuration. This intent should be used when you want to use this device to take on the
- * configurator role for an IoT/other device. When provided with a valid DPP URI string Settings
- * will open a wifi selection screen for the user to indicate which network they would like to
- * configure the device specified in the DPP URI string for and carry them through the rest of
- * the flow for provisioning the device.
+ * configurator role for an IoT/other device. When provided with a valid DPP URI
+ * string, Settings will open a Wi-Fi selection screen for the user to indicate which network
+ * they would like to configure the device specified in the DPP URI string and
+ * carry them through the rest of the flow for provisioning the device.
* <p>
- * In some cases, a matching Activity may not exist, so ensure you safeguard against this by
- * checking WifiManager.isEasyConnectSupported();
+ * In some cases, a matching Activity may not exist, so ensure to safeguard against this by
+ * checking {@link WifiManager#isEasyConnectSupported()}.
* <p>
* Input: The Intent's data URI specifies bootstrapping information for authenticating and
- * provisioning the peer, with the "DPP" scheme.
+ * provisioning the peer, and uses a "DPP" scheme. The URI should be attached to the intent
+ * using {@link Intent#setData(Uri)}. The calling app can obtain a DPP URI in any
+ * way, e.g. by scanning a QR code or other out-of-band methods. The calling app may also
+ * attach the {@link #EXTRA_EASY_CONNECT_BAND_LIST} extra to provide information
+ * about the bands supported by the enrollee device.
* <p>
- * Output: After {@code startActivityForResult}, the callback {@code onActivityResult} will have
- * resultCode {@link android.app.Activity#RESULT_OK} if Wi-Fi Easy Connect configuration succeeded
- * and the user tapped 'Done' button, or {@link android.app.Activity#RESULT_CANCELED} if operation
- * failed and user tapped 'Cancel'. In case the operation has failed, a status code from {@link
- * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode} will be returned as
- * Extra {@link #EXTRA_EASY_CONNECT_ERROR_CODE}. Easy Connect R2 Enrollees report additional
- * details about the error they encountered, which will be provided in the {@link
- * #EXTRA_EASY_CONNECT_ATTEMPTED_SSID}, {@link #EXTRA_EASY_CONNECT_CHANNEL_LIST}, and {@link
- * #EXTRA_EASY_CONNECT_BAND_LIST}.
+ * Output: After calling {@link android.app.Activity#startActivityForResult}, the callback
+ * {@code onActivityResult} will have resultCode {@link android.app.Activity#RESULT_OK} if
+ * the Wi-Fi Easy Connect configuration succeeded and the user tapped the 'Done' button, or
+ * {@link android.app.Activity#RESULT_CANCELED} if the operation failed and user tapped the
+ * 'Cancel' button. In case the operation has failed, a status code from
+ * {@link android.net.wifi.EasyConnectStatusCallback} {@code EASY_CONNECT_EVENT_FAILURE_*} will
+ * be returned as an Extra {@link #EXTRA_EASY_CONNECT_ERROR_CODE}. Easy Connect R2
+ * Enrollees report additional details about the error they encountered, which will be
+ * provided in the {@link #EXTRA_EASY_CONNECT_ATTEMPTED_SSID},
+ * {@link #EXTRA_EASY_CONNECT_CHANNEL_LIST}, and {@link #EXTRA_EASY_CONNECT_BAND_LIST}.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI =
@@ -525,12 +545,15 @@
/**
* Activity Extra: The Easy Connect operation error code
* <p>
- * An extra returned on the result intent received when using the {@link
- * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
- * extra contains the error code of the operation - one of
- * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode}.
- * If there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK},
+ * An extra returned on the result intent received when using the
+ * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation.
+ * This extra contains the integer error code of the operation - one of
+ * {@link android.net.wifi.EasyConnectStatusCallback} {@code EASY_CONNECT_EVENT_FAILURE_*}. If
+ * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK},
* then this extra is not attached to the result intent.
+ * <p>
+ * Use the {@link Intent#hasExtra(String)} to determine whether the extra is attached and
+ * {@link Intent#getIntExtra(String, int)} to obtain the error code data.
*/
public static final String EXTRA_EASY_CONNECT_ERROR_CODE =
"android.provider.extra.EASY_CONNECT_ERROR_CODE";
@@ -542,11 +565,13 @@
* #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
* extra contains the SSID of the Access Point that the remote Enrollee tried to connect to.
* This value is populated only by remote R2 devices, and only for the following error codes:
- * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}
- * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}.
+ * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}
+ * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}.
* Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If
* there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then
* this extra is not attached to the result intent.
+ * <p>
+ * Use the {@link Intent#getStringExtra(String)} to obtain the SSID.
*/
public static final String EXTRA_EASY_CONNECT_ATTEMPTED_SSID =
"android.provider.extra.EASY_CONNECT_ATTEMPTED_SSID";
@@ -556,13 +581,15 @@
* <p>
* An extra returned on the result intent received when using the {@link
* #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
- * extra contains the list channels the Enrollee used to scan for a network. This value is
+ * extra contains the channel list that the Enrollee scanned for a network. This value is
* populated only by remote R2 devices, and only for the following error code: {@link
- * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}.
+ * android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}.
* Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If
* there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then
* this extra is not attached to the result intent. The list is JSON formatted, as an array
* (Wi-Fi global operating classes) of arrays (Wi-Fi channels).
+ * <p>
+ * Use the {@link Intent#getStringExtra(String)} to obtain the list.
*/
public static final String EXTRA_EASY_CONNECT_CHANNEL_LIST =
"android.provider.extra.EASY_CONNECT_CHANNEL_LIST";
@@ -570,17 +597,31 @@
/**
* Activity Extra: The Band List that the Enrollee supports.
* <p>
- * An extra returned on the result intent received when using the {@link
- * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
- * extra contains the bands the Enrollee supports, expressed as the Global Operating Class,
- * see Table E-4 in IEEE Std 802.11-2016 -Global operating classes. This value is populated only
- * by remote R2 devices, and only for the following error codes: {@link
- * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}
- * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}
- * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION}.
- * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If
- * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then
- * this extra is not attached to the result intent.
+ * This extra contains the bands the Enrollee supports, expressed as the Global Operating
+ * Class, see Table E-4 in IEEE Std 802.11-2016 Global operating classes. It is used both as
+ * input, to configure the Easy Connect operation and as output of the operation.
+ * <p>
+ * As input: an optional extra to be attached to the
+ * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI}. If attached, it indicates the bands which
+ * the remote device (enrollee, device-to-be-configured) supports. The Settings operation
+ * may take this into account when presenting the user with list of networks configurations
+ * to be used. The calling app may obtain this information in any out-of-band method. The
+ * information should be attached as an array of raw integers - using the
+ * {@link Intent#putExtra(String, int[])}.
+ * <p>
+ * As output: an extra returned on the result intent received when using the
+ * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation
+ * . This value is populated only by remote R2 devices, and only for the following error
+ * codes:
+ * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK},
+ * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION},
+ * or
+ * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION}.
+ * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}.
+ * If there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}
+ * , then this extra is not attached to the result intent.
+ * <p>
+ * Use the {@link Intent#getIntArrayExtra(String)} to obtain the list.
*/
public static final String EXTRA_EASY_CONNECT_BAND_LIST =
"android.provider.extra.EASY_CONNECT_BAND_LIST";
@@ -5096,7 +5137,6 @@
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_P2P_DEVICE_NAME);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SAVED_STATE);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS);
- MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_ENHANCED_AUTO_JOIN);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NETWORK_SHOW_RSSI);
@@ -6379,6 +6419,15 @@
"accessibility_button_target_component";
/**
+ * The system class name of magnification controller which is a target to be toggled via
+ * accessibility shortcut or accessibility button.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER =
+ "com.android.server.accessibility.MagnificationController";
+
+ /**
* If touch exploration is enabled.
*/
public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
@@ -8770,9 +8819,9 @@
* added to both AIRPLANE_MODE_RADIOS and AIRPLANE_MODE_TOGGLEABLE_RADIOS, then Wifi
* will be turned off when entering airplane mode, but the user will be able to reenable
* Wifi in the Settings app.
- *
- * {@hide}
+ * @hide
*/
+ @SystemApi
public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
/**
@@ -9944,24 +9993,17 @@
* Setting to allow scans to be enabled even wifi is turned off for connectivity.
* @hide
*/
+ @SystemApi
public static final String WIFI_SCAN_ALWAYS_AVAILABLE =
"wifi_scan_always_enabled";
/**
- * The interval in milliseconds at which wifi rtt ranging requests will be throttled when
- * they are coming from the background.
- *
- * @hide
- */
- public static final String WIFI_RTT_BACKGROUND_EXEC_GAP_MS =
- "wifi_rtt_background_exec_gap_ms";
-
- /**
* Indicate whether factory reset request is pending.
*
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String WIFI_P2P_PENDING_FACTORY_RESET =
"wifi_p2p_pending_factory_reset";
@@ -9971,6 +10013,7 @@
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
/**
@@ -10014,10 +10057,10 @@
* enabled state.
* @hide
*/
+ @SystemApi
public static final String NETWORK_RECOMMENDATIONS_ENABLED =
"network_recommendations_enabled";
-
/**
* Which package name to use for network recommendations. If null, network recommendations
* will neither be requested nor accepted.
@@ -10042,17 +10085,6 @@
public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
/**
- * The number of milliseconds the {@link com.android.server.NetworkScoreService}
- * will give a recommendation request to complete before returning a default response.
- *
- * Type: long
- * @hide
- * @deprecated to be removed
- */
- public static final String NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS =
- "network_recommendation_request_timeout_ms";
-
- /**
* The expiration time in milliseconds for the {@link android.net.WifiKey} request cache in
* {@link com.android.server.wifi.RecommendedNetworkEvaluator}.
*
@@ -10070,6 +10102,7 @@
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
/**
@@ -10178,18 +10211,11 @@
"wifi_watchdog_poor_network_test_enabled";
/**
- * Setting to turn on suspend optimizations at screen off on Wi-Fi. Enabled by default and
- * needs to be set to 0 to disable it.
- * @hide
- */
- public static final String WIFI_SUSPEND_OPTIMIZATIONS_ENABLED =
- "wifi_suspend_optimizations_enabled";
-
- /**
* Setting to enable verbose logging in Wi-Fi; disabled by default, and setting to 1
* will enable it. In the future, additional values may be supported.
* @hide
*/
+ @SystemApi
public static final String WIFI_VERBOSE_LOGGING_ENABLED =
"wifi_verbose_logging_enabled";
@@ -10215,69 +10241,10 @@
* Errors in the parameters will cause the entire setting to be ignored.
* @hide
*/
+ @SystemApi
public static final String WIFI_SCORE_PARAMS =
"wifi_score_params";
- /**
- * Setting to enable logging WifiIsUnusableEvent in metrics
- * which gets triggered when wifi becomes unusable.
- * Disabled by default, and setting it to 1 will enable it.
- * @hide
- */
- public static final String WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED =
- "wifi_is_unusable_event_metrics_enabled";
-
- /**
- * The minimum number of txBad the framework has to observe
- * to trigger a wifi data stall.
- * @hide
- */
- public static final String WIFI_DATA_STALL_MIN_TX_BAD =
- "wifi_data_stall_min_tx_bad";
-
- /**
- * The minimum number of txSuccess the framework has to observe
- * to trigger a wifi data stall when rxSuccess is 0.
- * @hide
- */
- public static final String WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX =
- "wifi_data_stall_min_tx_success_without_rx";
-
- /**
- * Setting to enable logging Wifi LinkSpeedCounts in metrics.
- * Disabled by default, and setting it to 1 will enable it.
- * @hide
- */
- public static final String WIFI_LINK_SPEED_METRICS_ENABLED =
- "wifi_link_speed_metrics_enabled";
-
- /**
- * Setting to enable the PNO frequency culling optimization.
- * Disabled by default, and setting it to 1 will enable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_PNO_FREQUENCY_CULLING_ENABLED =
- "wifi_pno_frequency_culling_enabled";
-
- /**
- * Setting to enable including recency information when determining pno network priorities.
- * Disabled by default, and setting it to 1 will enable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_PNO_RECENCY_SORTING_ENABLED =
- "wifi_pno_recency_sorting_enabled";
-
- /**
- * Setting to enable the Wi-Fi link probing.
- * Enabled by default, and setting it to 0 will disable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_LINK_PROBING_ENABLED =
- "wifi_link_probing_enabled";
-
/**
* The maximum number of times we will retry a connection to an access
* point for which we have failed in acquiring an IP address from DHCP.
@@ -10317,6 +10284,7 @@
* The Wi-Fi peer-to-peer device name
* @hide
*/
+ @SystemApi
public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
/**
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 2e7ac3f5..f369064 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4142,7 +4142,6 @@
* <li>{@link #ENABLE_CMAS_PRESIDENTIAL_PREF}</li>
* <li>{@link #ENABLE_ALERT_VIBRATION_PREF}</li>
* <li>{@link #ENABLE_EMERGENCY_PERF}</li>
- * <li>{@link #ENABLE_FULL_VOLUME_PREF}</li>
* <li>{@link #ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF}</li>
* </ul>
* @hide
@@ -4205,10 +4204,6 @@
public static final @NonNull String ENABLE_EMERGENCY_PERF =
"enable_emergency_alerts";
- /** Preference to enable volume for alerts */
- public static final @NonNull String ENABLE_FULL_VOLUME_PREF =
- "use_full_volume";
-
/** Preference to enable receive alerts in second language */
public static final @NonNull String ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF =
"receive_cmas_in_second_language";
diff --git a/core/java/android/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java
index d646e23..00060ab 100644
--- a/core/java/android/se/omapi/SEService.java
+++ b/core/java/android/se/omapi/SEService.java
@@ -22,14 +22,11 @@
package android.se.omapi;
-import android.app.ActivityThread;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -143,10 +140,6 @@
throw new NullPointerException("Arguments must not be null");
}
- if (!hasOMAPIReaders()) {
- throw new UnsupportedOperationException("Device does not support any OMAPI reader");
- }
-
mContext = context;
mSEListener.mListener = listener;
mSEListener.mExecutor = executor;
@@ -277,23 +270,4 @@
throw new IllegalStateException(e.getMessage());
}
}
-
- /**
- * Helper to check if this device support any OMAPI readers
- */
- private static boolean hasOMAPIReaders() {
- IPackageManager pm = ActivityThread.getPackageManager();
- if (pm == null) {
- Log.e(TAG, "Cannot get package manager, assuming OMAPI readers supported");
- return true;
- }
- try {
- return pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_UICC, 0)
- || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_ESE, 0)
- || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_SD, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "Package manager query failed, assuming OMAPI readers supported", e);
- return true;
- }
- }
}
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index d827b30..262d989 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.IntentSender;
import android.os.Parcel;
import android.os.Parcelable;
@@ -238,6 +239,7 @@
public Builder(@NonNull RemoteViews presentation,
@NonNull InlinePresentation inlinePresentation) {
Preconditions.checkNotNull(presentation, "presentation must be non-null");
+ Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null");
mPresentation = presentation;
mInlinePresentation = inlinePresentation;
}
@@ -248,7 +250,8 @@
* @param presentation The presentation used to visualize this dataset.
*/
public Builder(@NonNull RemoteViews presentation) {
- this(presentation, null);
+ Preconditions.checkNotNull(presentation, "presentation must be non-null");
+ mPresentation = presentation;
}
/**
@@ -262,7 +265,9 @@
* @hide
*/
@SystemApi
+ @TestApi
public Builder(@NonNull InlinePresentation inlinePresentation) {
+ Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null");
mInlinePresentation = inlinePresentation;
}
@@ -576,6 +581,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public @NonNull Builder setInlinePresentation(@NonNull AutofillId id,
@Nullable AutofillValue value, @Nullable Pattern filter,
@NonNull InlinePresentation inlinePresentation) {
@@ -672,11 +678,13 @@
// using specially crafted parcels.
final RemoteViews presentation = parcel.readParcelable(null);
final InlinePresentation inlinePresentation = parcel.readParcelable(null);
- final Builder builder = presentation == null
- ? new Builder(inlinePresentation)
- : inlinePresentation == null
+ final Builder builder = presentation != null
+ ? inlinePresentation == null
? new Builder(presentation)
- : new Builder(presentation, inlinePresentation);
+ : new Builder(presentation, inlinePresentation)
+ : inlinePresentation == null
+ ? new Builder()
+ : new Builder(inlinePresentation);
final ArrayList<AutofillId> ids =
parcel.createTypedArrayList(AutofillId.CREATOR);
final ArrayList<AutofillValue> values =
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index 5977baf..4ead3fc 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -49,6 +49,9 @@
void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel);
void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId);
void onNotificationsSeen(in List<String> keys);
+ void onPanelRevealed(int items);
+ void onPanelHidden();
+ void onNotificationVisibilityChanged(String key, boolean isVisible);
void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
void onNotificationDirectReply(String key);
void onSuggestedReplySent(String key, in CharSequence reply, int source);
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index da40254..e976e18 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -182,6 +182,32 @@
}
/**
+ * Implement this to know when the notification panel is revealed
+ *
+ * @param items Number of items on the panel at time of opening
+ */
+ public void onPanelRevealed(int items) {
+
+ }
+
+ /**
+ * Implement this to know when the notification panel is hidden
+ */
+ public void onPanelHidden() {
+
+ }
+
+ /**
+ * Implement this to know when a notification becomes visible or hidden from the user.
+ *
+ * @param key the notification key
+ * @param isVisible whether the notification is visible.
+ */
+ public void onNotificationVisibilityChanged(@NonNull String key, boolean isVisible) {
+
+ }
+
+ /**
* Implement this to know when a notification change (expanded / collapsed) is visible to user.
*
* @param key the notification key
@@ -337,6 +363,30 @@
}
@Override
+ public void onPanelRevealed(int items) {
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = items;
+ mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_REVEALED,
+ args).sendToTarget();
+ }
+
+ @Override
+ public void onPanelHidden() {
+ SomeArgs args = SomeArgs.obtain();
+ mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_HIDDEN,
+ args).sendToTarget();
+ }
+
+ @Override
+ public void onNotificationVisibilityChanged(String key, boolean isVisible) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = key;
+ args.argi1 = isVisible ? 1 : 0;
+ mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_VISIBILITY_CHANGED,
+ args).sendToTarget();
+ }
+
+ @Override
public void onNotificationExpansionChanged(String key, boolean isUserAction,
boolean isExpanded) {
SomeArgs args = SomeArgs.obtain();
@@ -394,6 +444,9 @@
public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6;
public static final int MSG_ON_ACTION_INVOKED = 7;
public static final int MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED = 8;
+ public static final int MSG_ON_PANEL_REVEALED = 9;
+ public static final int MSG_ON_PANEL_HIDDEN = 10;
+ public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 11;
public MyHandler(Looper looper) {
super(looper, null, false);
@@ -480,6 +533,25 @@
onAllowedAdjustmentsChanged();
break;
}
+ case MSG_ON_PANEL_REVEALED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ int items = args.argi1;
+ args.recycle();
+ onPanelRevealed(items);
+ break;
+ }
+ case MSG_ON_PANEL_HIDDEN: {
+ onPanelHidden();
+ break;
+ }
+ case MSG_ON_NOTIFICATION_VISIBILITY_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ String key = (String) args.arg1;
+ boolean isVisible = args.argi1 == 1;
+ args.recycle();
+ onNotificationVisibilityChanged(key, isVisible);
+ break;
+ }
}
}
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 80d054b..fd04f49 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1383,6 +1383,22 @@
}
@Override
+ public void onPanelRevealed(int items) throws RemoteException {
+ // no-op in the listener
+ }
+
+ @Override
+ public void onPanelHidden() throws RemoteException {
+ // no-op in the listener
+ }
+
+ @Override
+ public void onNotificationVisibilityChanged(
+ String key, boolean isVisible) {
+ // no-op in the listener
+ }
+
+ @Override
public void onNotificationSnoozedUntilContext(
IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)
throws RemoteException {
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 67925bf..0f33998 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -119,7 +119,9 @@
@IntDef(flag = true, prefix = { "RECOGNITION_FLAG_" }, value = {
RECOGNITION_FLAG_NONE,
RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
- RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+ RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
+ RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
+ RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
})
public @interface RecognitionFlags {}
@@ -144,6 +146,26 @@
*/
public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
+ /**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use AEC.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+ * this flag is {@link #AUDIO_CAPABILITY_ECHO_CANCELLATION}. If this flag is passed without the
+ * audio capability supported, there will be no audio effect applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
+
+ /**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use noise suppression.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+ * this flag is {@link #AUDIO_CAPABILITY_NOISE_SUPPRESSION}. If this flag is passed without the
+ * audio capability supported, there will be no audio effect applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
+
//---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----//
// Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags.
@@ -168,6 +190,46 @@
public static final int RECOGNITION_MODE_USER_IDENTIFICATION
= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
+ //-- Audio capabilities. Values in returned bit field for getSupportedAudioCapabilities() --//
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+ AUDIO_CAPABILITY_ECHO_CANCELLATION,
+ AUDIO_CAPABILITY_NOISE_SUPPRESSION,
+ })
+ public @interface AudioCapabilities {}
+
+ /**
+ * If set the underlying module supports AEC.
+ * Returned by {@link #getSupportedAudioCapabilities()}
+ */
+ public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION =
+ SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+
+ /**
+ * If set, the underlying module supports noise suppression.
+ * Returned by {@link #getSupportedAudioCapabilities()}
+ */
+ public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION =
+ SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = {
+ MODEL_PARAM_THRESHOLD_FACTOR,
+ })
+ public @interface ModelParams {}
+
+ /**
+ * Controls the sensitivity threshold adjustment factor for a given model.
+ * Negative value corresponds to less sensitive model (high threshold) and
+ * a positive value corresponds to a more sensitive model (low threshold).
+ * Default value is 0.
+ */
+ public static final int MODEL_PARAM_THRESHOLD_FACTOR =
+ android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR;
+
static final String TAG = "AlwaysOnHotwordDetector";
static final boolean DBG = false;
@@ -198,6 +260,53 @@
private int mAvailability = STATE_NOT_READY;
/**
+ * A ModelParamRange is a representation of supported parameter range for a
+ * given loaded model.
+ */
+ public static final class ModelParamRange {
+ private final SoundTrigger.ModelParamRange mModelParamRange;
+
+ /** @hide */
+ ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) {
+ mModelParamRange = modelParamRange;
+ }
+
+ /**
+ * The inclusive start of supported range.
+ *
+ * @return start of range
+ */
+ public int start() {
+ return mModelParamRange.start;
+ }
+
+ /**
+ * The inclusive end of supported range.
+ *
+ * @return end of range
+ */
+ public int end() {
+ return mModelParamRange.end;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return mModelParamRange.toString();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ return mModelParamRange.equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return mModelParamRange.hashCode();
+ }
+ }
+
+ /**
* Additional payload for {@link Callback#onDetected}.
*/
public static class EventPayload {
@@ -385,6 +494,37 @@
}
/**
+ * Get the audio capabilities supported by the platform which can be enabled when
+ * starting a recognition.
+ *
+ * @see #AUDIO_CAPABILITY_ECHO_CANCELLATION
+ * @see #AUDIO_CAPABILITY_NOISE_SUPPRESSION
+ *
+ * @return Bit field encoding of the AudioCapabilities supported.
+ */
+ @AudioCapabilities
+ public int getSupportedAudioCapabilities() {
+ if (DBG) Slog.d(TAG, "getSupportedAudioCapabilities()");
+ synchronized (mLock) {
+ return getSupportedAudioCapabilitiesLocked();
+ }
+ }
+
+ private int getSupportedAudioCapabilitiesLocked() {
+ try {
+ ModuleProperties properties =
+ mModelManagementService.getDspModuleProperties(mVoiceInteractionService);
+ if (properties != null) {
+ return properties.audioCapabilities;
+ }
+
+ return 0;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Starts recognition for the associated keyphrase.
*
* @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
@@ -445,6 +585,83 @@
}
/**
+ * Set a model specific {@link ModelParams} with the given value. This
+ * parameter will keep its value for the duration the model is loaded regardless of starting and
+ * stopping recognition. Once the model is unloaded, the value will be lost.
+ * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this
+ * method.
+ *
+ * @param modelParam {@link ModelParams}
+ * @param value Value to set
+ * @return - {@link SoundTrigger#STATUS_OK} in case of success
+ * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+ * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+ * if API is not supported by HAL
+ */
+ public int setParameter(@ModelParams int modelParam, int value) {
+ if (DBG) {
+ Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")");
+ }
+
+ synchronized (mLock) {
+ if (mAvailability == STATE_INVALID) {
+ throw new IllegalStateException("setParameter called on an invalid detector");
+ }
+
+ return setParameterLocked(modelParam, value);
+ }
+ }
+
+ /**
+ * Get a model specific {@link ModelParams}. This parameter will keep its value
+ * for the duration the model is loaded regardless of starting and stopping recognition.
+ * Once the model is unloaded, the value will be lost. If the value is not set, a default
+ * value is returned. See {@link ModelParams} for parameter default values.
+ * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before
+ * calling this method.
+ *
+ * @param modelParam {@link ModelParams}
+ * @return value of parameter
+ */
+ public int getParameter(@ModelParams int modelParam) {
+ if (DBG) {
+ Slog.d(TAG, "getParameter(" + modelParam + ")");
+ }
+
+ synchronized (mLock) {
+ if (mAvailability == STATE_INVALID) {
+ throw new IllegalStateException("getParameter called on an invalid detector");
+ }
+
+ return getParameterLocked(modelParam);
+ }
+ }
+
+ /**
+ * Determine if parameter control is supported for the given model handle.
+ * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter}
+ * or {@link AlwaysOnHotwordDetector#getParameter}.
+ *
+ * @param modelParam {@link ModelParams}
+ * @return supported range of parameter, null if not supported
+ */
+ @Nullable
+ public ModelParamRange queryParameter(@ModelParams int modelParam) {
+ if (DBG) {
+ Slog.d(TAG, "queryParameter(" + modelParam + ")");
+ }
+
+ synchronized (mLock) {
+ if (mAvailability == STATE_INVALID) {
+ throw new IllegalStateException("queryParameter called on an invalid detector");
+ }
+
+ return queryParameterLocked(modelParam);
+ }
+ }
+
+ /**
* Creates an intent to start the enrollment for the associated keyphrase.
* This intent must be invoked using {@link Context#startForegroundService(Intent)}.
* Starting re-enrollment is only valid if the keyphrase is un-enrolled,
@@ -571,12 +788,21 @@
(recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
boolean allowMultipleTriggers =
(recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
+
+ int audioCapabilities = 0;
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
+ audioCapabilities |= AUDIO_CAPABILITY_ECHO_CANCELLATION;
+ }
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
+ audioCapabilities |= AUDIO_CAPABILITY_NOISE_SUPPRESSION;
+ }
+
int code = STATUS_ERROR;
try {
code = mModelManagementService.startRecognition(mVoiceInteractionService,
mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback,
new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
- recognitionExtra, null /* additional data */));
+ recognitionExtra, null /* additional data */, audioCapabilities));
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in startRecognition!", e);
}
@@ -601,6 +827,47 @@
return code;
}
+ private int setParameterLocked(@ModelParams int modelParam, int value) {
+ try {
+ int code = mModelManagementService.setParameter(mVoiceInteractionService,
+ mKeyphraseMetadata.id, modelParam, value);
+
+ if (code != STATUS_OK) {
+ Slog.w(TAG, "setParameter failed with error code " + code);
+ }
+
+ return code;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private int getParameterLocked(@ModelParams int modelParam) {
+ try {
+ return mModelManagementService.getParameter(mVoiceInteractionService,
+ mKeyphraseMetadata.id, modelParam);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Nullable
+ private ModelParamRange queryParameterLocked(@ModelParams int modelParam) {
+ try {
+ SoundTrigger.ModelParamRange modelParamRange =
+ mModelManagementService.queryParameter(mVoiceInteractionService,
+ mKeyphraseMetadata.id, modelParam);
+
+ if (modelParamRange == null) {
+ return null;
+ }
+
+ return new ModelParamRange(modelParamRange);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private void notifyStateChangedLocked() {
Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
message.arg1 = mAvailability;
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e08a06a..c9d3b92 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -384,6 +384,17 @@
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
public static final int LISTEN_REGISTRATION_FAILURE = 0x40000000;
+ /**
+ * Listen for Barring Information for the current registered / camped cell.
+ *
+ * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
+ * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+ *
+ * @see #onBarringInfoChanged()
+ */
+ @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+ public static final int LISTEN_BARRING_INFO = 0x80000000;
+
/*
* Subscription used to listen to the phone state changes
* @hide
@@ -979,6 +990,20 @@
}
/**
+ * Report updated barring information for the current camped/registered cell.
+ *
+ * <p>Barring info is provided for all services applicable to the current camped/registered
+ * cell, for the registered PLMN and current access class/access category.
+ *
+ * @param barringInfo for all services on the current cell.
+ *
+ * @see android.telephony.BarringInfo
+ */
+ public void onBarringInfoChanged(@NonNull BarringInfo barringInfo) {
+ // default implementation empty
+ }
+
+ /**
* The callback methods need to be called on the handler thread where
* this object was created. If the binder did that for us it'd be nice.
*
@@ -1262,6 +1287,14 @@
cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode)));
// default implementation empty
}
+
+ public void onBarringInfoChanged(BarringInfo barringInfo) {
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onBarringInfoChanged(barringInfo)));
+ }
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 9387a2c..4dffa62 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -708,4 +708,21 @@
} catch (RemoteException ex) {
}
}
+
+ /**
+ * Notify {@link BarringInfo} has changed for a specific subscription.
+ *
+ * @param slotIndex for the phone object that got updated barring info.
+ * @param subId for which the BarringInfo changed.
+ * @param barringInfo updated BarringInfo.
+ */
+ public void notifyBarringInfoChanged(
+ int slotIndex, int subId, @NonNull BarringInfo barringInfo) {
+ try {
+ sRegistry.notifyBarringInfoChanged(slotIndex, subId, barringInfo);
+ } catch (RemoteException ex) {
+ // system server crash
+ }
+ }
+
}
diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java
new file mode 100644
index 0000000..ada59d6
--- /dev/null
+++ b/core/java/android/timezone/CountryTimeZones.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.icu.util.TimeZone;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Information about a country's time zones.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class CountryTimeZones {
+
+ /**
+ * A mapping to a time zone ID with some associated metadata.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final class TimeZoneMapping {
+
+ private libcore.timezone.CountryTimeZones.TimeZoneMapping mDelegate;
+
+ TimeZoneMapping(libcore.timezone.CountryTimeZones.TimeZoneMapping delegate) {
+ this.mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns the ID for this mapping. See also {@link #getTimeZone()} which handles when the
+ * ID is unrecognized.
+ */
+ @NonNull
+ public String getTimeZoneId() {
+ return mDelegate.timeZoneId;
+ }
+
+ /**
+ * Returns a {@link TimeZone} object for this mapping, or {@code null} if the ID is
+ * unrecognized.
+ */
+ @Nullable
+ public TimeZone getTimeZone() {
+ return mDelegate.getTimeZone();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneMapping that = (TimeZoneMapping) o;
+ return this.mDelegate.equals(that.mDelegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.mDelegate.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+ }
+
+ /**
+ * The result of lookup up a time zone using offset information (and possibly more).
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final class OffsetResult {
+
+ private final TimeZone mTimeZone;
+ private final boolean mIsOnlyMatch;
+
+ /** Creates an instance with the supplied information. */
+ public OffsetResult(@NonNull TimeZone timeZone, boolean isOnlyMatch) {
+ mTimeZone = Objects.requireNonNull(timeZone);
+ mIsOnlyMatch = isOnlyMatch;
+ }
+
+ /**
+ * Returns a time zone that matches the supplied criteria.
+ */
+ @NonNull
+ public TimeZone getTimeZone() {
+ return mTimeZone;
+ }
+
+ /**
+ * Returns {@code true} if there is only one matching time zone for the supplied criteria.
+ */
+ public boolean isOnlyMatch() {
+ return mIsOnlyMatch;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ OffsetResult that = (OffsetResult) o;
+ return mIsOnlyMatch == that.mIsOnlyMatch
+ && mTimeZone.getID().equals(that.mTimeZone.getID());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTimeZone, mIsOnlyMatch);
+ }
+
+ @Override
+ public String toString() {
+ return "OffsetResult{"
+ + "mTimeZone=" + mTimeZone
+ + ", mIsOnlyMatch=" + mIsOnlyMatch
+ + '}';
+ }
+ }
+
+ @NonNull
+ private final libcore.timezone.CountryTimeZones mDelegate;
+
+ CountryTimeZones(libcore.timezone.CountryTimeZones delegate) {
+ mDelegate = delegate;
+ }
+
+ /**
+ * Returns true if the ISO code for the country is a match for the one specified.
+ */
+ public boolean isForCountryCode(@NonNull String countryIso) {
+ return mDelegate.isForCountryCode(countryIso);
+ }
+
+ /**
+ * Returns the default time zone ID for the country. Can return {@code null} in cases when no
+ * data is available or the time zone ID was not recognized.
+ */
+ @Nullable
+ public String getDefaultTimeZoneId() {
+ return mDelegate.getDefaultTimeZoneId();
+ }
+
+ /**
+ * Returns the default time zone for the country. Can return {@code null} in cases when no data
+ * is available or the time zone ID was not recognized.
+ */
+ @Nullable
+ public TimeZone getDefaultTimeZone() {
+ return mDelegate.getDefaultTimeZone();
+ }
+
+ /**
+ * Qualifier for a country's default time zone. {@code true} indicates whether the default
+ * would be a good choice <em>generally</em> when there's no other information available.
+ */
+ public boolean isDefaultTimeZoneBoosted() {
+ return mDelegate.getDefaultTimeZoneBoost();
+ }
+
+ /**
+ * Returns true if the country has at least one zone that is the same as UTC at the given time.
+ */
+ public boolean hasUtcZone(long whenMillis) {
+ return mDelegate.hasUtcZone(whenMillis);
+ }
+
+ /**
+ * Returns a time zone for the country, if there is one, that matches the desired properties. If
+ * there are multiple matches and the {@code bias} is one of them then it is returned, otherwise
+ * an arbitrary match is returned based on the {@link #getEffectiveTimeZoneMappingsAt(long)}
+ * ordering.
+ *
+ * @param totalOffsetMillis the offset from UTC at {@code whenMillis}
+ * @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST,
+ * {@code false} means not DST, {@code null} means unknown
+ * @param dstOffsetMillis the part of {@code totalOffsetMillis} contributed by DST, only used if
+ * {@code isDst} is {@code true}. The value can be {@code null} if the DST offset is
+ * unknown
+ * @param whenMillis the UTC time to match against
+ * @param bias the time zone to prefer, can be {@code null}
+ */
+ @Nullable
+ public OffsetResult lookupByOffsetWithBias(int totalOffsetMillis, @Nullable Boolean isDst,
+ @SuppressLint("AutoBoxing") @Nullable Integer dstOffsetMillis, long whenMillis,
+ @Nullable TimeZone bias) {
+ libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult =
+ mDelegate.lookupByOffsetWithBias(
+ totalOffsetMillis, isDst, dstOffsetMillis, whenMillis, bias);
+ return delegateOffsetResult == null ? null :
+ new OffsetResult(delegateOffsetResult.mTimeZone, delegateOffsetResult.mOneMatch);
+ }
+
+ /**
+ * Returns an immutable, ordered list of time zone mappings for the country in an undefined but
+ * "priority" order, filtered so that only "effective" time zone IDs are returned. An
+ * "effective" time zone is one that differs from another time zone used in the country after
+ * {@code whenMillis}. The list can be empty if there were no zones configured or the configured
+ * zone IDs were not recognized.
+ */
+ @NonNull
+ public List<TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long whenMillis) {
+ List<libcore.timezone.CountryTimeZones.TimeZoneMapping> delegateList =
+ mDelegate.getEffectiveTimeZoneMappingsAt(whenMillis);
+
+ List<TimeZoneMapping> toReturn = new ArrayList<>(delegateList.size());
+ for (libcore.timezone.CountryTimeZones.TimeZoneMapping delegateMapping : delegateList) {
+ toReturn.add(new TimeZoneMapping(delegateMapping));
+ }
+ return Collections.unmodifiableList(toReturn);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CountryTimeZones that = (CountryTimeZones) o;
+ return mDelegate.equals(that.mDelegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDelegate);
+ }
+
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+}
diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java
new file mode 100644
index 0000000..39dbe85
--- /dev/null
+++ b/core/java/android/timezone/TelephonyLookup.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+
+/**
+ * A class that can find time zone-related information about telephony networks.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyLookup {
+
+ private static Object sLock = new Object();
+ @GuardedBy("sLock")
+ private static TelephonyLookup sInstance;
+
+ @NonNull
+ private final libcore.timezone.TelephonyLookup mDelegate;
+
+ /**
+ * Obtains an instance for use when resolving telephony time zone information. This method never
+ * returns {@code null}.
+ */
+ @NonNull
+ public static TelephonyLookup getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new TelephonyLookup(libcore.timezone.TelephonyLookup.getInstance());
+ }
+ return sInstance;
+ }
+ }
+
+ private TelephonyLookup(@NonNull libcore.timezone.TelephonyLookup delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns an object capable of querying telephony network information. This method can return
+ * {@code null} in the event of an error while reading the underlying data files.
+ */
+ @Nullable
+ public TelephonyNetworkFinder getTelephonyNetworkFinder() {
+ libcore.timezone.TelephonyNetworkFinder telephonyNetworkFinderDelegate =
+ mDelegate.getTelephonyNetworkFinder();
+ return telephonyNetworkFinderDelegate != null
+ ? new TelephonyNetworkFinder(telephonyNetworkFinderDelegate) : null;
+ }
+}
diff --git a/core/java/android/timezone/TelephonyNetwork.java b/core/java/android/timezone/TelephonyNetwork.java
new file mode 100644
index 0000000..ae39fbd
--- /dev/null
+++ b/core/java/android/timezone/TelephonyNetwork.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.util.Objects;
+
+/**
+ * Information about a telephony network.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyNetwork {
+
+ @NonNull
+ private final libcore.timezone.TelephonyNetwork mDelegate;
+
+ TelephonyNetwork(@NonNull libcore.timezone.TelephonyNetwork delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns the Mobile Country Code of the network.
+ */
+ @NonNull
+ public String getMcc() {
+ return mDelegate.getMcc();
+ }
+
+ /**
+ * Returns the Mobile Network Code of the network.
+ */
+ @NonNull
+ public String getMnc() {
+ return mDelegate.getMnc();
+ }
+
+ /**
+ * Returns the country in which the network operates as an ISO 3166 alpha-2 (lower case).
+ */
+ @NonNull
+ public String getCountryIsoCode() {
+ return mDelegate.getCountryIsoCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TelephonyNetwork that = (TelephonyNetwork) o;
+ return mDelegate.equals(that.mDelegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDelegate);
+ }
+
+ @Override
+ public String toString() {
+ return "TelephonyNetwork{"
+ + "mDelegate=" + mDelegate
+ + '}';
+ }
+}
diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java
new file mode 100644
index 0000000..a81a516
--- /dev/null
+++ b/core/java/android/timezone/TelephonyNetworkFinder.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.util.Objects;
+
+/**
+ * A class that can find telephony networks loaded via {@link TelephonyLookup}.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyNetworkFinder {
+
+ @NonNull
+ private final libcore.timezone.TelephonyNetworkFinder mDelegate;
+
+ TelephonyNetworkFinder(libcore.timezone.TelephonyNetworkFinder delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns information held about a specific MCC + MNC combination. It is expected for this
+ * method to return {@code null}. Only known, unusual networks will typically have information
+ * returned, e.g. if they operate in countries other than the one suggested by their MCC.
+ */
+ @Nullable
+ public TelephonyNetwork findNetworkByMccMnc(@NonNull String mcc, @NonNull String mnc) {
+ Objects.requireNonNull(mcc);
+ Objects.requireNonNull(mnc);
+
+ libcore.timezone.TelephonyNetwork telephonyNetworkDelegate =
+ mDelegate.findNetworkByMccMnc(mcc, mnc);
+ return telephonyNetworkDelegate != null
+ ? new TelephonyNetwork(telephonyNetworkDelegate) : null;
+ }
+}
diff --git a/core/java/android/timezone/TimeZoneFinder.java b/core/java/android/timezone/TimeZoneFinder.java
new file mode 100644
index 0000000..15dfe62
--- /dev/null
+++ b/core/java/android/timezone/TimeZoneFinder.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A class that can be used to find time zones.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class TimeZoneFinder {
+
+ private static Object sLock = new Object();
+ @GuardedBy("sLock")
+ private static TimeZoneFinder sInstance;
+
+ private final libcore.timezone.TimeZoneFinder mDelegate;
+
+ private TimeZoneFinder(libcore.timezone.TimeZoneFinder delegate) {
+ mDelegate = delegate;
+ }
+
+ /**
+ * Obtains an instance for use when resolving telephony time zone information. This method never
+ * returns {@code null}.
+ */
+ @NonNull
+ public static TimeZoneFinder getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new TimeZoneFinder(libcore.timezone.TimeZoneFinder.getInstance());
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * Returns a {@link CountryTimeZones} object associated with the specified country code.
+ * Caching is handled as needed. If the country code is not recognized or there is an error
+ * during lookup this method can return null.
+ */
+ @Nullable
+ public CountryTimeZones lookupCountryTimeZones(@NonNull String countryIso) {
+ libcore.timezone.CountryTimeZones delegate = mDelegate.lookupCountryTimeZones(countryIso);
+ return delegate == null ? null : new CountryTimeZones(delegate);
+ }
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 1b2db36..9d22d30 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -42,6 +42,8 @@
public static final String DYNAMIC_SYSTEM = "settings_dynamic_system";
public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2";
public static final String SETTINGS_FUSE_FLAG = "settings_fuse";
+ public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ =
+ "settings_notif_convo_bypass_shortcut_req";
private static final Map<String, String> DEFAULT_FLAGS;
@@ -60,6 +62,7 @@
DEFAULT_FLAGS.put("settings_work_profile", "true");
DEFAULT_FLAGS.put("settings_controller_loading_enhancement", "false");
DEFAULT_FLAGS.put("settings_conditionals", "false");
+ DEFAULT_FLAGS.put(NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false");
}
/**
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index fa994ba..0892c94 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -16,6 +16,8 @@
package android.util;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
@@ -25,172 +27,270 @@
import android.net.NetworkInfo;
import android.net.SntpClient;
import android.os.SystemClock;
-import android.os.TimestampedValue;
import android.provider.Settings;
import android.text.TextUtils;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
/**
- * {@link TrustedTime} that connects with a remote NTP server as its trusted
- * time source.
+ * A singleton that connects with a remote NTP server as its trusted time source. This class
+ * is thread-safe. The {@link #forceRefresh()} method is synchronous, i.e. it may occupy the
+ * current thread while performing an NTP request. All other threads calling {@link #forceRefresh()}
+ * will block during that request.
*
* @hide
*/
public class NtpTrustedTime implements TrustedTime {
+
+ /**
+ * The result of a successful NTP query.
+ *
+ * @hide
+ */
+ public static class TimeResult {
+ private final long mTimeMillis;
+ private final long mElapsedRealtimeMillis;
+ private final long mCertaintyMillis;
+
+ public TimeResult(long timeMillis, long elapsedRealtimeMillis, long certaintyMillis) {
+ mTimeMillis = timeMillis;
+ mElapsedRealtimeMillis = elapsedRealtimeMillis;
+ mCertaintyMillis = certaintyMillis;
+ }
+
+ public long getTimeMillis() {
+ return mTimeMillis;
+ }
+
+ public long getElapsedRealtimeMillis() {
+ return mElapsedRealtimeMillis;
+ }
+
+ public long getCertaintyMillis() {
+ return mCertaintyMillis;
+ }
+
+ /** Calculates and returns the current time accounting for the age of this result. */
+ public long currentTimeMillis() {
+ return mTimeMillis + getAgeMillis();
+ }
+
+ /** Calculates and returns the age of this result. */
+ public long getAgeMillis() {
+ return SystemClock.elapsedRealtime() - mElapsedRealtimeMillis;
+ }
+
+ @Override
+ public String toString() {
+ return "TimeResult{"
+ + "mTimeMillis=" + mTimeMillis
+ + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
+ + ", mCertaintyMillis=" + mCertaintyMillis
+ + '}';
+ }
+ }
+
private static final String TAG = "NtpTrustedTime";
private static final boolean LOGD = false;
private static NtpTrustedTime sSingleton;
- private static Context sContext;
- private final String mServer;
- private final long mTimeout;
+ @NonNull
+ private final Context mContext;
- private ConnectivityManager mCM;
+ /**
+ * A supplier that returns the ConnectivityManager. The Supplier can return null if
+ * ConnectivityService isn't running yet.
+ */
+ private final Supplier<ConnectivityManager> mConnectivityManagerSupplier =
+ new Supplier<ConnectivityManager>() {
+ private ConnectivityManager mConnectivityManager;
- private boolean mHasCache;
- private long mCachedNtpTime;
- private long mCachedNtpElapsedRealtime;
- private long mCachedNtpCertainty;
+ @Nullable
+ @Override
+ public synchronized ConnectivityManager get() {
+ // We can't do this at initialization time: ConnectivityService might not be running
+ // yet.
+ if (mConnectivityManager == null) {
+ mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+ }
+ return mConnectivityManager;
+ }
+ };
- private NtpTrustedTime(String server, long timeout) {
- if (LOGD) Log.d(TAG, "creating NtpTrustedTime using " + server);
- mServer = server;
- mTimeout = timeout;
+ // Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during
+ // forceRefresh().
+ private volatile TimeResult mTimeResult;
+
+ private NtpTrustedTime(Context context) {
+ mContext = Objects.requireNonNull(context);
}
@UnsupportedAppUsage
public static synchronized NtpTrustedTime getInstance(Context context) {
if (sSingleton == null) {
- final Resources res = context.getResources();
- final ContentResolver resolver = context.getContentResolver();
-
- final String defaultServer = res.getString(
- com.android.internal.R.string.config_ntpServer);
- final long defaultTimeout = res.getInteger(
- com.android.internal.R.integer.config_ntpTimeout);
-
- final String secureServer = Settings.Global.getString(
- resolver, Settings.Global.NTP_SERVER);
- final long timeout = Settings.Global.getLong(
- resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout);
-
- final String server = secureServer != null ? secureServer : defaultServer;
- sSingleton = new NtpTrustedTime(server, timeout);
- sContext = context;
+ Context appContext = context.getApplicationContext();
+ sSingleton = new NtpTrustedTime(appContext);
}
-
return sSingleton;
}
- @Override
@UnsupportedAppUsage
public boolean forceRefresh() {
- // We can't do this at initialization time: ConnectivityService might not be running yet.
synchronized (this) {
- if (mCM == null) {
- mCM = sContext.getSystemService(ConnectivityManager.class);
+ NtpConnectionInfo connectionInfo = getNtpConnectionInfo();
+ if (connectionInfo == null) {
+ // missing server config, so no trusted time available
+ if (LOGD) Log.d(TAG, "forceRefresh: invalid server config");
+ return false;
}
- }
- final Network network = mCM == null ? null : mCM.getActiveNetwork();
- return forceRefresh(network);
- }
-
- public boolean forceRefresh(Network network) {
- if (TextUtils.isEmpty(mServer)) {
- // missing server, so no trusted time available
- return false;
- }
-
- // We can't do this at initialization time: ConnectivityService might not be running yet.
- synchronized (this) {
- if (mCM == null) {
- mCM = sContext.getSystemService(ConnectivityManager.class);
+ ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();
+ if (connectivityManager == null) {
+ if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager");
+ return false;
}
- }
+ final Network network = connectivityManager.getActiveNetwork();
+ final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
+ if (ni == null || !ni.isConnected()) {
+ if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
+ return false;
+ }
- final NetworkInfo ni = mCM == null ? null : mCM.getNetworkInfo(network);
- if (ni == null || !ni.isConnected()) {
- if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
- return false;
- }
-
-
- if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
- final SntpClient client = new SntpClient();
- if (client.requestTime(mServer, (int) mTimeout, network)) {
- mHasCache = true;
- mCachedNtpTime = client.getNtpTime();
- mCachedNtpElapsedRealtime = client.getNtpTimeReference();
- mCachedNtpCertainty = client.getRoundTripTime() / 2;
- return true;
- } else {
- return false;
+ if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
+ final SntpClient client = new SntpClient();
+ final String serverName = connectionInfo.getServer();
+ final int timeoutMillis = connectionInfo.getTimeoutMillis();
+ if (client.requestTime(serverName, timeoutMillis, network)) {
+ long ntpCertainty = client.getRoundTripTime() / 2;
+ mTimeResult = new TimeResult(
+ client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty);
+ return true;
+ } else {
+ return false;
+ }
}
}
- @Override
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
@UnsupportedAppUsage
public boolean hasCache() {
- return mHasCache;
+ return mTimeResult != null;
}
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
@Override
public long getCacheAge() {
- if (mHasCache) {
- return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
+ TimeResult timeResult = mTimeResult;
+ if (timeResult != null) {
+ return SystemClock.elapsedRealtime() - timeResult.getElapsedRealtimeMillis();
} else {
return Long.MAX_VALUE;
}
}
- @Override
- public long getCacheCertainty() {
- if (mHasCache) {
- return mCachedNtpCertainty;
- } else {
- return Long.MAX_VALUE;
- }
- }
-
- @Override
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
@UnsupportedAppUsage
public long currentTimeMillis() {
- if (!mHasCache) {
+ TimeResult timeResult = mTimeResult;
+ if (timeResult == null) {
throw new IllegalStateException("Missing authoritative time source");
}
if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit");
// current time is age after the last ntp cache; callers who
- // want fresh values will hit makeAuthoritative() first.
- return mCachedNtpTime + getCacheAge();
- }
-
- @UnsupportedAppUsage
- public long getCachedNtpTime() {
- if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
- return mCachedNtpTime;
- }
-
- @UnsupportedAppUsage
- public long getCachedNtpTimeReference() {
- return mCachedNtpElapsedRealtime;
+ // want fresh values will hit forceRefresh() first.
+ return timeResult.currentTimeMillis();
}
/**
- * Returns the combination of {@link #getCachedNtpTime()} and {@link
- * #getCachedNtpTimeReference()} as a {@link TimestampedValue}. This method is useful when
- * passing the time to another component that will adjust for elapsed time.
+ * Only kept for UnsupportedAppUsage.
*
- * @throws IllegalStateException if there is no cached value
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
*/
- public TimestampedValue<Long> getCachedNtpTimeSignal() {
- if (!mHasCache) {
- throw new IllegalStateException("Missing authoritative time source");
- }
- if (LOGD) Log.d(TAG, "getCachedNtpTimeSignal() cache hit");
-
- return new TimestampedValue<>(mCachedNtpElapsedRealtime, mCachedNtpTime);
+ @Deprecated
+ @UnsupportedAppUsage
+ public long getCachedNtpTime() {
+ if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
+ TimeResult timeResult = mTimeResult;
+ return timeResult == null ? 0 : timeResult.getTimeMillis();
}
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public long getCachedNtpTimeReference() {
+ TimeResult timeResult = mTimeResult;
+ return timeResult == null ? 0 : timeResult.getElapsedRealtimeMillis();
+ }
+
+ /**
+ * Returns an object containing the latest NTP information available. Can return {@code null} if
+ * no information is available.
+ */
+ @Nullable
+ public TimeResult getCachedTimeResult() {
+ return mTimeResult;
+ }
+
+ private static class NtpConnectionInfo {
+
+ @NonNull private final String mServer;
+ private final int mTimeoutMillis;
+
+ NtpConnectionInfo(@NonNull String server, int timeoutMillis) {
+ mServer = Objects.requireNonNull(server);
+ mTimeoutMillis = timeoutMillis;
+ }
+
+ @NonNull
+ public String getServer() {
+ return mServer;
+ }
+
+ int getTimeoutMillis() {
+ return mTimeoutMillis;
+ }
+ }
+
+ @GuardedBy("this")
+ private NtpConnectionInfo getNtpConnectionInfo() {
+ final ContentResolver resolver = mContext.getContentResolver();
+
+ final Resources res = mContext.getResources();
+ final String defaultServer = res.getString(
+ com.android.internal.R.string.config_ntpServer);
+ final int defaultTimeoutMillis = res.getInteger(
+ com.android.internal.R.integer.config_ntpTimeout);
+
+ final String secureServer = Settings.Global.getString(
+ resolver, Settings.Global.NTP_SERVER);
+ final int timeoutMillis = Settings.Global.getInt(
+ resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis);
+
+ final String server = secureServer != null ? secureServer : defaultServer;
+ return TextUtils.isEmpty(server) ? null : new NtpConnectionInfo(server, timeoutMillis);
+ }
}
diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java
index 952d7cb..8635340 100644
--- a/core/java/android/util/StatsLog.java
+++ b/core/java/android/util/StatsLog.java
@@ -254,6 +254,7 @@
* @param statsEvent The StatsEvent object containing the encoded buffer of data to write.
* @hide
*/
+ @SystemApi
public static void write(@NonNull final StatsEvent statsEvent) {
writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId());
statsEvent.release();
diff --git a/core/java/android/util/TrustedTime.java b/core/java/android/util/TrustedTime.java
index 1360f87..f41fe85 100644
--- a/core/java/android/util/TrustedTime.java
+++ b/core/java/android/util/TrustedTime.java
@@ -20,42 +20,48 @@
/**
* Interface that provides trusted time information, possibly coming from an NTP
- * server. Implementations may cache answers until {@link #forceRefresh()}.
+ * server.
*
* @hide
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
public interface TrustedTime {
/**
* Force update with an external trusted time source, returning {@code true}
* when successful.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
public boolean forceRefresh();
/**
* Check if this instance has cached a response from a trusted time source.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
- public boolean hasCache();
+ boolean hasCache();
/**
* Return time since last trusted time source contact, or
* {@link Long#MAX_VALUE} if never contacted.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
public long getCacheAge();
/**
- * Return certainty of cached trusted time in milliseconds, or
- * {@link Long#MAX_VALUE} if never contacted. Smaller values are more
- * precise.
- */
- public long getCacheCertainty();
-
- /**
* Return current time similar to {@link System#currentTimeMillis()},
* possibly using a cached authoritative time source.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
- public long currentTimeMillis();
+ long currentTimeMillis();
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4368115..178b3c0 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
@@ -1010,6 +1011,9 @@
* @return Supported WCG color spaces.
* @hide
*/
+ @SuppressLint("VisiblySynchronized")
+ @NonNull
+ @TestApi
public @ColorMode ColorSpace[] getSupportedWideColorGamut() {
synchronized (this) {
final ColorSpace[] defaultColorSpaces = new ColorSpace[0];
diff --git a/core/java/android/view/ITaskOrganizer.aidl b/core/java/android/view/ITaskOrganizer.aidl
new file mode 100644
index 0000000..e92aafe
--- /dev/null
+++ b/core/java/android/view/ITaskOrganizer.aidl
@@ -0,0 +1,38 @@
+/* //device/java/android/android/view/ITaskOrganizer.aidl
+**
+** Copyright 2019, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+import android.view.IWindowContainer;
+import android.view.SurfaceControl;
+import android.app.ActivityManager;
+
+/**
+ * Interface for ActivityTaskManager/WindowManager to delegate control of tasks.
+ * {@hide}
+ */
+oneway interface ITaskOrganizer {
+ void taskAppeared(in IWindowContainer container,
+ in ActivityManager.RunningTaskInfo taskInfo);
+ void taskVanished(in IWindowContainer container);
+
+ /**
+ * Called upon completion of
+ * ActivityTaskManagerService#applyTaskOrganizerTransaction
+ */
+ void transactionReady(int id, in SurfaceControl.Transaction t);
+}
\ No newline at end of file
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index c67ff6e..7986ceb 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -128,6 +128,19 @@
}
/**
+ * Called when a focus event is received.
+ *
+ * @param hasFocus if true, the window associated with this input channel has just received
+ * focus
+ * if false, the window associated with this input channel has just lost focus
+ * @param inTouchMode if true, the device is in touch mode
+ * if false, the device is not in touch mode
+ */
+ // Called from native code.
+ public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+ }
+
+ /**
* Called when a batched input event is pending.
*
* The batched input event will continue to accumulate additional movement
@@ -213,8 +226,13 @@
onBatchedInputEventPending();
}
- public static interface Factory {
- public InputEventReceiver createInputEventReceiver(
- InputChannel inputChannel, Looper looper);
+ /**
+ * Factory for InputEventReceiver
+ */
+ public interface Factory {
+ /**
+ * Create a new InputReceiver for a given inputChannel
+ */
+ InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper);
}
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index f7a6414..e2739c4 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -43,6 +43,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimationCallback.AnimationBounds;
import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -327,6 +328,14 @@
}
/**
+ * @see InsetsState#calculateVisibleInsets(Rect, Rect, int)
+ */
+ public Rect calculateVisibleInsets(Rect legacyVisibleInsets,
+ @SoftInputModeFlags int softInputMode) {
+ return mState.calculateVisibleInsets(mFrame, legacyVisibleInsets, softInputMode);
+ }
+
+ /**
* Called when the server has dispatched us a new set of inset controls.
*/
public void onControlsChanged(InsetsSourceControl[] activeControls) {
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 324d562..67ccfd6 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.Nullable;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Parcel;
@@ -23,6 +24,7 @@
import android.view.InsetsState.InternalInsetsType;
import java.io.PrintWriter;
+import java.util.Objects;
/**
* Represents the state of a single window generating insets for clients.
@@ -34,6 +36,7 @@
/** Frame of the source in screen coordinate space */
private final Rect mFrame;
+ private @Nullable Rect mVisibleFrame;
private boolean mVisible;
private final Rect mTmpFrame = new Rect();
@@ -54,6 +57,10 @@
mFrame.set(frame);
}
+ public void setVisibleFrame(@Nullable Rect visibleFrame) {
+ mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame;
+ }
+
public void setVisible(boolean visible) {
mVisible = visible;
}
@@ -66,6 +73,10 @@
return mFrame;
}
+ public @Nullable Rect getVisibleFrame() {
+ return mVisibleFrame;
+ }
+
public boolean isVisible() {
return mVisible;
}
@@ -79,10 +90,22 @@
* source.
*/
public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) {
+ return calculateInsets(relativeFrame, mFrame, ignoreVisibility);
+ }
+
+ /**
+ * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets.
+ */
+ public Insets calculateVisibleInsets(Rect relativeFrame) {
+ return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame,
+ false /* ignoreVisibility */);
+ }
+
+ private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) {
if (!ignoreVisibility && !mVisible) {
return Insets.NONE;
}
- if (!mTmpFrame.setIntersect(mFrame, relativeFrame)) {
+ if (!mTmpFrame.setIntersect(frame, relativeFrame)) {
return Insets.NONE;
}
@@ -110,6 +133,9 @@
pw.print(prefix);
pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType));
pw.print(" frame="); pw.print(mFrame.toShortString());
+ if (mVisibleFrame != null) {
+ pw.print(" visibleFrmae="); pw.print(mVisibleFrame.toShortString());
+ }
pw.print(" visible="); pw.print(mVisible);
pw.println();
}
@@ -123,6 +149,7 @@
if (mType != that.mType) return false;
if (mVisible != that.mVisible) return false;
+ if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
return mFrame.equals(that.mFrame);
}
@@ -137,6 +164,7 @@
public InsetsSource(Parcel in) {
mType = in.readInt();
mFrame = in.readParcelable(null /* loader */);
+ mVisibleFrame = in.readParcelable(null /* loader */);
mVisible = in.readBoolean();
}
@@ -149,6 +177,7 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeParcelable(mFrame, 0 /* flags*/);
+ dest.writeParcelable(mVisibleFrame, 0 /* flags */);
dest.writeBoolean(mVisible);
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index ae1e579..e33ca70 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -19,12 +19,14 @@
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.ViewRootImpl.sNewInsetsMode;
import static android.view.WindowInsets.Type.IME;
import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
import static android.view.WindowInsets.Type.SIZE;
import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.indexOf;
+import static android.view.WindowInsets.Type.isVisibleInsetsType;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
@@ -41,6 +43,7 @@
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -186,6 +189,32 @@
: systemBars());
}
+ public Rect calculateVisibleInsets(Rect frame, Rect legacyVisibleInsets,
+ @SoftInputModeFlags int softInputMode) {
+ if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
+ return legacyVisibleInsets;
+ }
+
+ Insets insets = Insets.NONE;
+ for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+ InsetsSource source = mSources.get(type);
+ if (source == null) {
+ continue;
+ }
+ if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) {
+ continue;
+ }
+
+ // Ignore everything that's not a system bar or IME.
+ int publicType = InsetsState.toPublicType(type);
+ if (!isVisibleInsetsType(publicType, softInputMode)) {
+ continue;
+ }
+ insets = Insets.max(source.calculateVisibleInsets(frame), insets);
+ }
+ return insets.toRect();
+ }
+
private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap,
@Nullable boolean[] typeVisibilityMap) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1782544..0de1a4f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -42,6 +42,7 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceControl.Transaction;
+import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.view.SurfaceCallbackHelper;
@@ -201,6 +202,9 @@
private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
private int mParentSurfaceGenerationId;
+ // The token of embedded windowless view hierarchy.
+ private IBinder mEmbeddedViewHierarchy;
+
public SurfaceView(Context context) {
this(context, null);
}
@@ -1531,4 +1535,27 @@
if (viewRoot == null) return;
viewRoot.setUseBLASTSyncTransaction();
}
+
+ /**
+ * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view
+ * hierarchy.
+ *
+ * @param token IBinder token.
+ * @hide
+ */
+ public void setEmbeddedViewHierarchy(IBinder token) {
+ mEmbeddedViewHierarchy = token;
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
+ if (mEmbeddedViewHierarchy == null) {
+ return;
+ }
+ // Add a leashed child when this SurfaceView embeds another view hierarchy. Getting this
+ // leashed child would return the root node in the embedded hierarchy
+ info.addChild(mEmbeddedViewHierarchy);
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ab89ef4..2ef944f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -826,6 +826,10 @@
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
+ if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ mWindowAttributes.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+ }
attrs = mWindowAttributes;
setTag();
@@ -1308,7 +1312,7 @@
mWindowAttributes.privateFlags |= compatibleWindowFlag;
if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
- mWindowAttributes.privateFlags =
+ mWindowAttributes.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
}
@@ -2103,6 +2107,12 @@
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
+ private void updateVisibleInsets() {
+ Rect visibleInsets = mInsetsController.calculateVisibleInsets(mPendingVisibleInsets,
+ mWindowAttributes.softInputMode);
+ mAttachInfo.mVisibleInsets.set(visibleInsets);
+ }
+
InsetsController getInsetsController() {
return mInsetsController;
}
@@ -2251,7 +2261,7 @@
insetsChanged = true;
}
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
- mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+ updateVisibleInsets();
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
@@ -2317,6 +2327,7 @@
if (mApplyInsetsRequested) {
mApplyInsetsRequested = false;
+ updateVisibleInsets();
dispatchApplyInsets(host);
if (mLayoutRequested) {
// Short-circuit catching a new layout request here, so
@@ -2501,7 +2512,7 @@
contentInsetsChanged = true;
}
if (visibleInsetsChanged) {
- mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+ updateVisibleInsets();
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
@@ -8033,6 +8044,11 @@
}
@Override
+ public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+ windowFocusChanged(hasFocus, inTouchMode);
+ }
+
+ @Override
public void dispose() {
unscheduleConsumeBatchedInput();
super.dispose();
diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java
index 607a870..c55caa3 100644
--- a/core/java/android/view/WindowContainerTransaction.java
+++ b/core/java/android/view/WindowContainerTransaction.java
@@ -62,6 +62,30 @@
return this;
}
+ /**
+ * Notify activies within the hiearchy of a container that they have entered picture-in-picture
+ * mode with the given bounds.
+ */
+ public WindowContainerTransaction scheduleFinishEnterPip(IWindowContainer container,
+ Rect bounds) {
+ Change chg = getOrCreateChange(container.asBinder());
+ chg.mSchedulePipCallback = true;
+ chg.mPinnedBounds = new Rect(bounds);
+ return this;
+ }
+
+ /**
+ * Sets whether a container or any of its children can be focusable. When {@code false}, no
+ * child can be focused; however, when {@code true}, it is still possible for children to be
+ * non-focusable due to WM policy.
+ */
+ public WindowContainerTransaction setFocusable(IWindowContainer container, boolean focusable) {
+ Change chg = getOrCreateChange(container.asBinder());
+ chg.mFocusable = focusable;
+ chg.mChangeMask |= Change.CHANGE_FOCUSABLE;
+ return this;
+ }
+
public Map<IBinder, Change> getChanges() {
return mChanges;
}
@@ -100,22 +124,48 @@
* @hide
*/
public static class Change implements Parcelable {
+ public static final int CHANGE_FOCUSABLE = 1;
+
private final Configuration mConfiguration = new Configuration();
+ private boolean mFocusable = true;
+ private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
+ private boolean mSchedulePipCallback = false;
+ private Rect mPinnedBounds = null;
+
public Change() {}
protected Change(Parcel in) {
mConfiguration.readFromParcel(in);
+ mFocusable = in.readBoolean();
+ mChangeMask = in.readInt();
mConfigSetMask = in.readInt();
mWindowSetMask = in.readInt();
+ mSchedulePipCallback = (in.readInt() != 0);
+ if (mSchedulePipCallback ) {
+ mPinnedBounds = new Rect();
+ mPinnedBounds.readFromParcel(in);
+ }
}
public Configuration getConfiguration() {
return mConfiguration;
}
+ /** Gets the requested focusable value */
+ public boolean getFocusable() {
+ if ((mChangeMask & CHANGE_FOCUSABLE) == 0) {
+ throw new RuntimeException("Focusable not set. check CHANGE_FOCUSABLE first");
+ }
+ return mFocusable;
+ }
+
+ public int getChangeMask() {
+ return mChangeMask;
+ }
+
@ActivityInfo.Config
public int getConfigSetMask() {
return mConfigSetMask;
@@ -126,6 +176,14 @@
return mWindowSetMask;
}
+ /**
+ * Returns the bounds to be used for scheduling the enter pip callback
+ * or null if no callback is to be scheduled.
+ */
+ public Rect getEnterPipBounds() {
+ return mPinnedBounds;
+ }
+
@Override
public String toString() {
final boolean changesBounds =
@@ -142,6 +200,9 @@
if (changesSss) {
sb.append("ssw:" + mConfiguration.smallestScreenWidthDp + ",");
}
+ if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
+ sb.append("focusable:" + mFocusable + ",");
+ }
sb.append("}");
return sb.toString();
}
@@ -149,8 +210,15 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
mConfiguration.writeToParcel(dest, flags);
+ dest.writeBoolean(mFocusable);
+ dest.writeInt(mChangeMask);
dest.writeInt(mConfigSetMask);
dest.writeInt(mWindowSetMask);
+
+ dest.writeInt(mSchedulePipCallback ? 1 : 0);
+ if (mSchedulePipCallback ) {
+ mPinnedBounds.writeToParcel(dest, flags);
+ }
}
@Override
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 9df131d..9291b56 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -29,6 +29,8 @@
import static android.view.WindowInsets.Type.all;
import static android.view.WindowInsets.Type.indexOf;
import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -40,6 +42,7 @@
import android.graphics.Rect;
import android.util.SparseArray;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethod;
@@ -1289,6 +1292,17 @@
public static @InsetsType int all() {
return 0xFFFFFFFF;
}
+
+ /**
+ * Checks whether the specified type is considered to be part of visible insets.
+ * @hide
+ */
+ public static boolean isVisibleInsetsType(int type,
+ @SoftInputModeFlags int softInputModeFlags) {
+ int softInputMode = softInputModeFlags & SOFT_INPUT_MASK_ADJUST;
+ return (type & Type.systemBars()) != 0
+ || (softInputMode != SOFT_INPUT_ADJUST_NOTHING && (type & Type.ime()) != 0);
+ }
}
/**
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 914ff18..b9f08ad 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -17,10 +17,14 @@
package android.view.accessibility;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -337,6 +341,48 @@
return emptyWindows;
}
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by accessibility id and given leash token instead of
+ * window id. This method is used to find the leashed node on the embedded view hierarchy.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param leashToken The token of the embedded hierarchy.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param bypassCache Whether to bypass the cache while looking for the node.
+ * @param prefetchFlags flags to guide prefetching.
+ * @param arguments Optional action arguments.
+ * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+ */
+ public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
+ int connectionId, @NonNull IBinder leashToken, long accessibilityNodeId,
+ boolean bypassCache, int prefetchFlags, Bundle arguments) {
+ if (leashToken == null) {
+ return null;
+ }
+ int windowId = -1;
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ windowId = connection.getWindowIdForLeashToken(leashToken);
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while calling remote getWindowIdForLeashToken", re);
+ }
+ if (windowId == -1) {
+ return null;
+ }
+ return findAccessibilityNodeInfoByAccessibilityId(connectionId, windowId,
+ accessibilityNodeId, bypassCache, prefetchFlags, arguments);
+ }
+
/**
* Finds an {@link AccessibilityNodeInfo} by accessibility id.
*
@@ -783,6 +829,31 @@
}
/**
+ * Takes the screenshot of the specified display and returns it by bitmap format.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @return The screenshot bitmap on success, null otherwise.
+ */
+ public Bitmap takeScreenshot(int connectionId, int displayId) {
+ Bitmap screenShot = null;
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ screenShot = connection.takeScreenshot(displayId);
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling remote takeScreenshot", re);
+ }
+ return screenShot;
+ }
+
+ /**
* Clears the result state.
*/
private void clearResultLocked() {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 2e5a4b5..3dfeffb 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1195,6 +1195,19 @@
@TestApi
@RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
public void performAccessibilityShortcut() {
+ performAccessibilityShortcut(null);
+ }
+
+ /**
+ * Perform the accessibility shortcut for the given target which is assigned to the shortcut.
+ *
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ public void performAccessibilityShortcut(@Nullable String targetName) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
@@ -1203,7 +1216,7 @@
}
}
try {
- service.performAccessibilityShortcut();
+ service.performAccessibilityShortcut(targetName);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
}
@@ -1270,7 +1283,22 @@
* @param displayId The logical display id.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
public void notifyAccessibilityButtonClicked(int displayId) {
+ notifyAccessibilityButtonClicked(displayId, null);
+ }
+
+ /**
+ * Perform the accessibility button for the given target which is assigned to the button.
+ *
+ * @param displayId displayId The logical display id.
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+ public void notifyAccessibilityButtonClicked(int displayId, @Nullable String targetName) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
@@ -1279,7 +1307,7 @@
}
}
try {
- service.notifyAccessibilityButtonClicked(displayId);
+ service.notifyAccessibilityButtonClicked(displayId, targetName);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 92aa7d5..184f3302 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -31,6 +31,7 @@
import android.graphics.Region;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.InputType;
@@ -110,6 +111,9 @@
public static final int ROOT_ITEM_ID = Integer.MAX_VALUE - 1;
/** @hide */
+ public static final int LEASHED_ITEM_ID = Integer.MAX_VALUE - 2;
+
+ /** @hide */
public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);
/** @hide */
@@ -117,6 +121,10 @@
AccessibilityNodeProvider.HOST_VIEW_ID);
/** @hide */
+ public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID,
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+
+ /** @hide */
public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
/** @hide */
@@ -788,6 +796,10 @@
private TouchDelegateInfo mTouchDelegateInfo;
+ private IBinder mLeashedChild;
+ private IBinder mLeashedParent;
+ private long mLeashedParentNodeId = UNDEFINED_NODE_ID;
+
/**
* Creates a new {@link AccessibilityNodeInfo}.
*/
@@ -1039,7 +1051,12 @@
return null;
}
final long childId = mChildNodeIds.get(index);
- AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ if (mLeashedChild != null && childId == LEASHED_NODE_ID) {
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild,
+ ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null);
+ }
+
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
childId, false, FLAG_PREFETCH_DESCENDANTS, null);
}
@@ -1062,6 +1079,43 @@
}
/**
+ * Adds a view root from leashed content as a child. This method is used to embedded another
+ * view hierarchy.
+ * <p>
+ * <strong>Note:</strong> Only one leashed child is permitted.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * Note that a view cannot be made its own child.
+ * </p>
+ *
+ * @param token The token to which a view root is added.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @hide
+ */
+ @TestApi
+ public void addChild(@NonNull IBinder token) {
+ enforceNotSealed();
+ if (token == null) {
+ return;
+ }
+ if (mChildNodeIds == null) {
+ mChildNodeIds = new LongArray();
+ }
+
+ mLeashedChild = token;
+ // Checking uniqueness.
+ // Since only one leashed child is permitted, skip adding ID if the ID already exists.
+ if (mChildNodeIds.indexOf(LEASHED_NODE_ID) >= 0) {
+ return;
+ }
+ mChildNodeIds.add(LEASHED_NODE_ID);
+ }
+
+ /**
* Unchecked version of {@link #addChild(View)} that does not verify
* uniqueness. For framework use only.
*
@@ -1090,6 +1144,38 @@
}
/**
+ * Removes a leashed child. If the child was not previously added to the node,
+ * calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param token The token of the leashed child
+ * @return true if the child was present
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @hide
+ */
+ public boolean removeChild(IBinder token) {
+ enforceNotSealed();
+ if (mChildNodeIds == null || mLeashedChild == null) {
+ return false;
+ }
+ if (!mLeashedChild.equals(token)) {
+ return false;
+ }
+ final int index = mChildNodeIds.indexOf(LEASHED_NODE_ID);
+ mLeashedChild = null;
+ if (index < 0) {
+ return false;
+ }
+ mChildNodeIds.remove(index);
+ return true;
+ }
+
+ /**
* Adds a virtual child which is a descendant of the given <code>root</code>.
* If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
* is added as a child.
@@ -1668,6 +1754,9 @@
*/
public AccessibilityNodeInfo getParent() {
enforceSealed();
+ if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
+ return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId);
+ }
return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId);
}
@@ -3257,6 +3346,40 @@
}
/**
+ * Sets the token and node id of the leashed parent.
+ *
+ * @param token The token.
+ * @param viewId The accessibility view id.
+ * @hide
+ */
+ @TestApi
+ public void setLeashedParent(@Nullable IBinder token, int viewId) {
+ enforceNotSealed();
+ mLeashedParent = token;
+ mLeashedParentNodeId = makeNodeId(viewId, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Gets the token of the leashed parent.
+ *
+ * @return The token.
+ * @hide
+ */
+ public @Nullable IBinder getLeashedParent() {
+ return mLeashedParent;
+ }
+
+ /**
+ * Gets the node id of the leashed parent.
+ *
+ * @return The accessibility node id.
+ * @hide
+ */
+ public long getLeashedParentNodeId() {
+ return mLeashedParentNodeId;
+ }
+
+ /**
* Sets if this instance is sealed.
*
* @param sealed Whether is sealed.
@@ -3559,6 +3682,18 @@
if (!Objects.equals(mTouchDelegateInfo, DEFAULT.mTouchDelegateInfo)) {
nonDefaultFields |= bitAt(fieldIndex);
}
+ fieldIndex++;
+ if (mLeashedChild != DEFAULT.mLeashedChild) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mLeashedParent != DEFAULT.mLeashedParent) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mLeashedParentNodeId != DEFAULT.mLeashedParentNodeId) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
int totalFields = fieldIndex;
parcel.writeLong(nonDefaultFields);
@@ -3685,6 +3820,16 @@
mTouchDelegateInfo.writeToParcel(parcel, flags);
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeStrongBinder(mLeashedChild);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeStrongBinder(mLeashedParent);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeLong(mLeashedParentNodeId);
+ }
+
if (DEBUG) {
fieldIndex--;
if (totalFields != fieldIndex) {
@@ -3768,6 +3913,10 @@
final TouchDelegateInfo otherInfo = other.mTouchDelegateInfo;
mTouchDelegateInfo = (otherInfo != null)
? new TouchDelegateInfo(otherInfo.mTargetMap, true) : null;
+
+ mLeashedChild = other.mLeashedChild;
+ mLeashedParent = other.mLeashedParent;
+ mLeashedParentNodeId = other.mLeashedParentNodeId;
}
private void initPoolingInfos(AccessibilityNodeInfo other) {
@@ -3921,6 +4070,16 @@
mTouchDelegateInfo = TouchDelegateInfo.CREATOR.createFromParcel(parcel);
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mLeashedChild = parcel.readStrongBinder();
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mLeashedParent = parcel.readStrongBinder();
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mLeashedParentNodeId = parcel.readLong();
+ }
+
mSealed = sealed;
}
@@ -4200,6 +4359,19 @@
| FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
}
+ private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
+ IBinder leashToken, long accessibilityId) {
+ if (!((leashToken != null)
+ && (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID)
+ && (connectionId != UNDEFINED_CONNECTION_ID))) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
+ leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
+ | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+ }
+
/** @hide */
public static String idToString(long accessibilityId) {
int accessibilityViewId = getAccessibilityViewId(accessibilityId);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 392db57..fcaaa2e 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -66,12 +66,12 @@
// Used by UiAutomation
IBinder getWindowToken(int windowId, int userId);
- void notifyAccessibilityButtonClicked(int displayId);
+ void notifyAccessibilityButtonClicked(int displayId, String targetName);
void notifyAccessibilityButtonVisibilityChanged(boolean available);
// Requires Manifest.permission.MANAGE_ACCESSIBILITY
- void performAccessibilityShortcut();
+ void performAccessibilityShortcut(String targetName);
// Requires Manifest.permission.MANAGE_ACCESSIBILITY
List<String> getAccessibilityShortcutTargets(int shortcutType);
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 386c9cb..860ce90 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -17,6 +17,8 @@
package android.view.inputmethod;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.os.Parcelable;
import android.view.inline.InlinePresentationSpec;
@@ -49,6 +51,21 @@
*/
private final @NonNull List<InlinePresentationSpec> mPresentationSpecs;
+ /**
+ * The package name of the app that requests for the inline suggestions and will host the
+ * embedded suggestion views. The app does not have to set the value for the field because
+ * it'll be set by the system for safety reasons.
+ */
+ private @NonNull String mHostPackageName;
+
+ /**
+ * @hide
+ * @see {@link #mHostPackageName}.
+ */
+ public void setHostPackageName(@NonNull String hostPackageName) {
+ mHostPackageName = hostPackageName;
+ }
+
private void onConstructed() {
Preconditions.checkState(mMaxSuggestionCount >= mPresentationSpecs.size());
}
@@ -57,9 +74,15 @@
return SUGGESTION_COUNT_UNLIMITED;
}
+ private static String defaultHostPackageName() {
+ return ActivityThread.currentPackageName();
+ }
+
/** @hide */
abstract static class BaseBuilder {
abstract Builder setPresentationSpecs(@NonNull List<InlinePresentationSpec> value);
+
+ abstract Builder setHostPackageName(@Nullable String value);
}
@@ -80,11 +103,15 @@
@DataClass.Generated.Member
/* package-private */ InlineSuggestionsRequest(
int maxSuggestionCount,
- @NonNull List<InlinePresentationSpec> presentationSpecs) {
+ @NonNull List<InlinePresentationSpec> presentationSpecs,
+ @NonNull String hostPackageName) {
this.mMaxSuggestionCount = maxSuggestionCount;
this.mPresentationSpecs = presentationSpecs;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPresentationSpecs);
+ this.mHostPackageName = hostPackageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostPackageName);
onConstructed();
}
@@ -108,6 +135,16 @@
return mPresentationSpecs;
}
+ /**
+ * The package name of the app that requests for the inline suggestions and will host the
+ * embedded suggestion views. The app does not have to set the value for the field because
+ * it'll be set by the system for safety reasons.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getHostPackageName() {
+ return mHostPackageName;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -116,13 +153,14 @@
return "InlineSuggestionsRequest { " +
"maxSuggestionCount = " + mMaxSuggestionCount + ", " +
- "presentationSpecs = " + mPresentationSpecs +
+ "presentationSpecs = " + mPresentationSpecs + ", " +
+ "hostPackageName = " + mHostPackageName +
" }";
}
@Override
@DataClass.Generated.Member
- public boolean equals(@android.annotation.Nullable Object o) {
+ public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(InlineSuggestionsRequest other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
@@ -134,7 +172,8 @@
//noinspection PointlessBooleanExpression
return true
&& mMaxSuggestionCount == that.mMaxSuggestionCount
- && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs);
+ && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs)
+ && java.util.Objects.equals(mHostPackageName, that.mHostPackageName);
}
@Override
@@ -146,6 +185,7 @@
int _hash = 1;
_hash = 31 * _hash + mMaxSuggestionCount;
_hash = 31 * _hash + java.util.Objects.hashCode(mPresentationSpecs);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mHostPackageName);
return _hash;
}
@@ -157,6 +197,7 @@
dest.writeInt(mMaxSuggestionCount);
dest.writeParcelableList(mPresentationSpecs, flags);
+ dest.writeString(mHostPackageName);
}
@Override
@@ -173,11 +214,15 @@
int maxSuggestionCount = in.readInt();
List<InlinePresentationSpec> presentationSpecs = new ArrayList<>();
in.readParcelableList(presentationSpecs, InlinePresentationSpec.class.getClassLoader());
+ String hostPackageName = in.readString();
this.mMaxSuggestionCount = maxSuggestionCount;
this.mPresentationSpecs = presentationSpecs;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPresentationSpecs);
+ this.mHostPackageName = hostPackageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostPackageName);
onConstructed();
}
@@ -205,6 +250,7 @@
private int mMaxSuggestionCount;
private @NonNull List<InlinePresentationSpec> mPresentationSpecs;
+ private @NonNull String mHostPackageName;
private long mBuilderFieldsSet = 0L;
@@ -260,22 +306,40 @@
return this;
}
+ /**
+ * The package name of the app that requests for the inline suggestions and will host the
+ * embedded suggestion views. The app does not have to set the value for the field because
+ * it'll be set by the system for safety reasons.
+ */
+ @DataClass.Generated.Member
+ @Override
+ @NonNull Builder setHostPackageName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mHostPackageName = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull InlineSuggestionsRequest build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x4; // Mark builder used
+ mBuilderFieldsSet |= 0x8; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mMaxSuggestionCount = defaultMaxSuggestionCount();
}
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mHostPackageName = defaultHostPackageName();
+ }
InlineSuggestionsRequest o = new InlineSuggestionsRequest(
mMaxSuggestionCount,
- mPresentationSpecs);
+ mPresentationSpecs,
+ mHostPackageName);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x4) != 0) {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -283,10 +347,10 @@
}
@DataClass.Generated(
- time = 1576637222199L,
+ time = 1578948035951L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
- inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\npublic void setHostPackageName(java.lang.String)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java
new file mode 100644
index 0000000..fe8bbb8
--- /dev/null
+++ b/core/java/android/webkit/PacProcessor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+
+/**
+ * Class to evaluate PAC scripts.
+ * @hide
+ */
+
+@SystemApi
+public interface PacProcessor {
+
+ /**
+ * Returns the default PacProcessor instance.
+ *
+ * @return the default PacProcessor instance.
+ */
+ @NonNull
+ static PacProcessor getInstance() {
+ return WebViewFactory.getProvider().getPacProcessor();
+ }
+
+ /**
+ * Set PAC script to use.
+ *
+ * @param script PAC script.
+ * @return true if PAC script is successfully set.
+ */
+ boolean setProxyScript(@NonNull String script);
+
+ /**
+ * Gets a list of proxy servers to use.
+ * @param url The URL being accessed.
+ * @return a PAC-style semicolon-separated list of valid proxy servers.
+ * For example: "PROXY xxx.xxx.xxx.xxx:xx; SOCKS yyy.yyy.yyy:yy".
+ */
+ @Nullable
+ String makeProxyRequest(@NonNull String url);
+}
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 6a1ed39..f7c3ec0 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -175,6 +175,15 @@
WebViewDatabase getWebViewDatabase(Context context);
/**
+ * Gets the singleton PacProcessor instance.
+ * @return the PacProcessor instance
+ */
+ @NonNull
+ default PacProcessor getPacProcessor() {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ /**
* Gets the classloader used to load internal WebView implementation classes. This interface
* should only be used by the WebView Support Library.
*/
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 20af76b..b891af5 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -152,7 +152,7 @@
// Specifies whether to allow starting a cursor drag by dragging anywhere over the text.
@VisibleForTesting
- public static boolean FLAG_ENABLE_CURSOR_DRAG = false;
+ public static boolean FLAG_ENABLE_CURSOR_DRAG = true;
// Specifies whether to use the magnifier when pressing the insertion or selection handles.
private static final boolean FLAG_USE_MAGNIFIER = true;
@@ -5737,14 +5737,14 @@
private boolean mIsDraggingCursor;
public void onTouchEvent(MotionEvent event) {
- if (getSelectionController().isCursorBeingModified()) {
+ if (hasSelectionController() && getSelectionController().isCursorBeingModified()) {
return;
}
switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mIsDraggingCursor = false;
- break;
case MotionEvent.ACTION_MOVE:
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ break;
+ }
if (mIsDraggingCursor) {
performCursorDrag(event);
} else if (FLAG_ENABLE_CURSOR_DRAG
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index 6277afe..b13ca42 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -173,6 +173,13 @@
mIsDragCloseToVertical = (4 * deltaXSquared) <= distanceSquared;
}
}
+ } else if (action == MotionEvent.ACTION_CANCEL) {
+ mLastDownMillis = 0;
+ mLastUpMillis = 0;
+ mMultiTapStatus = MultiTapStatus.NONE;
+ mMultiTapInSameArea = false;
+ mMovedEnoughForDrag = false;
+ mIsDragCloseToVertical = false;
}
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index d86766e..01a0e9b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -565,7 +565,8 @@
}
private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
- if (icon != null && icon.getType() == Icon.TYPE_URI) {
+ if (icon != null && (icon.getType() == Icon.TYPE_URI
+ || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
visitor.accept(icon.getUri());
}
}
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 5731e50..8565493 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -422,7 +422,7 @@
/**
* Update the displayed time if necessary and invalidate the view.
*/
- public void refresh() {
+ public void refreshTime() {
onTimeChanged();
invalidate();
}
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 9bdb4c1..d119b2e 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -16,6 +16,8 @@
package android.widget;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,8 +44,12 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import com.android.internal.annotations.GuardedBy;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* A toast is a view containing a quick little message for the user. The toast class
@@ -262,6 +268,29 @@
}
/**
+ * Adds a callback to be notified when the toast is shown or hidden.
+ *
+ * Note that if the toast is blocked for some reason you won't get a call back.
+ *
+ * @see #removeCallback(Callback)
+ */
+ public void addCallback(@NonNull Callback callback) {
+ checkNotNull(callback);
+ synchronized (mTN.mCallbacks) {
+ mTN.mCallbacks.add(callback);
+ }
+ }
+
+ /**
+ * Removes a callback previously added with {@link #addCallback(Callback)}.
+ */
+ public void removeCallback(@NonNull Callback callback) {
+ synchronized (mTN.mCallbacks) {
+ mTN.mCallbacks.remove(callback);
+ }
+ }
+
+ /**
* Gets the LayoutParams for the Toast window.
* @hide
*/
@@ -389,6 +418,9 @@
String mPackageName;
+ @GuardedBy("mCallbacks")
+ private final List<Callback> mCallbacks = new ArrayList<>();
+
static final long SHORT_DURATION_TIMEOUT = 4000;
static final long LONG_DURATION_TIMEOUT = 7000;
@@ -449,6 +481,12 @@
};
}
+ private List<Callback> getCallbacks() {
+ synchronized (mCallbacks) {
+ return new ArrayList<>(mCallbacks);
+ }
+ }
+
/**
* schedule handleShow into the right thread
*/
@@ -522,6 +560,9 @@
try {
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
+ for (Callback callback : getCallbacks()) {
+ callback.onToastShown();
+ }
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
@@ -564,8 +605,30 @@
} catch (RemoteException e) {
}
+ for (Callback callback : getCallbacks()) {
+ callback.onToastHidden();
+ }
mView = null;
}
}
}
+
+ /**
+ * Callback object to be called when the toast is shown or hidden.
+ *
+ * Callback methods will be called on the looper thread provided on construction.
+ *
+ * @see #addCallback(Callback)
+ */
+ public abstract static class Callback {
+ /**
+ * Called when the toast is displayed on the screen.
+ */
+ public void onToastShown() {}
+
+ /**
+ * Called when the toast is hidden.
+ */
+ public void onToastHidden() {}
+ }
}
diff --git a/core/java/com/android/ims/internal/uce/common/CapInfo.java b/core/java/com/android/ims/internal/uce/common/CapInfo.java
index c45a3a4..2bb3f1f 100644
--- a/core/java/com/android/ims/internal/uce/common/CapInfo.java
+++ b/core/java/com/android/ims/internal/uce/common/CapInfo.java
@@ -64,6 +64,20 @@
private boolean mRcsIpVideoCallSupported = false;
/** RCS IP Video call support . */
private boolean mRcsIpVideoOnlyCallSupported = false;
+ /** IP Geo location Push using SMS. */
+ private boolean mGeoSmsSupported = false;
+ /** RCS call composer support. */
+ private boolean mCallComposerSupported = false;
+ /** RCS post-call support. */
+ private boolean mPostCallSupported = false;
+ /** Shared map support. */
+ private boolean mSharedMapSupported = false;
+ /** Shared Sketch supported. */
+ private boolean mSharedSketchSupported = false;
+ /** Chatbot communication support. */
+ private boolean mChatbotSupported = false;
+ /** Chatbot role support. */
+ private boolean mChatbotRoleSupported = false;
/** List of supported extensions. */
private String[] mExts = new String[10];
/** Time used to compute when to query again. */
@@ -386,6 +400,104 @@
this.mRcsIpVideoOnlyCallSupported = rcsIpVideoOnlyCallSupported;
}
+ /**
+ * Checks whether Geo Push via SMS is supported.
+ */
+ public boolean isGeoSmsSupported() {
+ return mGeoSmsSupported;
+ }
+
+ /**
+ * Sets Geolocation Push via SMS as supported or not supported.
+ */
+ public void setGeoSmsSupported(boolean geoSmsSupported) {
+ this.mGeoSmsSupported = geoSmsSupported;
+ }
+
+ /**
+ * Checks whether RCS call composer is supported.
+ */
+ public boolean isCallComposerSupported() {
+ return mCallComposerSupported;
+ }
+
+ /**
+ * Sets call composer as supported or not supported.
+ */
+ public void setCallComposerSupported(boolean callComposerSupported) {
+ this.mCallComposerSupported = callComposerSupported;
+ }
+
+ /**
+ * Checks whether post call is supported.
+ */
+ public boolean isPostCallSupported(){
+ return mPostCallSupported;
+ }
+
+ /**
+ * Sets post call as supported or not supported.
+ */
+ public void setPostCallSupported(boolean postCallSupported) {
+ this.mPostCallSupported = postCallSupported;
+ }
+
+ /**
+ * Checks whether shared map is supported.
+ */
+ public boolean isSharedMapSupported() {
+ return mSharedMapSupported;
+ }
+
+ /**
+ * Sets shared map as supported or not supported.
+ */
+ public void setSharedMapSupported(boolean sharedMapSupported) {
+ this.mSharedMapSupported = sharedMapSupported;
+ }
+
+ /**
+ * Checks whether shared sketch is supported.
+ */
+ public boolean isSharedSketchSupported() {
+ return mSharedSketchSupported;
+ }
+
+ /**
+ * Sets shared sketch as supported or not supported.
+ */
+ public void setSharedSketchSupported(boolean sharedSketchSupported) {
+ this.mSharedSketchSupported = sharedSketchSupported;
+ }
+
+ /**
+ * Checks whether chatbot communication is supported.
+ */
+ public boolean isChatbotSupported() {
+ return mChatbotSupported;
+ }
+
+ /**
+ * Sets chatbot communication as supported or not supported.
+ */
+ public void setChatbotSupported(boolean chatbotSupported) {
+ this.mChatbotSupported = chatbotSupported;
+ }
+
+ /**
+ * Checks whether chatbot role is supported.
+ */
+ public boolean isChatbotRoleSupported() {
+ return mChatbotRoleSupported;
+ }
+
+ /**
+ * Sets chatbot role as supported or not supported.
+ */
+ public void setChatbotRoleSupported(boolean chatbotRoleSupported) {
+ this.mChatbotRoleSupported = chatbotRoleSupported;
+ }
+
/** Gets the list of supported extensions. */
public String[] getExts() {
return mExts;
@@ -434,6 +546,13 @@
dest.writeInt(mGeoPushSupported ? 1 : 0);
dest.writeInt(mSmSupported ? 1 : 0);
dest.writeInt(mFullSnFGroupChatSupported ? 1 : 0);
+ dest.writeInt(mGeoSmsSupported ? 1 : 0);
+ dest.writeInt(mCallComposerSupported ? 1 : 0);
+ dest.writeInt(mPostCallSupported ? 1 : 0);
+ dest.writeInt(mSharedMapSupported ? 1 : 0);
+ dest.writeInt(mSharedSketchSupported ? 1 : 0);
+ dest.writeInt(mChatbotSupported ? 1 : 0);
+ dest.writeInt(mChatbotRoleSupported ? 1 : 0);
dest.writeInt(mRcsIpVoiceCallSupported ? 1 : 0);
dest.writeInt(mRcsIpVideoCallSupported ? 1 : 0);
@@ -476,6 +595,13 @@
mGeoPushSupported = (source.readInt() == 0) ? false : true;
mSmSupported = (source.readInt() == 0) ? false : true;
mFullSnFGroupChatSupported = (source.readInt() == 0) ? false : true;
+ mGeoSmsSupported = (source.readInt() == 0) ? false : true;
+ mCallComposerSupported = (source.readInt() == 0) ? false : true;
+ mPostCallSupported = (source.readInt() == 0) ? false : true;
+ mSharedMapSupported = (source.readInt() == 0) ? false : true;
+ mSharedSketchSupported = (source.readInt() == 0) ? false : true;
+ mChatbotSupported = (source.readInt() == 0) ? false : true;
+ mChatbotRoleSupported = (source.readInt() == 0) ? false : true;
mRcsIpVoiceCallSupported = (source.readInt() == 0) ? false : true;
mRcsIpVideoCallSupported = (source.readInt() == 0) ? false : true;
diff --git a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
index a50a22f..fdff86f 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
@@ -47,6 +47,10 @@
public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_IWLAN = 8;
/** Trigger is unknown. */
public static final int UCE_PRES_PUBLISH_TRIGGER_UNKNOWN = 9;
+ /** Move to 5G NR with VoPS disabled. */
+ public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10;
+ /** Move to 5G NR with VoPS enabled. */
+ public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11;
@@ -113,4 +117,4 @@
public void readFromParcel(Parcel source) {
mPublishTriggerType = source.readInt();
}
-}
\ No newline at end of file
+}
diff --git a/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java b/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
index 4b0b098..9aee879f 100644
--- a/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
+++ b/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
@@ -18,16 +18,9 @@
import android.content.Context;
import android.content.Intent;
-
-import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Message;
-import android.os.ServiceManager;
import android.os.RemoteException;
-
-import java.util.HashMap;
-import android.util.Log;
+import android.os.ServiceManager;
/**
* ImsUceManager Declaration
@@ -49,55 +42,25 @@
private IUceService mUceService = null;
private UceServiceDeathRecipient mDeathReceipient = new UceServiceDeathRecipient();
private Context mContext;
- private int mPhoneId;
- /**
- * Stores the UceManager instaces of Clients identified by
- * phoneId
- * @hide
- */
- private static HashMap<Integer, ImsUceManager> sUceManagerInstances =
- new HashMap<Integer, ImsUceManager>();
+ private static final Object sLock = new Object();
+ private static ImsUceManager sUceManager;
public static final String ACTION_UCE_SERVICE_UP =
"com.android.ims.internal.uce.UCE_SERVICE_UP";
public static final String ACTION_UCE_SERVICE_DOWN =
"com.android.ims.internal.uce.UCE_SERVICE_DOWN";
- /** Uce Service status received in IUceListener.setStatus()
- * callback
- * @hide
- */
- public static final int UCE_SERVICE_STATUS_FAILURE = 0;
- /** indicate UI to call Presence/Options API. */
- public static final int UCE_SERVICE_STATUS_ON = 1;
- /** Indicate UI destroy Presence/Options */
- public static final int UCE_SERVICE_STATUS_CLOSED = 2;
- /** Service up and trying to register for network events */
- public static final int UCE_SERVICE_STATUS_READY = 3;
-
- /**
- * Part of the ACTION_UCE_SERVICE_UP or _DOWN intents. A long
- * value; the phone ID corresponding to the IMS service coming up or down.
- * Internal use only.
- * @hide
- */
- public static final String EXTRA_PHONE_ID = "android:phone_id";
-
/**
* Gets the instance of UCE Manager
* @hide
*/
- public static ImsUceManager getInstance(Context context, int phoneId) {
- //if (DBG) Log.d (LOG_TAG, "GetInstance Called");
- synchronized (sUceManagerInstances) {
- if (sUceManagerInstances.containsKey(phoneId)) {
- return sUceManagerInstances.get(phoneId);
- } else {
- ImsUceManager uceMgr = new ImsUceManager(context, phoneId);
- sUceManagerInstances.put(phoneId, uceMgr);
- return uceMgr;
+ public static ImsUceManager getInstance(Context context) {
+ synchronized (sLock) {
+ if (sUceManager == null && context != null) {
+ sUceManager = new ImsUceManager(context);
}
+ return sUceManager;
}
}
@@ -105,10 +68,9 @@
* Constructor
* @hide
*/
- private ImsUceManager(Context context, int phoneId) {
+ private ImsUceManager(Context context) {
//if (DBG) Log.d (LOG_TAG, "Constructor");
mContext = context;
- mPhoneId = phoneId;
createUceService(true);
}
@@ -129,7 +91,7 @@
* Gets the UCE service name
* @hide
*/
- private String getUceServiceName(int phoneId) {
+ private String getUceServiceName() {
return UCE_SERVICE;
}
@@ -143,14 +105,14 @@
public void createUceService(boolean checkService) {
//if (DBG) Log.d (LOG_TAG, "CreateUceService Called");
if (checkService) {
- IBinder binder = ServiceManager.checkService(getUceServiceName(mPhoneId));
+ IBinder binder = ServiceManager.checkService(getUceServiceName());
if (binder == null) {
//if (DBG)Log.d (LOG_TAG, "Unable to find IBinder");
return;
}
}
- IBinder b = ServiceManager.getService(getUceServiceName(mPhoneId));
+ IBinder b = ServiceManager.getService(getUceServiceName());
if (b != null) {
try {
@@ -174,12 +136,10 @@
private class UceServiceDeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
- //if (DBG) Log.d (LOG_TAG, "found IBinder/IUceService Service Died");
mUceService = null;
if (mContext != null) {
Intent intent = new Intent(ACTION_UCE_SERVICE_DOWN);
- intent.putExtra(EXTRA_PHONE_ID, mPhoneId);
mContext.sendBroadcast(new Intent(intent));
}
}
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index de204ba..a9f0175 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -19,6 +19,15 @@
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static android.view.accessibility.AccessibilityManager.ShortcutType;
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.COMPONENT_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.FRAGMENT_TYPE;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.ICON_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.LABEL_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.SETTINGS_KEY;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -63,10 +72,6 @@
* Activity used to display and persist a service or feature target for the Accessibility button.
*/
public class AccessibilityButtonChooserActivity extends Activity {
-
- private static final String MAGNIFICATION_COMPONENT_ID =
- "com.android.server.accessibility.MagnificationController";
-
private static final char SERVICES_SEPARATOR = ':';
private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
@@ -76,7 +81,7 @@
ACCESSIBILITY_SHORTCUT_KEY);
private int mShortcutType;
- private List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
+ private final List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
private AlertDialog mAlertDialog;
private TargetAdapter mTargetAdapter;
@@ -99,7 +104,7 @@
UserShortcutType.TRIPLETAP,
})
/** Denotes the user shortcut type. */
- public @interface UserShortcutType {
+ private @interface UserShortcutType {
int DEFAULT = 0;
int SOFTWARE = 1; // 1 << 0
int HARDWARE = 2; // 1 << 1
@@ -122,7 +127,7 @@
AccessibilityServiceFragmentType.INTUITIVE,
AccessibilityServiceFragmentType.BOUNCE,
})
- public @interface AccessibilityServiceFragmentType {
+ private @interface AccessibilityServiceFragmentType {
int LEGACY = 0;
int INVISIBLE = 1;
int INTUITIVE = 2;
@@ -140,11 +145,61 @@
ShortcutMenuMode.LAUNCH,
ShortcutMenuMode.EDIT,
})
- public @interface ShortcutMenuMode {
+ private @interface ShortcutMenuMode {
int LAUNCH = 0;
int EDIT = 1;
}
+ /**
+ * Annotation for align the element index of white listing feature
+ * {@code WHITE_LISTING_FEATURES}.
+ *
+ * {@code COMPONENT_ID} is to get the service component name.
+ * {@code LABEL_ID} is to get the service label text.
+ * {@code ICON_ID} is to get the service icon.
+ * {@code FRAGMENT_TYPE} is to get the service fragment type.
+ * {@code SETTINGS_KEY} is to get the service settings key.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ WhiteListingFeatureElementIndex.COMPONENT_ID,
+ WhiteListingFeatureElementIndex.LABEL_ID,
+ WhiteListingFeatureElementIndex.ICON_ID,
+ WhiteListingFeatureElementIndex.FRAGMENT_TYPE,
+ WhiteListingFeatureElementIndex.SETTINGS_KEY,
+ })
+ @interface WhiteListingFeatureElementIndex {
+ int COMPONENT_ID = 0;
+ int LABEL_ID = 1;
+ int ICON_ID = 2;
+ int FRAGMENT_TYPE = 3;
+ int SETTINGS_KEY = 4;
+ }
+
+ private static final String[][] WHITE_LISTING_FEATURES = {
+ {
+ COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
+ String.valueOf(R.string.color_inversion_feature_name),
+ String.valueOf(R.drawable.ic_accessibility_color_inversion),
+ String.valueOf(AccessibilityServiceFragmentType.INTUITIVE),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+ },
+ {
+ DALTONIZER_COMPONENT_NAME.flattenToString(),
+ String.valueOf(R.string.color_correction_feature_name),
+ String.valueOf(R.drawable.ic_accessibility_color_correction),
+ String.valueOf(AccessibilityServiceFragmentType.INTUITIVE),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+ },
+ {
+ MAGNIFICATION_CONTROLLER_NAME,
+ String.valueOf(R.string.accessibility_magnification_chooser_text),
+ String.valueOf(R.drawable.ic_accessibility_magnification),
+ String.valueOf(AccessibilityServiceFragmentType.INVISIBLE),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
+ },
+ };
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -199,6 +254,20 @@
private static List<AccessibilityButtonTarget> getServiceTargets(@NonNull Context context,
@ShortcutType int shortcutType) {
+ final List<AccessibilityButtonTarget> targets = new ArrayList<>();
+ targets.addAll(getAccessibilityServiceTargets(context));
+ targets.addAll(getWhiteListingServiceTargets(context));
+
+ final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
+ targets.removeIf(target -> !requiredTargets.contains(target.getId()));
+
+ return targets;
+ }
+
+ private static List<AccessibilityButtonTarget> getAccessibilityServiceTargets(
+ @NonNull Context context) {
final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityServiceInfo> installedServices =
@@ -209,29 +278,74 @@
final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size());
for (AccessibilityServiceInfo info : installedServices) {
- if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
- targets.add(new AccessibilityButtonTarget(context, info));
- }
- }
-
- final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
- targets.removeIf(target -> !requiredTargets.contains(target.getId()));
-
- // TODO(b/146815874): Will replace it with white list services.
- if (Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) {
- final AccessibilityButtonTarget magnificationTarget = new AccessibilityButtonTarget(
- context,
- MAGNIFICATION_COMPONENT_ID,
- R.string.accessibility_magnification_chooser_text,
- R.drawable.ic_accessibility_magnification,
- AccessibilityServiceFragmentType.INTUITIVE);
- targets.add(magnificationTarget);
+ targets.add(new AccessibilityButtonTarget(context, info));
}
return targets;
}
+ private static List<AccessibilityButtonTarget> getWhiteListingServiceTargets(
+ @NonNull Context context) {
+ final List<AccessibilityButtonTarget> targets = new ArrayList<>();
+
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ final AccessibilityButtonTarget target = new AccessibilityButtonTarget(
+ context,
+ WHITE_LISTING_FEATURES[i][COMPONENT_ID],
+ Integer.parseInt(WHITE_LISTING_FEATURES[i][LABEL_ID]),
+ Integer.parseInt(WHITE_LISTING_FEATURES[i][ICON_ID]),
+ Integer.parseInt(WHITE_LISTING_FEATURES[i][FRAGMENT_TYPE]));
+ targets.add(target);
+ }
+
+ return targets;
+ }
+
+ private static boolean isWhiteListingServiceEnabled(@NonNull Context context,
+ AccessibilityButtonTarget target) {
+
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(target.getId())) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ WHITE_LISTING_FEATURES[i][SETTINGS_KEY],
+ /* settingsValueOff */ 0) == /* settingsValueOn */ 1;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isWhiteListingService(String componentId) {
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void setWhiteListingServiceEnabled(String componentId, int settingsValue) {
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
+ Settings.Secure.putInt(getContentResolver(),
+ WHITE_LISTING_FEATURES[i][SETTINGS_KEY], settingsValue);
+ return;
+ }
+ }
+ }
+
+ private void disableService(ComponentName componentName) {
+ final String componentId = componentName.flattenToString();
+
+ if (isWhiteListingService(componentId)) {
+ setWhiteListingServiceEnabled(componentName.flattenToString(),
+ /* settingsValueOff */ 0);
+ } else {
+ setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+ }
+ }
+
private static class ViewHolder {
ImageView mIconView;
TextView mLabelView;
@@ -350,11 +464,14 @@
private void updateIntuitiveActionItemVisibility(@NonNull Context context,
@NonNull ViewHolder holder, AccessibilityButtonTarget target) {
final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
+ final boolean isServiceEnabled = isWhiteListingService(target.getId())
+ ? isWhiteListingServiceEnabled(context, target)
+ : isAccessibilityServiceEnabled(context, target);
holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
- holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled(context, target));
+ holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled);
holder.mItemContainer.setVisibility(View.VISIBLE);
}
@@ -411,7 +528,7 @@
}
}
- private static boolean isServiceEnabled(@NonNull Context context,
+ private static boolean isAccessibilityServiceEnabled(@NonNull Context context,
AccessibilityButtonTarget target) {
final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
@@ -429,13 +546,65 @@
}
private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
- Settings.Secure.putString(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
- mTargets.get(position).getId());
- // TODO(b/146969684): notify accessibility button clicked.
+ final AccessibilityButtonTarget target = mTargets.get(position);
+ switch (target.getFragmentType()) {
+ case AccessibilityServiceFragmentType.LEGACY:
+ onLegacyTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.INVISIBLE:
+ onInvisibleTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.INTUITIVE:
+ onIntuitiveTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.BOUNCE:
+ // Do nothing
+ break;
+ default:
+ throw new IllegalStateException("Unexpected fragment type");
+ }
+
mAlertDialog.dismiss();
}
+ private void onLegacyTargetSelected(AccessibilityButtonTarget target) {
+ if (mShortcutType == ACCESSIBILITY_BUTTON) {
+ final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+ } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ switchServiceState(target);
+ } else {
+ throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
+ }
+ }
+
+ private void onInvisibleTargetSelected(AccessibilityButtonTarget target) {
+ final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+ }
+
+ private void onIntuitiveTargetSelected(AccessibilityButtonTarget target) {
+ switchServiceState(target);
+ }
+
+ private void switchServiceState(AccessibilityButtonTarget target) {
+ final ComponentName componentName =
+ ComponentName.unflattenFromString(target.getId());
+ final String componentId = componentName.flattenToString();
+
+ if (isWhiteListingService(componentId)) {
+ setWhiteListingServiceEnabled(componentId,
+ isWhiteListingServiceEnabled(this, target)
+ ? /* settingsValueOff */ 0
+ : /* settingsValueOn */ 1);
+ } else {
+ setAccessibilityServiceState(this, componentName,
+ /* enabled= */!isAccessibilityServiceEnabled(this, target));
+ }
+ }
+
private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) {
final AccessibilityButtonTarget target = mTargets.get(position);
final ComponentName targetComponentName =
@@ -470,14 +639,14 @@
if (!hasValueInSettings(this,
ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) {
- setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+ disableService(componentName);
}
} else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
if (!hasValueInSettings(this,
ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) {
- setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+ disableService(componentName);
}
} else {
throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
diff --git a/core/java/com/android/internal/app/BlockedAppActivity.java b/core/java/com/android/internal/app/BlockedAppActivity.java
new file mode 100644
index 0000000..fbdbbfb
--- /dev/null
+++ b/core/java/com/android/internal/app/BlockedAppActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+/**
+ * A dialog shown to the user when they try to launch an app that is not allowed in lock task
+ * mode. The intent to start this activity must be created with the static factory method provided
+ * below.
+ */
+public class BlockedAppActivity extends AlertActivity {
+
+ private static final String TAG = "BlockedAppActivity";
+ private static final String PACKAGE_NAME = "com.android.internal.app";
+ private static final String EXTRA_BLOCKED_PACKAGE = PACKAGE_NAME + ".extra.BLOCKED_PACKAGE";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, /* defaultValue= */ -1);
+ if (userId < 0) {
+ Slog.wtf(TAG, "Invalid user: " + userId);
+ finish();
+ return;
+ }
+
+ String packageName = intent.getStringExtra(EXTRA_BLOCKED_PACKAGE);
+ if (TextUtils.isEmpty(packageName)) {
+ Slog.wtf(TAG, "Invalid package: " + packageName);
+ finish();
+ return;
+ }
+
+ CharSequence appLabel = getAppLabel(userId, packageName);
+
+ mAlertParams.mTitle = getString(R.string.app_blocked_title);
+ mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
+ mAlertParams.mPositiveButtonText = getString(android.R.string.ok);
+ setupAlert();
+ }
+
+ private CharSequence getAppLabel(int userId, String packageName) {
+ PackageManager pm = getPackageManager();
+ try {
+ ApplicationInfo aInfo =
+ pm.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId);
+ return aInfo.loadLabel(pm);
+ } catch (PackageManager.NameNotFoundException ne) {
+ Slog.e(TAG, "Package " + packageName + " not found", ne);
+ }
+ return packageName;
+ }
+
+
+ /** Creates an intent that launches {@link BlockedAppActivity}. */
+ public static Intent createIntent(int userId, String packageName) {
+ return new Intent()
+ .setClassName("android", BlockedAppActivity.class.getName())
+ .putExtra(Intent.EXTRA_USER_ID, userId)
+ .putExtra(EXTRA_BLOCKED_PACKAGE, packageName);
+ }
+}
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index d94294f..74bfb96 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -61,10 +61,6 @@
int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam,
int value);
- /**
- * @throws UnsupportedOperationException if hal or model do not support this API.
- * @throws IllegalArgumentException if invalid model handle or parameter is passed.
- */
int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam);
@nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId,
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index f462f5d..be2d1d6 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -26,6 +26,7 @@
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.IVoiceInteractionSessionListener;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
import android.hardware.soundtrigger.SoundTrigger;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
@@ -91,6 +92,49 @@
*/
int stopRecognition(in IVoiceInteractionService service, int keyphraseId,
in IRecognitionStatusCallback callback);
+ /**
+ * Set a model specific ModelParams with the given value. This
+ * parameter will keep its value for the duration the model is loaded regardless of starting and
+ * stopping recognition. Once the model is unloaded, the value will be lost.
+ * queryParameter should be checked first before calling this method.
+ *
+ * @param service The current VoiceInteractionService.
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param modelParam ModelParams
+ * @param value Value to set
+ * @return - {@link SoundTrigger#STATUS_OK} in case of success
+ * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+ * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+ * if API is not supported by HAL
+ */
+ int setParameter(in IVoiceInteractionService service, int keyphraseId,
+ in ModelParams modelParam, int value);
+ /**
+ * Get a model specific ModelParams. This parameter will keep its value
+ * for the duration the model is loaded regardless of starting and stopping recognition.
+ * Once the model is unloaded, the value will be lost. If the value is not set, a default
+ * value is returned. See ModelParams for parameter default values.
+ * queryParameter should be checked first before calling this method.
+ *
+ * @param service The current VoiceInteractionService.
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param modelParam ModelParams
+ * @return value of parameter
+ */
+ int getParameter(in IVoiceInteractionService service, int keyphraseId,
+ in ModelParams modelParam);
+ /**
+ * Determine if parameter control is supported for the given model handle.
+ * This method should be checked prior to calling setParameter or getParameter.
+ *
+ * @param service The current VoiceInteractionService.
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param modelParam ModelParams
+ * @return supported range of parameter, null if not supported
+ */
+ @nullable SoundTrigger.ModelParamRange queryParameter(in IVoiceInteractionService service,
+ int keyphraseId, in ModelParams modelParam);
/**
* @return the component name for the currently active voice interaction service
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 9e23c28..de64573 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -350,7 +350,19 @@
* (boolean) Whether screenshot flow going to the corner (instead of shown in a notification)
* is enabled.
*/
- public static final String SCREENSHOT_CORNER_FLOW = "screenshot_corner_flow";
+ public static final String SCREENSHOT_CORNER_FLOW = "enable_screenshot_corner_flow";
+
+ /**
+ * (boolean) Whether scrolling screenshots are enabled.
+ */
+ public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling";
+
+ // Flags related to Nav Bar
+
+ /**
+ * (boolean) Whether to force the Nav Bar handle to remain opaque.
+ */
+ public static final String NAV_BAR_HANDLE_FORCE_OPAQUE = "nav_bar_handle_force_opaque";
private SystemUiDeviceConfigFlags() {
}
diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java
index cca97f6..3a450de 100644
--- a/core/java/com/android/internal/logging/UiEventLogger.java
+++ b/core/java/com/android/internal/logging/UiEventLogger.java
@@ -16,6 +16,9 @@
package com.android.internal.logging;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
/**
* Logging interface for UI events. Normal implementation is UiEventLoggerImpl.
* For testing, use fake implementation UiEventLoggerFake.
@@ -26,13 +29,24 @@
/** Put your Event IDs in enums that implement this interface, and document them using the
* UiEventEnum annotation.
* Event IDs must be globally unique. This will be enforced by tooling (forthcoming).
- * OEMs should use event IDs above 100000.
+ * OEMs should use event IDs above 100000 and below 1000000 (1 million).
*/
interface UiEventEnum {
int getId();
}
+
/**
- * Log a simple event, with no package or instance ID.
+ * Log a simple event, with no package information. Does nothing if event.getId() <= 0.
+ * @param event an enum implementing UiEventEnum interface.
*/
- void log(UiEventEnum eventID);
+ void log(@NonNull UiEventEnum event);
+
+ /**
+ * Log an event with package information. Does nothing if event.getId() <= 0.
+ * Give both uid and packageName if both are known, but one may be omitted if unknown.
+ * @param event an enum implementing UiEventEnum interface.
+ * @param uid the uid of the relevant app, if known (0 otherwise).
+ * @param packageName the package name of the relevant app, if known (null otherwise).
+ */
+ void log(@NonNull UiEventEnum event, int uid, @Nullable String packageName);
}
diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
index e64fba2..bdf460c 100644
--- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java
+++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
@@ -24,14 +24,16 @@
* See UiEventReported atom in atoms.proto for more context.
*/
public class UiEventLoggerImpl implements UiEventLogger {
- /**
- * Log a simple event, with no package or instance ID.
- */
@Override
public void log(UiEventEnum event) {
+ log(event, 0, null);
+ }
+
+ @Override
+ public void log(UiEventEnum event, int uid, String packageName) {
final int eventID = event.getId();
if (eventID > 0) {
- StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, 0, null);
+ StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName);
}
}
}
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index 92e9bbb..6be5b81 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -30,7 +30,7 @@
/**
* Immutable data class used to record fake log events.
*/
- public class FakeUiEvent {
+ public static class FakeUiEvent {
public final int eventId;
public final int uid;
public final String packageName;
@@ -44,15 +44,20 @@
private Queue<FakeUiEvent> mLogs = new LinkedList<FakeUiEvent>();
- @Override
- public void log(UiEventEnum event) {
- final int eventId = event.getId();
- if (eventId > 0) {
- mLogs.offer(new FakeUiEvent(eventId, 0, null));
- }
- }
-
public Queue<FakeUiEvent> getLogs() {
return mLogs;
}
+
+ @Override
+ public void log(UiEventEnum event) {
+ log(event, 0, null);
+ }
+
+ @Override
+ public void log(UiEventEnum event, int uid, String packageName) {
+ final int eventId = event.getId();
+ if (eventId > 0) {
+ mLogs.offer(new FakeUiEvent(eventId, uid, packageName));
+ }
+ }
}
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 6fd271c..0f50596f 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -16,6 +16,7 @@
package com.android.internal.telephony;
+import android.telephony.BarringInfo;
import android.telephony.CallAttributes;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
@@ -64,4 +65,5 @@
void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo);
void onRegistrationFailed(in CellIdentity cellIdentity,
String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
+ void onBarringInfoChanged(in BarringInfo barringInfo);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 8e97ae1..47752c5 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -19,6 +19,7 @@
import android.content.Intent;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
+import android.telephony.BarringInfo;
import android.telephony.CallQuality;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
@@ -99,4 +100,5 @@
void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo);
void notifyRegistrationFailed(int slotIndex, int subId, in CellIdentity cellIdentity,
String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
+ void notifyBarringInfoChanged(int slotIndex, int subId, in BarringInfo barringInfo);
}
diff --git a/core/java/com/android/internal/util/ConnectivityUtil.java b/core/java/com/android/internal/util/ConnectivityUtil.java
new file mode 100644
index 0000000..799352b
--- /dev/null
+++ b/core/java/com/android/internal/util/ConnectivityUtil.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+
+/**
+ * Utility methods for common functionality using by different networks.
+ *
+ * @hide
+ */
+public class ConnectivityUtil {
+
+ private static final String TAG = "ConnectivityUtil";
+
+ private final Context mContext;
+ private final AppOpsManager mAppOps;
+ private final UserManager mUserManager;
+
+ public ConnectivityUtil(Context context) {
+ mContext = context;
+ mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ /**
+ * API to determine if the caller has fine/coarse location permission (depending on
+ * config/targetSDK level) and the location mode is enabled for the user. SecurityException is
+ * thrown if the caller has no permission or the location mode is disabled.
+ * @param pkgName package name of the application requesting access
+ * @param featureId The feature in the package
+ * @param uid The uid of the package
+ * @param message A message describing why the permission was checked. Only needed if this is
+ * not inside of a two-way binder call from the data receiver
+ */
+ public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid,
+ @Nullable String message)
+ throws SecurityException {
+ checkPackage(uid, pkgName);
+
+ // Location mode must be enabled
+ if (!isLocationModeEnabled()) {
+ // Location mode is disabled, scan results cannot be returned
+ throw new SecurityException("Location mode is disabled for the device");
+ }
+
+ // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to
+ // location information.
+ boolean canAppPackageUseLocation = checkCallersLocationPermission(pkgName, featureId,
+ uid, /* coarseForTargetSdkLessThanQ */ true, message);
+
+ // If neither caller or app has location access, there is no need to check
+ // any other permissions. Deny access to scan results.
+ if (!canAppPackageUseLocation) {
+ throw new SecurityException("UID " + uid + " has no location permission");
+ }
+ // If the User or profile is current, permission is granted
+ // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
+ if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) {
+ throw new SecurityException("UID " + uid + " profile not permitted");
+ }
+ }
+
+ /**
+ * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or
+ * android.Manifest.permission.ACCESS_COARSE_LOCATION (depending on config/targetSDK level)
+ * and a corresponding app op is allowed for this package and uid.
+ *
+ * @param pkgName PackageName of the application requesting access
+ * @param featureId The feature in the package
+ * @param uid The uid of the package
+ * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE
+ * else (false or targetSDK >= Q) then will check for FINE
+ * @param message A message describing why the permission was checked. Only needed if this is
+ * not inside of a two-way binder call from the data receiver
+ */
+ public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId,
+ int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) {
+ boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid);
+
+ String permissionType = Manifest.permission.ACCESS_FINE_LOCATION;
+ if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
+ // Having FINE permission implies having COARSE permission (but not the reverse)
+ permissionType = Manifest.permission.ACCESS_COARSE_LOCATION;
+ }
+ if (getUidPermission(permissionType, uid)
+ == PackageManager.PERMISSION_DENIED) {
+ return false;
+ }
+
+ // Always checking FINE - even if will not enforce. This will record the request for FINE
+ // so that a location request by the app is surfaced to the user.
+ boolean isFineLocationAllowed = noteAppOpAllowed(
+ AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message);
+ if (isFineLocationAllowed) {
+ return true;
+ }
+ if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
+ return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid,
+ message);
+ }
+ return false;
+ }
+
+ /**
+ * Retrieves a handle to LocationManager (if not already done) and check if location is enabled.
+ */
+ public boolean isLocationModeEnabled() {
+ LocationManager locationManager =
+ (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ try {
+ return locationManager.isLocationEnabledForUser(UserHandle.of(
+ getCurrentUser()));
+ } catch (Exception e) {
+ Log.e(TAG, "Failure to get location mode via API, falling back to settings", e);
+ return false;
+ }
+ }
+
+ private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (mContext.getPackageManager().getApplicationInfoAsUser(
+ packageName, 0,
+ UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion
+ < versionCode) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // In case of exception, assume unknown app (more strict checking)
+ // Note: This case will never happen since checkPackage is
+ // called to verify validity before checking App's version.
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return false;
+ }
+
+ private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId,
+ int uid, @Nullable String message) {
+ return mAppOps.noteOp(op, uid, pkgName, featureId, message) == AppOpsManager.MODE_ALLOWED;
+ }
+
+ private void checkPackage(int uid, String pkgName) throws SecurityException {
+ if (pkgName == null) {
+ throw new SecurityException("Checking UID " + uid + " but Package Name is Null");
+ }
+ mAppOps.checkPackage(uid, pkgName);
+ }
+
+ private boolean isCurrentProfile(int uid) {
+ UserHandle currentUser = UserHandle.of(getCurrentUser());
+ UserHandle callingUser = UserHandle.getUserHandleForUid(uid);
+ return currentUser.equals(callingUser)
+ || mUserManager.isSameProfileGroup(currentUser, callingUser);
+ }
+
+ private boolean checkInteractAcrossUsersFull(int uid) {
+ return getUidPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ @VisibleForTesting
+ protected int getCurrentUser() {
+ return ActivityManager.getCurrentUser();
+ }
+
+ private int getUidPermission(String permissionType, int uid) {
+ // We don't care about pid, pass in -1
+ return mContext.checkPermission(permissionType, -1, uid);
+ }
+}
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 5bc96d8..408a7a8 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -126,7 +126,9 @@
* @param reference an object reference
* @return the non-null reference that was validated
* @throws NullPointerException if {@code reference} is null
+ * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
*/
+ @Deprecated
@UnsupportedAppUsage
public static @NonNull <T> T checkNotNull(final T reference) {
if (reference == null) {
@@ -144,7 +146,9 @@
* be converted to a string using {@link String#valueOf(Object)}
* @return the non-null reference that was validated
* @throws NullPointerException if {@code reference} is null
+ * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
*/
+ @Deprecated
@UnsupportedAppUsage
public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) {
if (reference == null) {
@@ -154,26 +158,6 @@
}
/**
- * Ensures that an object reference passed as a parameter to the calling
- * method is not null.
- *
- * @param reference an object reference
- * @param messageTemplate a printf-style message template to use if the check fails; will
- * be converted to a string using {@link String#format(String, Object...)}
- * @param messageArgs arguments for {@code messageTemplate}
- * @return the non-null reference that was validated
- * @throws NullPointerException if {@code reference} is null
- */
- public static @NonNull <T> T checkNotNull(final T reference,
- final String messageTemplate,
- final Object... messageArgs) {
- if (reference == null) {
- throw new NullPointerException(String.format(messageTemplate, messageArgs));
- }
- return reference;
- }
-
- /**
* Ensures the truth of an expression involving the state of the calling
* instance, but not involving any parameters to the calling method.
*
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 43a227a..d7a01c4 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -2011,13 +2011,27 @@
}
if (!dispatchNestedPreFling(velocityX, velocityY)) {
- final boolean canScroll = canScrollHorizontal || canScrollVertical;
- dispatchNestedFling(velocityX, velocityY, canScroll);
+ final View firstChild = mLayout.getChildAt(0);
+ final View lastChild = mLayout.getChildAt(mLayout.getChildCount() - 1);
+ boolean consumed = false;
+ if (velocityY < 0) {
+ consumed = getChildAdapterPosition(firstChild) > 0
+ || firstChild.getTop() < getPaddingTop();
+ }
+
+ if (velocityY > 0) {
+ consumed = getChildAdapterPosition(lastChild) < mAdapter.getItemCount() - 1
+ || lastChild.getBottom() > getHeight() - getPaddingBottom();
+ }
+
+ dispatchNestedFling(velocityX, velocityY, consumed);
if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) {
return true;
}
+ final boolean canScroll = canScrollHorizontal || canScrollVertical;
+
if (canScroll) {
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 13bfc1b..31b5e49 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -44,21 +44,21 @@
import com.android.internal.util.XmlUtils;
import com.android.server.DropboxLogTags;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
/**
* Performs a number of miscellaneous, non-system-critical actions
* after the system has finished booting.
@@ -424,7 +424,23 @@
for (String propPostfix : MOUNT_DURATION_PROPS_POSTFIX) {
int duration = SystemProperties.getInt("ro.boottime.init.mount_all." + propPostfix, 0);
if (duration != 0) {
- MetricsLogger.histogram(null, "boot_mount_all_duration_" + propPostfix, duration);
+ int eventType;
+ switch (propPostfix) {
+ case "early":
+ eventType = StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_EARLY_DURATION;
+ break;
+ case "default":
+ eventType =
+ StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_DEFAULT_DURATION;
+ break;
+ case "late":
+ eventType = StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_LATE_DURATION;
+ break;
+ default:
+ continue;
+ }
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_DURATION_REPORTED, eventType,
+ duration);
}
}
}
@@ -555,16 +571,19 @@
Pattern pattern = Pattern.compile(LAST_SHUTDOWN_TIME_PATTERN, Pattern.MULTILINE);
Matcher matcher = pattern.matcher(lines);
if (matcher.find()) {
- MetricsLogger.histogram(null, "boot_fs_shutdown_duration",
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__SHUTDOWN_DURATION,
Integer.parseInt(matcher.group(1)));
- MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__SHUTDOWN_UMOUNT_STAT,
Integer.parseInt(matcher.group(2)));
Slog.i(TAG, "boot_fs_shutdown," + matcher.group(1) + "," + matcher.group(2));
} else { // not found
// This can happen when a device has too much kernel log after file system unmount
// ,exceeding maxReadSize. And having that much kernel logging can affect overall
// performance as well. So it is better to fix the kernel to reduce the amount of log.
- MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__SHUTDOWN_UMOUNT_STAT,
UMOUNT_STATUS_NOT_AVAILABLE);
Slog.w(TAG, "boot_fs_shutdown, string not found");
}
@@ -674,7 +693,11 @@
return;
}
stat = fixFsckFsStat(partition, stat, lines, startLineNumber, endLineNumber);
- MetricsLogger.histogram(null, "boot_fs_stat_" + partition, stat);
+ if ("userdata".equals(partition) || "data".equals(partition)) {
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__FS_MGR_FS_STAT_DATA_PARTITION,
+ stat);
+ }
Slog.i(TAG, "fs_stat, partition:" + partition + " stat:0x" + Integer.toHexString(stat));
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 6e6746f..a2f514a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -344,7 +344,6 @@
cppflags: ["-Wno-conversion-null"],
srcs: [
- "android/graphics/apex/android_bitmap.cpp",
"android/graphics/apex/android_matrix.cpp",
"android/graphics/apex/android_paint.cpp",
"android/graphics/apex/android_region.cpp",
@@ -430,6 +429,7 @@
android: {
srcs: [ // sources that depend on android only libraries
"android/graphics/apex/android_canvas.cpp",
+ "android/graphics/apex/android_bitmap.cpp",
"android/graphics/apex/renderthread.cpp",
"android/graphics/apex/jni_runtime.cpp",
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index fa64fd1..adedffd 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -52,42 +52,34 @@
using namespace android;
-jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format) {
- const char* mimeType;
+const char* getMimeType(SkEncodedImageFormat format) {
switch (format) {
case SkEncodedImageFormat::kBMP:
- mimeType = "image/bmp";
- break;
+ return "image/bmp";
case SkEncodedImageFormat::kGIF:
- mimeType = "image/gif";
- break;
+ return "image/gif";
case SkEncodedImageFormat::kICO:
- mimeType = "image/x-ico";
- break;
+ return "image/x-ico";
case SkEncodedImageFormat::kJPEG:
- mimeType = "image/jpeg";
- break;
+ return "image/jpeg";
case SkEncodedImageFormat::kPNG:
- mimeType = "image/png";
- break;
+ return "image/png";
case SkEncodedImageFormat::kWEBP:
- mimeType = "image/webp";
- break;
+ return "image/webp";
case SkEncodedImageFormat::kHEIF:
- mimeType = "image/heif";
- break;
+ return "image/heif";
case SkEncodedImageFormat::kWBMP:
- mimeType = "image/vnd.wap.wbmp";
- break;
+ return "image/vnd.wap.wbmp";
case SkEncodedImageFormat::kDNG:
- mimeType = "image/x-adobe-dng";
- break;
+ return "image/x-adobe-dng";
default:
- mimeType = nullptr;
- break;
+ return nullptr;
}
+}
+jstring getMimeTypeAsJavaString(JNIEnv* env, SkEncodedImageFormat format) {
jstring jstr = nullptr;
+ const char* mimeType = getMimeType(format);
if (mimeType) {
// NOTE: Caller should env->ExceptionCheck() for OOM
// (can't check for nullptr as it's a valid return value)
@@ -289,10 +281,9 @@
// Set the options and return if the client only wants the size.
if (options != NULL) {
- jstring mimeType = encodedFormatToString(
- env, (SkEncodedImageFormat)codec->getEncodedFormat());
+ jstring mimeType = getMimeTypeAsJavaString(env, codec->getEncodedFormat());
if (env->ExceptionCheck()) {
- return nullObjectReturn("OOM in encodedFormatToString()");
+ return nullObjectReturn("OOM in getMimeTypeAsJavaString()");
}
env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h
index e37c98d..45bffc4 100644
--- a/core/jni/android/graphics/BitmapFactory.h
+++ b/core/jni/android/graphics/BitmapFactory.h
@@ -26,6 +26,6 @@
extern jclass gBitmapConfig_class;
extern jmethodID gBitmapConfig_nativeToConfigMethodID;
-jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format);
+jstring getMimeTypeAsJavaString(JNIEnv*, SkEncodedImageFormat);
#endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index f18632d..06b4ff8 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -197,7 +197,7 @@
env->SetIntField(options, gOptions_heightFieldID, bitmap.height());
env->SetObjectField(options, gOptions_mimeFieldID,
- encodedFormatToString(env, (SkEncodedImageFormat)brd->getEncodedFormat()));
+ getMimeTypeAsJavaString(env, brd->getEncodedFormat()));
if (env->ExceptionCheck()) {
return nullObjectReturn("OOM in encodedFormatToString()");
}
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 627f8f5..a900286 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -475,7 +475,7 @@
static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
- return encodedFormatToString(env, decoder->mCodec->getEncodedFormat());
+ return getMimeTypeAsJavaString(env, decoder->mCodec->getEncodedFormat());
}
static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
diff --git a/media/java/android/media/RouteDiscoveryRequest.aidl b/core/jni/android/graphics/MimeType.h
similarity index 85%
copy from media/java/android/media/RouteDiscoveryRequest.aidl
copy to core/jni/android/graphics/MimeType.h
index 744f656..38a579c 100644
--- a/media/java/android/media/RouteDiscoveryRequest.aidl
+++ b/core/jni/android/graphics/MimeType.h
@@ -14,6 +14,8 @@
* limitations under the License.
*/
-package android.media;
+#pragma once
-parcelable RouteDiscoveryRequest;
+#include "SkEncodedImageFormat.h"
+
+const char* getMimeType(SkEncodedImageFormat);
diff --git a/core/jni/android/graphics/apex/android_bitmap.cpp b/core/jni/android/graphics/apex/android_bitmap.cpp
index 0f56779..6a3c01e 100644
--- a/core/jni/android/graphics/apex/android_bitmap.cpp
+++ b/core/jni/android/graphics/apex/android_bitmap.cpp
@@ -125,7 +125,7 @@
static bool nearlyEqual(float a, float b) {
// By trial and error, this is close enough to match for the ADataSpaces we
// compare for.
- return ::fabs(a-b) < .002f;
+ return ::fabs(a - b) < .002f;
}
static bool nearlyEqual(const skcms_TransferFunction& x, const skcms_TransferFunction& y) {
@@ -147,15 +147,14 @@
return true;
}
-static constexpr skcms_TransferFunction k2Dot6 =
- { 2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
// Skia's SkNamedGamut::kDCIP3 is based on a white point of D65. This gamut
// matches the white point used by ColorSpace.Named.DCIP3.
static constexpr skcms_Matrix3x3 kDCIP3 = {{
- { 0.486143, 0.323835, 0.154234 },
- { 0.226676, 0.710327, 0.0629966 },
- { 0.000800549, 0.0432385, 0.78275 },
+ {0.486143, 0.323835, 0.154234},
+ {0.226676, 0.710327, 0.0629966},
+ {0.000800549, 0.0432385, 0.78275},
}};
ADataSpace ABitmap_getDataSpace(ABitmap* bitmapHandle) {
@@ -203,8 +202,8 @@
return ADATASPACE_ADOBE_RGB;
}
- if (nearlyEqual(fn, SkNamedTransferFn::kRec2020)
- && nearlyEqual(gamut, SkNamedGamut::kRec2020)) {
+ if (nearlyEqual(fn, SkNamedTransferFn::kRec2020) &&
+ nearlyEqual(gamut, SkNamedGamut::kRec2020)) {
return ADATASPACE_BT2020;
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 79cf019..c4ee195 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2312,6 +2312,48 @@
return jStatus;
}
+static jint
+android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobject thiz,
+ jobject jaa, jobjectArray jDeviceArray)
+{
+ const jsize maxResultSize = env->GetArrayLength(jDeviceArray);
+ // the JNI is always expected to provide us with an array capable of holding enough
+ // devices i.e. the most we ever route a track to. This is preferred over receiving an ArrayList
+ // with reverse JNI to make the array grow as need as this would be less efficient, and some
+ // components call this method often
+ if (jDeviceArray == nullptr || maxResultSize == 0) {
+ ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__);
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
+ jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
+ if (jStatus != (jint) AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+
+ AudioDeviceTypeAddrVector devices;
+ jStatus = check_AudioSystem_Command(
+ AudioSystem::getDevicesForAttributes(*(paa.get()), &devices));
+ if (jStatus != NO_ERROR) {
+ return jStatus;
+ }
+
+ if (devices.size() > maxResultSize) {
+ return AUDIO_JAVA_INVALID_OPERATION;
+ }
+ size_t index = 0;
+ jobject jAudioDeviceAddress = NULL;
+ for (const auto& device : devices) {
+ jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &device);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+ env->SetObjectArrayElement(jDeviceArray, index++, jAudioDeviceAddress);
+ }
+ return jStatus;
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -2395,6 +2437,7 @@
{"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy},
{"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy},
{"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy},
+ {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes}
};
static const JNINativeMethod gEventHandlerMethods[] = {
@@ -2584,7 +2627,7 @@
gMidAudioRecordRoutingProxy_release =
android::GetMethodIDOrDie(env, gClsAudioRecordRoutingProxy, "native_release", "()V");
- AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback);
+ AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
return RegisterMethodsOrDie(env, kEventHandlerClassPathName, gEventHandlerMethods,
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index ff596b4..062b886 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -220,8 +220,12 @@
// ----------------------------------------------------------------------------
+static std::unique_ptr<DynamicLibManager> sDynamicLibManager =
+ std::make_unique<DynamicLibManager>();
+
// Let the opaque type AAssetManager refer to a guarded AssetManager2 instance.
struct GuardedAssetManager : public ::AAssetManager {
+ GuardedAssetManager() : guarded_assetmanager(sDynamicLibManager.get()) {}
Guarded<AssetManager2> guarded_assetmanager;
};
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 0992beb..fb8e633 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -537,9 +537,9 @@
LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this);
if (mObject != NULL) {
JNIEnv* env = javavm_to_jnienv(mVM);
-
+ jobject jBinderProxy = javaObjectForIBinder(env, who.promote());
env->CallStaticVoidMethod(gBinderProxyOffsets.mClass,
- gBinderProxyOffsets.mSendDeathNotice, mObject);
+ gBinderProxyOffsets.mSendDeathNotice, mObject, jBinderProxy);
if (env->ExceptionCheck()) {
jthrowable excep = env->ExceptionOccurred();
report_exception(env, excep,
@@ -1532,8 +1532,9 @@
gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
gBinderProxyOffsets.mGetInstance = GetStaticMethodIDOrDie(env, clazz, "getInstance",
"(JJ)Landroid/os/BinderProxy;");
- gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
- "(Landroid/os/IBinder$DeathRecipient;)V");
+ gBinderProxyOffsets.mSendDeathNotice =
+ GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
+ "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V");
gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J");
clazz = FindClassOrDie(env, "java/lang/Class");
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 59dab0c8..649e5d2 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -40,10 +40,15 @@
static const bool kDebugDispatchCycle = false;
+static const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
static struct {
jclass clazz;
jmethodID dispatchInputEvent;
+ jmethodID onFocusEvent;
jmethodID dispatchBatchedInputEventPending;
} gInputEventReceiverClassInfo;
@@ -219,8 +224,7 @@
bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%" PRId64,
- getInputChannelName().c_str(),
- consumeBatches ? "true" : "false", frameTime);
+ getInputChannelName().c_str(), toString(consumeBatches), frameTime);
}
if (consumeBatches) {
@@ -235,6 +239,7 @@
for (;;) {
uint32_t seq;
InputEvent* inputEvent;
+
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent);
if (status) {
@@ -302,6 +307,19 @@
inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
break;
}
+ case AINPUT_EVENT_TYPE_FOCUS: {
+ FocusEvent* focusEvent = static_cast<FocusEvent*>(inputEvent);
+ if (kDebugDispatchCycle) {
+ ALOGD("channel '%s' ~ Received focus event: hasFocus=%s, inTouchMode=%s.",
+ getInputChannelName().c_str(), toString(focusEvent->getHasFocus()),
+ toString(focusEvent->getInTouchMode()));
+ }
+ env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onFocusEvent,
+ jboolean(focusEvent->getHasFocus()),
+ jboolean(focusEvent->getInTouchMode()));
+ finishInputEvent(seq, true /* handled */);
+ return OK;
+ }
default:
assert(false); // InputConsumer should prevent this from ever happening
@@ -421,6 +439,8 @@
gInputEventReceiverClassInfo.dispatchInputEvent = GetMethodIDOrDie(env,
gInputEventReceiverClassInfo.clazz,
"dispatchInputEvent", "(ILandroid/view/InputEvent;)V");
+ gInputEventReceiverClassInfo.onFocusEvent =
+ GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onFocusEvent", "(ZZ)V");
gInputEventReceiverClassInfo.dispatchBatchedInputEventPending = GetMethodIDOrDie(env,
gInputEventReceiverClassInfo.clazz, "dispatchBatchedInputEventPending", "()V");
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4a7276c..b47080f 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -263,7 +263,8 @@
status_t res = ScreenshotClient::capture(displayToken, dataspace,
ui::PixelFormat::RGBA_8888,
sourceCrop, width, height,
- useIdentityTransform, rotation, captureSecureLayers, &buffer, capturedSecureLayers);
+ useIdentityTransform, ui::toRotation(rotation),
+ captureSecureLayers, &buffer, capturedSecureLayers);
if (res != NO_ERROR) {
return NULL;
}
@@ -724,7 +725,8 @@
{
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->setDisplayProjection(token, orientation, layerStackRect, displayRect);
+ transaction->setDisplayProjection(token, static_cast<ui::Rotation>(orientation),
+ layerStackRect, displayRect);
}
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 5039213..39ea45a 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -746,9 +746,6 @@
const userid_t user_id = multiuser_get_user_id(uid);
const std::string user_source = StringPrintf("/mnt/user/%d", user_id);
- const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
- bool isFuse = GetBoolProperty(kPropFuse, false);
-
// Shell is neither AID_ROOT nor AID_EVERYBODY. Since it equally needs 'execute' access to
// /mnt/user/0 to 'adb shell ls /sdcard' for instance, we set the uid bit of /mnt/user/0 to
// AID_SHELL. This gives shell access along with apps running as group everybody (user 0 apps)
@@ -757,9 +754,15 @@
PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL,
multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
+ bool isFuse = GetBoolProperty(kPropFuse, false);
+
if (isFuse) {
if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
+ const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
BindMount(pass_through_source, "/storage", fail_fn);
+ } else if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
+ const std::string installer_source = StringPrintf("/mnt/installer/%d", user_id);
+ BindMount(installer_source, "/storage", fail_fn);
} else {
BindMount(user_source, "/storage", fail_fn);
}
@@ -1153,6 +1156,36 @@
createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn);
}
+// Relabel directory
+static void relabelDir(const char* path, security_context_t context, fail_fn_t fail_fn) {
+ if (setfilecon(path, context) != 0) {
+ fail_fn(CREATE_ERROR("Failed to setfilecon %s %s", path, strerror(errno)));
+ }
+}
+
+// Relabel all directories under a path non-recursively.
+static void relabelAllDirs(const char* path, security_context_t context, fail_fn_t fail_fn) {
+ DIR* dir = opendir(path);
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", path));
+ }
+ struct dirent* ent;
+ while ((ent = readdir(dir))) {
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
+ auto filePath = StringPrintf("%s/%s", path, ent->d_name);
+ if (ent->d_type == DT_DIR) {
+ relabelDir(filePath.c_str(), context, fail_fn);
+ } else if (ent->d_type == DT_LNK) {
+ if (lsetfilecon(filePath.c_str(), context) != 0) {
+ fail_fn(CREATE_ERROR("Failed to lsetfilecon %s %s", filePath.c_str(), strerror(errno)));
+ }
+ } else {
+ fail_fn(CREATE_ERROR("Unexpected type: %d %s", ent->d_type, filePath.c_str()));
+ }
+ }
+ closedir(dir);
+}
+
/**
* Make other apps data directory not visible in CE, DE storage.
*
@@ -1212,10 +1245,36 @@
snprintf(internalDePath, PATH_MAX, "/data/user_de");
snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand");
+ security_context_t dataDataContext = nullptr;
+ if (getfilecon(internalDePath, &dataDataContext) < 0) {
+ fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", internalDePath,
+ strerror(errno)));
+ }
+
MountAppDataTmpFs(internalLegacyCePath, fail_fn);
MountAppDataTmpFs(internalCePath, fail_fn);
MountAppDataTmpFs(internalDePath, fail_fn);
- MountAppDataTmpFs(externalPrivateMountPath, fail_fn);
+
+ // Mount tmpfs on all external vols DE and CE storage
+ DIR* dir = opendir(externalPrivateMountPath);
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath));
+ }
+ struct dirent* ent;
+ while ((ent = readdir(dir))) {
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
+ if (ent->d_type != DT_DIR) {
+ fail_fn(CREATE_ERROR("Unexpected type: %d %s", ent->d_type, ent->d_name));
+ }
+ auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name);
+ auto cePath = StringPrintf("%s/user", volPath.c_str());
+ auto dePath = StringPrintf("%s/user_de", volPath.c_str());
+ MountAppDataTmpFs(cePath.c_str(), fail_fn);
+ MountAppDataTmpFs(dePath.c_str(), fail_fn);
+ }
+ closedir(dir);
+
+ bool legacySymlinkCreated = false;
for (int i = 0; i < size; i += 3) {
jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
@@ -1263,7 +1322,14 @@
// If it's user 0, create a symlink /data/user/0 -> /data/data,
// otherwise create /data/user/$USER
if (userId == 0) {
- symlink(internalLegacyCePath, internalCeUserPath);
+ if (!legacySymlinkCreated) {
+ legacySymlinkCreated = true;
+ int result = symlink(internalLegacyCePath, internalCeUserPath);
+ if (result != 0) {
+ fail_fn(CREATE_ERROR("Failed to create symlink %s %s", internalCeUserPath,
+ strerror(errno)));
+ }
+ }
actualCePath = internalLegacyCePath;
} else {
PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION,
@@ -1277,6 +1343,43 @@
isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode,
actualCePath, actualDePath, fail_fn);
}
+ // We set the label AFTER everything is done, as we are applying
+ // the file operations on tmpfs. If we set the label when we mount
+ // tmpfs, SELinux will not happy as we are changing system_data_files.
+ // Relabel dir under /data/user, including /data/user/0
+ relabelAllDirs(internalCePath, dataDataContext, fail_fn);
+
+ // Relabel /data/user
+ relabelDir(internalCePath, dataDataContext, fail_fn);
+
+ // Relabel /data/data
+ relabelDir(internalLegacyCePath, dataDataContext, fail_fn);
+
+ // Relabel dir under /data/user_de
+ relabelAllDirs(internalDePath, dataDataContext, fail_fn);
+
+ // Relabel /data/user_de
+ relabelDir(internalDePath, dataDataContext, fail_fn);
+
+ // Relabel CE and DE dirs under /mnt/expand
+ dir = opendir(externalPrivateMountPath);
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath));
+ }
+ while ((ent = readdir(dir))) {
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
+ auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name);
+ auto cePath = StringPrintf("%s/user", volPath.c_str());
+ auto dePath = StringPrintf("%s/user_de", volPath.c_str());
+
+ relabelAllDirs(cePath.c_str(), dataDataContext, fail_fn);
+ relabelDir(cePath.c_str(), dataDataContext, fail_fn);
+ relabelAllDirs(dePath.c_str(), dataDataContext, fail_fn);
+ relabelDir(dePath.c_str(), dataDataContext, fail_fn);
+ }
+ closedir(dir);
+
+ freecon(dataDataContext);
}
// Utility routine to specialize a zygote child process.
@@ -1328,7 +1431,7 @@
// 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)) {
+ && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name,
fail_fn);
}
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index b83b31c..cd3887e 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2553,4 +2553,8 @@
// OS: R
MANAGE_EXTERNAL_STORAGE = 1822;
+ // Open: Settings > DND > People
+ // OS: R
+ DND_PEOPLE = 1823;
+
}
diff --git a/core/proto/android/server/animationadapter.proto b/core/proto/android/server/animationadapter.proto
index c6925f4..70627ed 100644
--- a/core/proto/android/server/animationadapter.proto
+++ b/core/proto/android/server/animationadapter.proto
@@ -50,7 +50,6 @@
optional WindowAnimationSpecProto window = 1;
optional MoveAnimationSpecProto move = 2;
optional AlphaAnimationSpecProto alpha = 3;
- optional RotationAnimationSpecProto rotate = 4;
}
/* represents WindowAnimationSpec */
@@ -77,12 +76,3 @@
optional float to = 2;
optional int64 duration_ms = 3;
}
-
-/* represents RotationAnimationSpec */
-message RotationAnimationSpecProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-
- optional float start_luma = 1;
- optional float end_luma = 2;
- optional int64 duration_ms = 3;
-}
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 06040a5..b71e539 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -32,8 +32,8 @@
import "frameworks/base/core/proto/android/server/job/enums.proto";
import "frameworks/base/core/proto/android/server/statlogger.proto";
import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/base/core/proto/android/util/quotatracker.proto";
-// Next tag: 21
message JobSchedulerServiceDumpProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -160,10 +160,13 @@
optional JobConcurrencyManagerProto concurrency_manager = 20;
optional JobStorePersistStatsProto persist_stats = 21;
+
+ optional .android.util.quota.CountQuotaTrackerProto quota_tracker = 22;
+
+ // Next tag: 23
}
// A com.android.server.job.JobSchedulerService.Constants object.
-// Next tag: 29
message ConstantsProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -246,6 +249,14 @@
// Whether to use heartbeats or rolling window for quota management. True
// will use heartbeats, false will use a rolling window.
reserved 23; // use_heartbeats
+ // Whether to enable quota limits on APIs.
+ optional bool enable_api_quotas = 31;
+ // The maximum number of schedule() calls an app can make in a set amount of time.
+ optional int32 api_quota_schedule_count = 32;
+ // The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over.
+ optional int64 api_quota_schedule_window_ms = 33;
+ // Whether or not to throw an exception when an app hits its schedule quota limit.
+ optional bool api_quota_schedule_throw_exception = 34;
message QuotaController {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -331,7 +342,7 @@
// In this time after screen turns on, we increase job concurrency.
optional int32 screen_off_job_concurrency_increase_delay_ms = 28;
- // Next tag: 31
+ // Next tag: 35
}
// Next tag: 4
@@ -651,8 +662,7 @@
optional bool is_active = 2;
// The time this timer last became active. Only valid if is_active is true.
optional int64 start_time_elapsed = 3;
- // How many background jobs are currently running. Valid only if the device is_active
- // is true.
+ // How many background jobs are currently running. Valid only if is_active is true.
optional int32 bg_job_count = 4;
// All of the jobs that the Timer is currently tracking.
repeated JobStatusShortInfoProto running_jobs = 5;
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index 557075c..2de5b7f 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -32,6 +32,10 @@
}
message GraphicsStatsProto {
+ enum PipelineType {
+ GL = 0;
+ VULKAN = 1;
+ }
option (android.msg_privacy).dest = DEST_AUTOMATIC;
// The package name of the app
@@ -54,6 +58,9 @@
// The gpu frame time histogram for the package
repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+ // HWUI renders pipeline type: GL or Vulkan
+ optional PipelineType pipeline = 8;
}
message GraphicsStatsJankSummaryProto {
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 9054d54..0fca1d1 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -154,4 +154,5 @@
SET_AUTO_TIME = 127;
SET_AUTO_TIME_ZONE = 128;
SET_PACKAGES_PROTECTED = 129;
+ SET_FACTORY_RESET_PROTECTION = 130;
}
diff --git a/core/proto/android/stats/launcher/Android.bp b/core/proto/android/stats/launcher/Android.bp
index b8fb6ff..976a0b8 100644
--- a/core/proto/android/stats/launcher/Android.bp
+++ b/core/proto/android/stats/launcher/Android.bp
@@ -25,3 +25,16 @@
"*.proto",
],
}
+
+java_library {
+ name: "launcherprotoslite",
+ proto: {
+ type: "lite",
+ include_dirs: ["external/protobuf/src"],
+ },
+
+ sdk_version: "current",
+ srcs: [
+ "*.proto",
+ ],
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 03d34b5..2665e8a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -370,6 +370,7 @@
<protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
<protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" />
<protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" />
+ <protected-broadcast android:name="android.net.wifi.action.NETWORK_SETTINGS_RESET" />
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" />
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" />
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
@@ -444,6 +445,7 @@
<protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
<protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" />
+ <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" />
<protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" />
<protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" />
@@ -639,10 +641,6 @@
<protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
- <!-- NETWORK_SET_TIME moved from com.android.phone to system server. It should ultimately be
- removed. -->
- <protected-broadcast android:name="android.telephony.action.NETWORK_SET_TIME" />
-
<!-- For tether entitlement recheck-->
<protected-broadcast
android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
@@ -1790,6 +1788,11 @@
android:label="@string/permlab_preferredPaymentInfo"
android:protectionLevel="normal" />
+ <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
+ @hide -->
+ <permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
<!-- @deprecated This permission used to allow too broad access to sensitive methods and all its
uses have been replaced by a more appropriate permission. Most uses have been replaced with
a NETWORK_STACK or NETWORK_SETTINGS check. Please look up the documentation of the
@@ -2070,8 +2073,10 @@
android:protectionLevel="signature|privileged" />
<!-- Allows read only access to precise phone state.
- @hide Pending API council approval -->
+ Allows reading of detailed information about phone state for special-use applications
+ such as dialers, carrier applications, or ims applications. -->
<permission android:name="android.permission.READ_PRECISE_PHONE_STATE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
android:protectionLevel="signature|privileged" />
<!-- @SystemApi Allows read access to privileged phone state.
@@ -2563,7 +2568,7 @@
<!-- Allows telephony to suggest the time / time zone.
<p>Not for use by third-party applications.
- @hide
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @hide
-->
<permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE"
android:protectionLevel="signature|telephony" />
@@ -3381,6 +3386,14 @@
<permission android:name="android.permission.NOTIFY_TV_INPUTS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to interact with tuner resources through
+ Tuner Resource Manager.
+ <p>Protection level: signature|privileged
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.TUNER_RESOURCE_ACCESS"
+ android:protectionLevel="signature|privileged" />
+
<!-- Must be required by a {@link android.media.routing.MediaRouteService}
to ensure that only the system can interact with it.
@hide -->
@@ -4963,6 +4976,12 @@
android:process=":ui">
</activity>
+ <activity android:name="com.android.internal.app.BlockedAppActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+
<activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true">
diff --git a/core/res/res/anim/screen_rotate_0_enter.xml b/core/res/res/anim/screen_rotate_0_enter.xml
index 629be7e..93cf365 100644
--- a/core/res/res/anim/screen_rotate_0_enter.xml
+++ b/core/res/res/anim/screen_rotate_0_enter.xml
@@ -1,25 +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.
- -->
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
- <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
- android:interpolator="@interpolator/screen_rotation_alpha_in"
- android:fillEnabled="true"
- android:fillBefore="true" android:fillAfter="true"
- android:duration="@android:integer/config_screen_rotation_fade_in" />
+ android:shareInterpolator="false">
+ <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:duration="@android:integer/config_shortAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_0_exit.xml b/core/res/res/anim/screen_rotate_0_exit.xml
index fa046a0..37d5a411 100644
--- a/core/res/res/anim/screen_rotate_0_exit.xml
+++ b/core/res/res/anim/screen_rotate_0_exit.xml
@@ -1,25 +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.
- -->
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
- <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
- android:interpolator="@interpolator/screen_rotation_alpha_out"
- android:fillEnabled="true"
- android:fillBefore="true" android:fillAfter="true"
- android:duration="@android:integer/config_screen_rotation_fade_out" />
+ android:shareInterpolator="false">
</set>
diff --git a/core/res/res/anim/screen_rotate_0_frame.xml b/core/res/res/anim/screen_rotate_0_frame.xml
new file mode 100644
index 0000000..5ea9bf8
--- /dev/null
+++ b/core/res/res/anim/screen_rotate_0_frame.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+ <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:duration="@android:integer/config_shortAnimTime" />
+</set>
diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml
index 889a615..688a8d5 100644
--- a/core/res/res/anim/screen_rotate_180_enter.xml
+++ b/core/res/res/anim/screen_rotate_180_enter.xml
@@ -18,11 +18,11 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
+ android:shareInterpolator="false">
<rotate android:fromDegrees="180" android:toDegrees="0"
- android:pivotX="50%" android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true" android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="@android:integer/config_screen_rotation_total_180" />
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml
index 766fcfa..58a1868 100644
--- a/core/res/res/anim/screen_rotate_180_exit.xml
+++ b/core/res/res/anim/screen_rotate_180_exit.xml
@@ -18,11 +18,11 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
+ android:shareInterpolator="false">
<rotate android:fromDegrees="0" android:toDegrees="-180"
- android:pivotX="50%" android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true" android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="@android:integer/config_screen_rotation_total_180" />
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/screen_rotate_alpha.xml b/core/res/res/anim/screen_rotate_alpha.xml
index 2cac982..c49ef9c 100644
--- a/core/res/res/anim/screen_rotate_alpha.xml
+++ b/core/res/res/anim/screen_rotate_alpha.xml
@@ -20,8 +20,8 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
- android:interpolator="@interpolator/screen_rotation_alpha_out"
+ android:interpolator="@interpolator/decelerate_quint"
android:fillEnabled="true"
android:fillBefore="true" android:fillAfter="true"
- android:duration="@android:integer/config_screen_rotation_fade_out" />
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_enter.xml b/core/res/res/anim/screen_rotate_minus_90_enter.xml
index 87fd25e..b16d5fc 100644
--- a/core/res/res/anim/screen_rotate_minus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_enter.xml
@@ -18,17 +18,19 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
+ android:shareInterpolator="false">
+ <!-- Version for two-phase anim
<rotate android:fromDegrees="-90" android:toDegrees="0"
- android:pivotX="50%" android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true" android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="@android:integer/config_screen_rotation_total_90" />
- <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true" android:fillAfter="true"
- android:interpolator="@interpolator/screen_rotation_alpha_in"
- android:startOffset="@android:integer/config_screen_rotation_fade_in_delay"
- android:duration="@android:integer/config_screen_rotation_fade_in" />
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_longAnimTime" />
+ -->
+ <rotate android:fromDegrees="-90" android:toDegrees="0"
+ android:pivotX="50%" android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_exit.xml b/core/res/res/anim/screen_rotate_minus_90_exit.xml
index c3aee14..0927dd3 100644
--- a/core/res/res/anim/screen_rotate_minus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_exit.xml
@@ -18,16 +18,26 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
+ android:shareInterpolator="false">
+ <!-- Version for two-phase animation
<rotate android:fromDegrees="0" android:toDegrees="90"
- android:pivotX="50%" android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true" android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="@android:integer/config_screen_rotation_total_90" />
- <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
- android:fillEnabled="true"
- android:fillBefore="true" android:fillAfter="true"
- android:interpolator="@interpolator/screen_rotation_alpha_out"
- android:duration="@android:integer/config_screen_rotation_fade_out" />
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_longAnimTime" />
+ -->
+ <scale android:fromXScale="100%" android:toXScale="100%p"
+ android:fromYScale="100%" android:toYScale="100%p"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ <rotate android:fromDegrees="0" android:toDegrees="90"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_enter.xml b/core/res/res/anim/screen_rotate_plus_90_enter.xml
index 8849db4..86a8d24 100644
--- a/core/res/res/anim/screen_rotate_plus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_enter.xml
@@ -18,16 +18,19 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
+ android:shareInterpolator="false">
+ <!-- Version for two-phase animation
<rotate android:fromDegrees="90" android:toDegrees="0"
- android:pivotX="50%" android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true" android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="@android:integer/config_screen_rotation_total_90" />
- <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
- android:fillEnabled="true"
- android:interpolator="@interpolator/screen_rotation_alpha_in"
- android:startOffset="@android:integer/config_screen_rotation_fade_in_delay"
- android:duration="@android:integer/config_screen_rotation_fade_in" />
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_longAnimTime" />
+ -->
+ <rotate android:fromDegrees="90" android:toDegrees="0"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_exit.xml b/core/res/res/anim/screen_rotate_plus_90_exit.xml
index de84c3b..fd786f9 100644
--- a/core/res/res/anim/screen_rotate_plus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_exit.xml
@@ -18,16 +18,26 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
+ android:shareInterpolator="false">
+ <!-- Version for two-phase animation
<rotate android:fromDegrees="0" android:toDegrees="-90"
- android:pivotX="50%" android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true" android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="@android:integer/config_screen_rotation_total_90" />
- <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
- android:interpolator="@interpolator/screen_rotation_alpha_out"
- android:fillEnabled="true"
- android:fillBefore="true" android:fillAfter="true"
- android:duration="@android:integer/config_screen_rotation_fade_out" />
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_longAnimTime" />
+ -->
+ <scale android:fromXScale="100%" android:toXScale="100%p"
+ android:fromYScale="100%" android:toYScale="100%p"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ <rotate android:fromDegrees="0" android:toDegrees="-90"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/drawable/ic_accessibility_color_correction.xml b/core/res/res/drawable/ic_accessibility_color_correction.xml
new file mode 100644
index 0000000..02fa4b8
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_color_correction.xml
@@ -0,0 +1,37 @@
+<vector android:height="24dp" android:viewportHeight="192"
+ android:viewportWidth="192" android:width="24dp"
+ xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#00BCD4" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M183.35,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.37c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.92,82.13 183.73,83.39 183.35,84.63z" android:strokeAlpha="0.2"/>
+ <path android:pathData="M60.01,135L60.01,135H60l48.04,49h33.17c6.28,0 11.82,-4.13 13.67,-10.19l18.68,-57.84L129.51,72.2l-0.01,0c0.27,0.27 0.52,0.5 0.77,0.77l0.55,0.55c1.57,1.56 1.57,4.08 -0.04,5.68l-12.56,12.49l6.36,6.3l1.38,1.37l-5.67,5.64l-5.71,-5.68L79.15,135H60.42H60.01z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="155.9627" android:endY="165.1493"
+ android:startX="98.4649" android:startY="107.6516" android:type="linear">
+ <item android:color="#19263238" android:offset="0"/>
+ <item android:color="#00212121" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path android:fillColor="#0097A7" android:pathData="M68.55,120.35l32.173,-32.173l7.658,7.658l-32.173,32.173z"/>
+ <path android:fillColor="#FFFFFF" android:pathData="M130.83,73.52l-9.42,-9.36c-1.57,-1.56 -4.1,-1.56 -5.67,0l-12.56,12.48L95.42,69l-5.67,5.64l5.71,5.68L60,116.97V135h19.15l35.42,-35.68l5.71,5.68l5.67,-5.64l-7.73,-7.68l12.56,-12.48C132.4,77.6 132.4,75.08 130.83,73.52zM74.98,126.77l-6.43,-6.43l32.17,-32.17l6.43,6.43L74.98,126.77z"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M120.28,105l-5.71,-5.68l-35.42,35.68l-19.15,0l1,1l19.15,0l35.42,-35.68l5.71,5.68l5.68,-5.64l-1,-1z" android:strokeAlpha="0.1"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M90.75,75.64l-0.01,0l4.71,4.68l0.01,0z" android:strokeAlpha="0.1"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M131.83,74.52l-0.97,-0.97c1.54,1.56 1.53,4.06 -0.07,5.65l-12.56,12.48l1,1l12.55,-12.48C133.4,78.6 133.4,76.08 131.83,74.52z" android:strokeAlpha="0.1"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M101.72,89.17l6.67,6.66l0,0l-7.67,-7.66l-32.17,32.17l1,1z" android:strokeAlpha="0.1"/>
+ <path android:pathData="M37.13,173.7L8.62,83.69c-1.7,-5.8 0.3,-12 5,-15.6l73.52,-57.1c5.2,-4 12.5,-4 17.6,0.1l73.82,58.9c4.6,3.7 6.5,9.9 4.8,15.6l-28.51,88.21c-1.8,6.1 -7.4,10.2 -13.7,10.2H50.83C44.53,184 38.93,179.8 37.13,173.7z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:centerX="21.977" android:centerY="23.8809"
+ android:gradientRadius="158.0384" android:type="radial">
+ <item android:color="#19FFFFFF" android:offset="0"/>
+ <item android:color="#00FFFFFF" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+</vector>
diff --git a/core/res/res/drawable/ic_accessibility_color_inversion.xml b/core/res/res/drawable/ic_accessibility_color_inversion.xml
new file mode 100644
index 0000000..97b30b0
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_color_inversion.xml
@@ -0,0 +1,47 @@
+<vector android:height="24dp" android:viewportHeight="192"
+ android:viewportWidth="192" android:width="24dp"
+ xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#546E7A" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M183.37,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.38c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.94,82.13 183.74,83.39 183.37,84.63z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M53,130.05l5.03,-4.79L44.16,112c-0.05,0.78 -0.14,1.52 -0.15,2.34C43.62,136.61 61.27,154.56 84,156v-5.31C70.13,149.64 58.56,141.54 53,130.05z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M109,52v5.31c13.65,1.05 24.67,9.15 30.11,20.64l-4.92,4.79L147.81,96c0.09,-0.78 0.17,-1.53 0.19,-2.34C148.38,71.39 131.36,53.44 109,52z" android:strokeAlpha="0.2"/>
+ <path android:pathData="M154.89,173.81l13.57,-42.02l-46.53,-46.56C125.75,90.5 128,96.98 128,104c0,17.7 -14.3,32 -32,32c-8.64,0 -16.47,-3.42 -22.22,-8.97l0.9,0.91L130.73,184h10.49C147.5,184 153.04,179.87 154.89,173.81z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="153.3523" android:endY="161.6371"
+ android:startX="90.6075" android:startY="98.8923" android:type="linear">
+ <item android:color="#33263238" android:offset="0"/>
+ <item android:color="#05263238" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path android:pathData="M96,129.6V78.4c-14.11,0 -25.6,11.49 -25.6,25.6S81.89,129.6 96,129.6z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="150.8492" android:endY="164.1401"
+ android:startX="88.1044" android:startY="101.3954" android:type="linear">
+ <item android:color="#33263238" android:offset="0"/>
+ <item android:color="#05263238" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M96,136c-17.53,0 -31.72,-14.04 -31.99,-31.5c0,0.17 -0.01,0.33 -0.01,0.5c0,17.7 14.3,32 32,32s32,-14.3 32,-32c0,-0.17 -0.01,-0.33 -0.01,-0.5C127.72,121.96 113.53,136 96,136z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M70.4,104c0,0.17 0.01,0.33 0.01,0.5C70.68,90.62 82.06,79.4 96,79.4v-1C81.89,78.4 70.4,89.88 70.4,104z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z" android:strokeAlpha="0.2"/>
+ <path android:fillColor="#FFFFFF" android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z"/>
+ <path android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="156.2451" android:endY="171.4516"
+ android:startX="37.0633" android:startY="52.269802" android:type="linear">
+ <item android:color="#19FFFFFF" android:offset="0"/>
+ <item android:color="#00FFFFFF" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+</vector>
diff --git a/core/res/res/interpolator/screen_rotation_alpha_out.xml b/core/res/res/interpolator/screen_rotation_alpha_out.xml
deleted file mode 100644
index 73a37d4..0000000
--- a/core/res/res/interpolator/screen_rotation_alpha_out.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.
- -->
-
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.57"
- android:controlY1="0"
- android:controlX2="0.71"
- android:controlY2=".43"/>
diff --git a/core/res/res/layout/autofill_inline_suggestion.xml b/core/res/res/layout/autofill_inline_suggestion.xml
new file mode 100644
index 0000000..f7ac164
--- /dev/null
+++ b/core/res/res/layout/autofill_inline_suggestion.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="56dp"
+ android:background="@color/white"
+ android:orientation="horizontal"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp">
+
+ <ImageView
+ android:id="@+id/autofill_inline_suggestion_start_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
+ android:contentDescription="autofill_inline_suggestion_start_icon" />
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:layout_weight="1"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:orientation="vertical"
+ android:gravity="center_vertical">
+
+ <TextView
+ android:id="@+id/autofill_inline_suggestion_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ tools:text="username1"/>
+
+ <TextView
+ android:id="@+id/autofill_inline_suggestion_subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ tools:text="inline fill service"/>
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/autofill_inline_suggestion_end_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
+ android:contentDescription="autofill_inline_suggestion_end_icon" />
+</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9c08728..4475415 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3752,6 +3752,8 @@
</p>
-->
<attr name="canRequestFingerprintGestures" format="boolean" />
+ <!-- Attribute whether the accessibility service wants to be able to take screenshot. -->
+ <attr name="canTakeScreenshot" format="boolean" />
<!-- Animated image of the accessibility service purpose or behavior, to help users
understand how the service can help them.-->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 245aed1..6f554f02 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -147,24 +147,6 @@
<integer name="config_activityShortDur">150</integer>
<integer name="config_activityDefaultDur">220</integer>
- <!-- Fade out time for screen rotation -->
- <integer name="config_screen_rotation_fade_out">116</integer>
-
- <!-- Fade in time for screen rotation -->
- <integer name="config_screen_rotation_fade_in">233</integer>
-
- <!-- Fade in delay time for screen rotation -->
- <integer name="config_screen_rotation_fade_in_delay">100</integer>
-
- <!-- Total time for 90 degree screen rotation animations -->
- <integer name="config_screen_rotation_total_90">333</integer>
-
- <!-- Total time for 180 degree screen rotation animation -->
- <integer name="config_screen_rotation_total_180">433</integer>
-
- <!-- Total time for the rotation background color transition -->
- <integer name="config_screen_rotation_color_transition">200</integer>
-
<!-- The duration (in milliseconds) of the tooltip show/hide animations. -->
<integer name="config_tooltipAnimTime">150</integer>
@@ -1895,6 +1877,8 @@
<string name="config_defaultCallRedirection" translatable="false"></string>
<!-- The name of the package that will hold the call screening role by default. -->
<string name="config_defaultCallScreening" translatable="false"></string>
+ <!-- The name of the package that will hold the system gallery role. -->
+ <string name="config_systemGallery" translatable="false">com.android.gallery</string>
<!-- Enable/disable default bluetooth profiles:
HSP_AG, ObexObjectPush, Audio, NAP -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index bf7558c..d437aa1 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -777,4 +777,9 @@
<!-- Assistant handles -->
<dimen name="assist_handle_shadow_radius">2dp</dimen>
+ <!-- For Waterfall Display -->
+ <dimen name="waterfall_display_left_edge_size">0px</dimen>
+ <dimen name="waterfall_display_top_edge_size">0px</dimen>
+ <dimen name="waterfall_display_right_edge_size">0px</dimen>
+ <dimen name="waterfall_display_bottom_edge_size">0px</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 817ccde..9860830 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3000,7 +3000,6 @@
<public-group type="attr" first-id="0x01010607">
<public name="importantForContentCapture" />
<public name="forceQueryable" />
- <!-- @hide @SystemApi -->
<public name="resourcesMap" />
<public name="animatedImageDrawable"/>
<public name="htmlDescription"/>
@@ -3008,6 +3007,7 @@
<public name="featureId" />
<public name="supportsInlineSuggestions" />
<public name="crossProfile" />
+ <public name="canTakeScreenshot"/>
</public-group>
<public-group type="drawable" first-id="0x010800b5">
@@ -3031,6 +3031,8 @@
<public name="config_defaultCallRedirection" />
<!-- @hide @SystemApi -->
<public name="config_defaultCallScreening" />
+ <!-- @hide @SystemApi @TestApi -->
+ <public name="config_systemGallery" />
</public-group>
<public-group type="bool" first-id="0x01110005">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a0e4064..a81565a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -723,11 +723,11 @@
<string name="permgroupdesc_sms">send and view SMS messages</string>
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permgrouplab_storage">Storage</string>
+ <string name="permgrouplab_storage">Files and media</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgroupdesc_storage">access photos, media, and files on your device</string>
- <!-- Title of a category of application permissioncds, listed so the user can choose whether they want to allow the application to do this. -->
+ <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_microphone">Microphone</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgroupdesc_microphone">record audio</string>
@@ -794,6 +794,11 @@
<string name="capability_desc_canCaptureFingerprintGestures">Can capture gestures performed on
the device\'s fingerprint sensor.</string>
+ <!-- Title for the capability of an accessibility service to take screenshot. [CHAR LIMIT=32] -->
+ <string name="capability_title_canTakeScreenshot">Take screenshot</string>
+ <!-- Description for the capability of an accessibility service to take screenshot. [CHAR LIMIT=NONE] -->
+ <string name="capability_desc_canTakeScreenshot">Can take a screenshot of the display.</string>
+
<!-- Permissions -->
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -4922,6 +4927,13 @@
<!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
<string name="work_mode_turn_on">Turn on</string>
+ <!-- Title of the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=50] -->
+ <string name="app_blocked_title">App is not available</string>
+ <!-- Default message shown in the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=NONE] -->
+ <string name="app_blocked_message">
+ <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is not available right now.
+ </string>
+
<!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
<string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
<!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] -->
@@ -5207,25 +5219,14 @@
<!-- Battery saver strings -->
<!-- The user visible name of the notification channel for battery saver notifications [CHAR_LIMIT=80] -->
<string name="battery_saver_notification_channel_name">Battery Saver</string>
- <!-- Title of notification letting users know why battery saver didn't turn back on automatically after the device was unplugged [CHAR_LIMIT=NONE] -->
- <string name="battery_saver_sticky_disabled_notification_title">Battery Saver won\u2019t reactivate until battery low again</string>
- <!-- Summary of notification letting users know why battery saver didn't turn back on automatically after the device was unplugged [CHAR_LIMIT=NONE] -->
- <string name="battery_saver_sticky_disabled_notification_summary">Battery has been charged to a sufficient level. Battery Saver won\u2019t reactivate until the battery is low again.</string>
+ <!-- Title of notification letting users know that battery saver is now off [CHAR_LIMIT=80] -->
+ <string name="battery_saver_off_notification_title">Battery Saver turned off</string>
<!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] -->
- <string name="battery_saver_charged_notification_title" product="default">Phone <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string>
+ <string name="battery_saver_charged_notification_summary" product="default">Phone has enough charge. Features no longer restricted.</string>
<!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] -->
- <string name="battery_saver_charged_notification_title" product="tablet">Tablet <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string>
+ <string name="battery_saver_charged_notification_summary" product="tablet">Tablet has enough charge. Features no longer restricted.</string>
<!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] -->
- <string name="battery_saver_charged_notification_title" product="device">Device <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string>
- <!-- Summary of notification letting users know that battery saver is now off [CHAR_LIMIT=NONE] -->
- <string name="battery_saver_off_notification_summary">Battery Saver is off. Features no longer restricted.</string>
- <!-- Alternative summary of notification letting users know that battery saver has been turned off.
- If it's easy to translate the difference between "Battery Saver turned off. Features no longer restricted."
- and "Battery Saver is off. Features no longer restricted." into the target language,
- then translate "Battery Saver turned off. Features no longer restricted."
- If the translation doesn't make a difference or the difference is hard to capture in the target language,
- then translate "Battery Saver is off. Features no longer restricted." instead. [CHAR_LIMIT=NONE] -->
- <string name="battery_saver_off_alternative_notification_summary">Battery Saver turned off. Features no longer restricted.</string>
+ <string name="battery_saver_charged_notification_summary" product="device">Device has enough charge. Features no longer restricted.</string>
<!-- Description of media type: folder or directory that contains additional files. [CHAR LIMIT=32] -->
<string name="mime_type_folder">Folder</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 973d5f6..43c966c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1805,6 +1805,7 @@
<!-- From services -->
<java-symbol type="anim" name="screen_rotate_0_enter" />
<java-symbol type="anim" name="screen_rotate_0_exit" />
+ <java-symbol type="anim" name="screen_rotate_0_frame" />
<java-symbol type="anim" name="screen_rotate_180_enter" />
<java-symbol type="anim" name="screen_rotate_180_exit" />
<java-symbol type="anim" name="screen_rotate_180_frame" />
@@ -1980,7 +1981,6 @@
<java-symbol type="integer" name="config_virtualKeyQuietTimeMillis" />
<java-symbol type="integer" name="config_brightness_ramp_rate_fast" />
<java-symbol type="integer" name="config_brightness_ramp_rate_slow" />
- <java-symbol type="integer" name="config_screen_rotation_color_transition" />
<java-symbol type="layout" name="am_compat_mode_dialog" />
<java-symbol type="layout" name="launch_warning" />
<java-symbol type="layout" name="safe_mode" />
@@ -3036,6 +3036,9 @@
<java-symbol type="string" name="app_suspended_more_details" />
<java-symbol type="string" name="app_suspended_default_message" />
+ <java-symbol type="string" name="app_blocked_title" />
+ <java-symbol type="string" name="app_blocked_message" />
+
<!-- Used internally for assistant to launch activity transitions -->
<java-symbol type="id" name="cross_task_transition" />
@@ -3214,6 +3217,8 @@
<java-symbol type="string" name="edit_accessibility_shortcut_menu_button" />
<java-symbol type="string" name="cancel_accessibility_shortcut_menu_button" />
+ <java-symbol type="drawable" name="ic_accessibility_color_inversion" />
+ <java-symbol type="drawable" name="ic_accessibility_color_correction" />
<java-symbol type="drawable" name="ic_accessibility_magnification" />
<java-symbol type="drawable" name="ic_delete_item" />
@@ -3230,6 +3235,7 @@
<java-symbol type="layout" name="autofill_dataset_picker"/>
<java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/>
<java-symbol type="layout" name="autofill_dataset_picker_header_footer"/>
+ <java-symbol type="layout" name="autofill_inline_suggestion" />
<java-symbol type="id" name="autofill" />
<java-symbol type="id" name="autofill_dataset_footer"/>
<java-symbol type="id" name="autofill_dataset_header"/>
@@ -3237,6 +3243,10 @@
<java-symbol type="id" name="autofill_dataset_list"/>
<java-symbol type="id" name="autofill_dataset_picker"/>
<java-symbol type="id" name="autofill_dataset_title" />
+ <java-symbol type="id" name="autofill_inline_suggestion_end_icon" />
+ <java-symbol type="id" name="autofill_inline_suggestion_start_icon" />
+ <java-symbol type="id" name="autofill_inline_suggestion_subtitle" />
+ <java-symbol type="id" name="autofill_inline_suggestion_title" />
<java-symbol type="id" name="autofill_save_custom_subtitle" />
<java-symbol type="id" name="autofill_save_icon" />
<java-symbol type="id" name="autofill_save_no" />
@@ -3644,11 +3654,8 @@
<java-symbol type="bool" name="config_useSystemProvidedLauncherForSecondary" />
<java-symbol type="string" name="battery_saver_notification_channel_name" />
- <java-symbol type="string" name="battery_saver_sticky_disabled_notification_title" />
- <java-symbol type="string" name="battery_saver_sticky_disabled_notification_summary" />
- <java-symbol type="string" name="battery_saver_charged_notification_title" />
- <java-symbol type="string" name="battery_saver_off_notification_summary" />
- <java-symbol type="string" name="battery_saver_off_alternative_notification_summary" />
+ <java-symbol type="string" name="battery_saver_off_notification_title" />
+ <java-symbol type="string" name="battery_saver_charged_notification_summary" />
<java-symbol type="string" name="dynamic_mode_notification_channel_name" />
<java-symbol type="string" name="dynamic_mode_notification_title" />
<java-symbol type="string" name="dynamic_mode_notification_summary" />
@@ -3801,5 +3808,14 @@
<!-- Assistant handles -->
<java-symbol type="dimen" name="assist_handle_shadow_radius" />
+ <!-- For Waterfall Display -->
+ <java-symbol type="dimen" name="waterfall_display_left_edge_size" />
+ <java-symbol type="dimen" name="waterfall_display_top_edge_size" />
+ <java-symbol type="dimen" name="waterfall_display_right_edge_size" />
+ <java-symbol type="dimen" name="waterfall_display_bottom_edge_size" />
+
+ <!-- Accessibility take screenshot -->
+ <java-symbol type="string" name="capability_desc_canTakeScreenshot" />
+ <java-symbol type="string" name="capability_title_canTakeScreenshot" />
</resources>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index caae908..2df6d1c 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -8,6 +8,7 @@
"EnabledTestApp/src/**/*.java",
"BinderProxyCountingTestApp/src/**/*.java",
"BinderProxyCountingTestService/src/**/*.java",
+ "BinderDeathRecipientHelperApp/src/**/*.java",
"aidl/**/I*.aidl",
],
@@ -59,7 +60,11 @@
resource_dirs: ["res"],
resource_zips: [":FrameworksCoreTests_apks_as_resources"],
- data: [":BstatsTestApp"],
+ data: [
+ ":BstatsTestApp",
+ ":BinderDeathRecipientHelperApp1",
+ ":BinderDeathRecipientHelperApp2",
+ ],
}
// Rules to copy all the test apks to the intermediate raw resource directory
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 1aea98a..b85a332 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -95,6 +95,7 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.KILL_UID" />
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index b40aa87..ed9d3f5 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -21,6 +21,8 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="FrameworksCoreTests.apk" />
<option name="test-file-name" value="BstatsTestApp.apk" />
+ <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" />
+ <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" />
</target_preparer>
<option name="test-tag" value="FrameworksCoreTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp
new file mode 100644
index 0000000..25e4fc3
--- /dev/null
+++ b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp
@@ -0,0 +1,19 @@
+android_test_helper_app {
+ name: "BinderDeathRecipientHelperApp1",
+
+ srcs: ["**/*.java"],
+
+ sdk_version: "current",
+}
+
+android_test_helper_app {
+ name: "BinderDeathRecipientHelperApp2",
+
+ srcs: ["**/*.java"],
+
+ sdk_version: "current",
+
+ aaptflags: [
+ "--rename-manifest-package com.android.frameworks.coretests.bdr_helper_app2",
+ ],
+}
diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml
new file mode 100644
index 0000000..dbd1774
--- /dev/null
+++ b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.bdr_helper_app1">
+
+ <application>
+ <receiver android:name="com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver"
+ android:exported="true"/>
+
+ </application>
+</manifest>
diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java
new file mode 100644
index 0000000..ab79e69
--- /dev/null
+++ b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.bdr_helper_app;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Receiver used to hand off a binder owned by this process to
+ * {@link android.os.BinderDeathRecipientTest}.
+ */
+public class TestCommsReceiver extends BroadcastReceiver {
+ private static final String TAG = TestCommsReceiver.class.getSimpleName();
+ private static final String PACKAGE_NAME = "com.android.frameworks.coretests.bdr_helper_app";
+
+ public static final String ACTION_GET_BINDER = PACKAGE_NAME + ".action.GET_BINDER";
+ public static final String EXTRA_KEY_BINDER = PACKAGE_NAME + ".EXTRA_BINDER";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case ACTION_GET_BINDER:
+ final Bundle resultExtras = new Bundle();
+ resultExtras.putBinder(EXTRA_KEY_BINDER, new Binder());
+ setResult(Activity.RESULT_OK, null, resultExtras);
+ break;
+ default:
+ Log.e(TAG, "Unknown action " + intent.getAction());
+ break;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java
new file mode 100644
index 0000000..2091d55
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.appsearch.AppSearch.Document;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyProto;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+@SmallTest
+public class AppSearchDocumentTest {
+
+ @Test
+ public void testDocumentEquals_Identical() {
+ Document document1 = Document.newBuilder("uri1", "schemaType1")
+ .setCreationTimestampSecs(0L)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+ Document document2 = Document.newBuilder("uri1", "schemaType1")
+ .setCreationTimestampSecs(0L)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+ assertThat(document1).isEqualTo(document2);
+ assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_DifferentOrder() {
+ Document document1 = Document.newBuilder("uri1", "schemaType1")
+ .setCreationTimestampSecs(0L)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ // Create second document with same parameter but different order.
+ Document document2 = Document.newBuilder("uri1", "schemaType1")
+ .setCreationTimestampSecs(0L)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .build();
+ assertThat(document1).isEqualTo(document2);
+ assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_Failure() {
+ Document document1 = Document.newBuilder("uri1", "schemaType1")
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .build();
+
+ // Create second document with same order but different value.
+ Document document2 = Document.newBuilder("uri1", "schemaType1")
+ .setProperty("longKey1", 1L, 2L, 4L) // Different
+ .build();
+ assertThat(document1).isNotEqualTo(document2);
+ assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_Failure_RepeatedFieldOrder() {
+ Document document1 = Document.newBuilder("uri1", "schemaType1")
+ .setProperty("booleanKey1", true, false, true)
+ .build();
+
+ // Create second document with same order but different value.
+ Document document2 = Document.newBuilder("uri1", "schemaType1")
+ .setProperty("booleanKey1", true, true, false) // Different
+ .build();
+ assertThat(document1).isNotEqualTo(document2);
+ assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentGetSingleValue() {
+ Document document = Document.newBuilder("uri1", "schemaType1")
+ .setProperty("longKey1", 1L)
+ .setProperty("doubleKey1", 1.0)
+ .setProperty("booleanKey1", true)
+ .setProperty("stringKey1", "test-value1").build();
+ assertThat(document.getUri()).isEqualTo("uri1");
+ assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+ assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+ assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0);
+ assertThat(document.getPropertyBoolean("booleanKey1")).isTrue();
+ assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+ }
+
+ @Test
+ public void testDocumentGetArrayValues() {
+ Document document = Document.newBuilder("uri1", "schemaType1")
+ .setScore(1)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ assertThat(document.getUri()).isEqualTo("uri1");
+ assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+ assertThat(document.getScore()).isEqualTo(1);
+ assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L, 2L, 3L);
+ assertThat(document.getPropertyDoubleArray("doubleKey1")).usingExactEquality()
+ .containsExactly(1.0, 2.0, 3.0);
+ assertThat(document.getPropertyBooleanArray("booleanKey1")).asList()
+ .containsExactly(true, false, true);
+ assertThat(document.getPropertyStringArray("stringKey1")).asList()
+ .containsExactly("test-value1", "test-value2", "test-value3");
+ }
+
+ @Test
+ public void testDocumentGetValues_DifferentTypes() {
+ Document document = Document.newBuilder("uri1", "schemaType1")
+ .setScore(1)
+ .setProperty("longKey1", 1L)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ // Get a value for a key that doesn't exist
+ assertThat(document.getPropertyDouble("doubleKey1")).isNull();
+ assertThat(document.getPropertyDoubleArray("doubleKey1")).isNull();
+
+ // Get a value with a single element as an array and as a single value
+ assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+ assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L);
+
+ // Get a value with multiple elements as an array and as a single value
+ assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+ assertThat(document.getPropertyStringArray("stringKey1")).asList()
+ .containsExactly("test-value1", "test-value2", "test-value3");
+
+ // Get a value of the wrong type
+ assertThat(document.getPropertyDouble("longKey1")).isNull();
+ assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
+ }
+
+ @Test
+ public void testDocumentInvalid() {
+ Document.Builder builder = Document.newBuilder("uri1", "schemaType1");
+ assertThrows(
+ IllegalArgumentException.class, () -> builder.setProperty("test", new boolean[]{}));
+ }
+
+ @Test
+ public void testDocumentProtoPopulation() {
+ Document document = Document.newBuilder("uri1", "schemaType1")
+ .setScore(1)
+ .setCreationTimestampSecs(0)
+ .setProperty("longKey1", 1L)
+ .setProperty("doubleKey1", 1.0)
+ .setProperty("booleanKey1", true)
+ .setProperty("stringKey1", "test-value1")
+ .build();
+
+ // Create the Document proto. Need to sort the property order by key.
+ DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
+ .setUri("uri1").setSchema("schemaType1").setScore(1).setCreationTimestampSecs(0);
+ HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
+ propertyProtoMap.put("longKey1",
+ PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
+ propertyProtoMap.put("doubleKey1",
+ PropertyProto.newBuilder().setName("doubleKey1").addDoubleValues(1.0));
+ propertyProtoMap.put("booleanKey1",
+ PropertyProto.newBuilder().setName("booleanKey1").addBooleanValues(true));
+ propertyProtoMap.put("stringKey1",
+ PropertyProto.newBuilder().setName("stringKey1").addStringValues("test-value1"));
+ List<String> sortedKey = new ArrayList<>(propertyProtoMap.keySet());
+ Collections.sort(sortedKey);
+ for (String key : sortedKey) {
+ documentProtoBuilder.addProperties(propertyProtoMap.get(key));
+ }
+ assertThat(document.getProto()).isEqualTo(documentProtoBuilder.build());
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java
new file mode 100644
index 0000000..c50b1da
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearch.Email;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class AppSearchEmailTest {
+
+ @Test
+ public void testBuildEmailAndGetValue() {
+ Email email = Email.newBuilder("uri")
+ .setFrom("FakeFromAddress")
+ .setCc("CC1", "CC2")
+ // Score and Property are mixed into the middle to make sure DocumentBuilder's
+ // methods can be interleaved with EmailBuilder's methods.
+ .setScore(1)
+ .setProperty("propertyKey", "propertyValue1", "propertyValue2")
+ .setSubject("subject")
+ .setBody("EmailBody")
+ .build();
+
+ assertThat(email.getUri()).isEqualTo("uri");
+ assertThat(email.getFrom()).isEqualTo("FakeFromAddress");
+ assertThat(email.getTo()).isNull();
+ assertThat(email.getCc()).asList().containsExactly("CC1", "CC2");
+ assertThat(email.getBcc()).isNull();
+ assertThat(email.getScore()).isEqualTo(1);
+ assertThat(email.getPropertyString("propertyKey")).isEqualTo("propertyValue1");
+ assertThat(email.getPropertyStringArray("propertyKey")).asList().containsExactly(
+ "propertyValue1", "propertyValue2");
+ assertThat(email.getSubject()).isEqualTo("subject");
+ assertThat(email.getBody()).isEqualTo("EmailBody");
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
new file mode 100644
index 0000000..0be52c1
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema.IndexingConfig;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.IndexingConfig.TokenizerType;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import org.junit.Test;
+
+@SmallTest
+public class AppSearchSchemaTest {
+ @Test
+ public void testSuccess() {
+ AppSearchSchema schema = AppSearchSchema.newBuilder()
+ .addType(AppSearchSchema.newSchemaTypeBuilder("Email")
+ .addProperty(AppSearchSchema.newPropertyBuilder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+ .build()
+ ).build()
+ ).addProperty(AppSearchSchema.newPropertyBuilder("body")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+ .build()
+ ).build()
+ ).build()
+
+ ).addType(AppSearchSchema.newSchemaTypeBuilder("MusicRecording")
+ .addProperty(AppSearchSchema.newPropertyBuilder("artist")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+ .build()
+ ).build()
+ ).addProperty(AppSearchSchema.newPropertyBuilder("pubDate")
+ .setDataType(PropertyConfig.DATA_TYPE_INT64)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_NONE)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_UNKNOWN)
+ .build()
+ ).build()
+ ).build()
+ ).build();
+
+ SchemaProto expectedProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("Email")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("body")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ )
+
+ ).addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("MusicRecording")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("artist")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("pubDate")
+ .setDataType(PropertyConfigProto.DataType.Code.INT64)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.NONE)
+ .setTermMatchType(TermMatchType.Code.UNKNOWN)
+ )
+ )
+ ).build();
+
+ assertThat(schema.getProto()).isEqualTo(expectedProto);
+ }
+
+ @Test
+ public void testInvalidEnums() {
+ PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+ assertThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
+ assertThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+ }
+
+ @Test
+ public void testMissingFields() {
+ PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+ Exception e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: dataType");
+
+ builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: schemaType");
+
+ builder.setSchemaType("TestType");
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: cardinality");
+
+ builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
+ builder.build();
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java
new file mode 100644
index 0000000..4ee4aa6
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearch.Document;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/** Tests that {@link Document} and {@link Document.Builder} are extendable by developers.
+ *
+ * <p>This class is intentionally in a different package than {@link Document} to make sure there
+ * are no package-private methods required for external developers to add custom types.
+ */
+@SmallTest
+public class CustomerDocumentTest {
+ @Test
+ public void testBuildCustomerDocument() {
+ CustomerDocument customerDocument = CustomerDocument.newBuilder("uri1")
+ .setScore(1)
+ .setCreationTimestampSecs(0)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ assertThat(customerDocument.getUri()).isEqualTo("uri1");
+ assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument");
+ assertThat(customerDocument.getScore()).isEqualTo(1);
+ assertThat(customerDocument.getCreationTimestampSecs()).isEqualTo(0L);
+ assertThat(customerDocument.getPropertyLongArray("longKey1")).asList()
+ .containsExactly(1L, 2L, 3L);
+ assertThat(customerDocument.getPropertyDoubleArray("doubleKey1")).usingExactEquality()
+ .containsExactly(1.0, 2.0, 3.0);
+ assertThat(customerDocument.getPropertyBooleanArray("booleanKey1")).asList()
+ .containsExactly(true, false, true);
+ assertThat(customerDocument.getPropertyStringArray("stringKey1")).asList()
+ .containsExactly("test-value1", "test-value2", "test-value3");
+ }
+
+ /**
+ * An example document type for test purposes, defined outside of
+ * {@link android.app.appsearch.AppSearch} (the way an external developer would define it).
+ */
+ private static class CustomerDocument extends Document {
+ private CustomerDocument(Document document) {
+ super(document);
+ }
+
+ public static CustomerDocument.Builder newBuilder(String uri) {
+ return new CustomerDocument.Builder(uri);
+ }
+
+ public static class Builder extends Document.Builder<CustomerDocument.Builder> {
+ private Builder(@NonNull String uri) {
+ super(uri, "customerDocument");
+ }
+
+ @Override
+ public CustomerDocument build() {
+ return new CustomerDocument(super.build());
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
index c6c0b46..b13bcd1 100644
--- a/core/tests/coretests/src/android/net/NetworkKeyTest.java
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.net.wifi.ScanResult;
@@ -107,7 +108,7 @@
@Test
public void createFromScanResult_nullSsid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.BSSID = VALID_BSSID;
assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -115,7 +116,7 @@
@Test
public void createFromScanResult_emptySsid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = "";
scanResult.BSSID = VALID_BSSID;
@@ -124,7 +125,7 @@
@Test
public void createFromScanResult_noneSsid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = WifiManager.UNKNOWN_SSID;
scanResult.BSSID = VALID_BSSID;
@@ -133,7 +134,7 @@
@Test
public void createFromScanResult_nullBssid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = VALID_UNQUOTED_SSID;
assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -141,7 +142,7 @@
@Test
public void createFromScanResult_emptyBssid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = VALID_UNQUOTED_SSID;
scanResult.BSSID = "";
@@ -150,7 +151,7 @@
@Test
public void createFromScanResult_invalidBssid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = VALID_UNQUOTED_SSID;
scanResult.BSSID = INVALID_BSSID;
@@ -159,7 +160,7 @@
@Test
public void createFromScanResult_validSsid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = VALID_UNQUOTED_SSID;
scanResult.BSSID = VALID_BSSID;
diff --git a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
new file mode 100644
index 0000000..2cce43f
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests functionality of {@link android.os.IBinder.DeathRecipient} callbacks.
+ */
+@RunWith(AndroidJUnit4.class)
+public class BinderDeathRecipientTest {
+ private static final String TAG = BinderDeathRecipientTest.class.getSimpleName();
+ private static final String TEST_PACKAGE_NAME_1 =
+ "com.android.frameworks.coretests.bdr_helper_app1";
+ private static final String TEST_PACKAGE_NAME_2 =
+ "com.android.frameworks.coretests.bdr_helper_app2";
+
+ private Context mContext;
+ private Handler mHandler;
+ private ActivityManager mActivityManager;
+ private Set<Pair<IBinder, IBinder.DeathRecipient>> mLinkedDeathRecipients = new ArraySet<>();
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mActivityManager = mContext.getSystemService(ActivityManager.class);
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ private IBinder getNewRemoteBinder(String testPackage) throws InterruptedException {
+ final CountDownLatch resultLatch = new CountDownLatch(1);
+ final AtomicInteger resultCode = new AtomicInteger(Activity.RESULT_CANCELED);
+ final AtomicReference<Bundle> resultExtras = new AtomicReference<>();
+
+ final Intent intent = new Intent(TestCommsReceiver.ACTION_GET_BINDER)
+ .setClassName(testPackage, TestCommsReceiver.class.getName());
+ mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ resultCode.set(getResultCode());
+ resultExtras.set(getResultExtras(true));
+ resultLatch.countDown();
+ }
+ }, mHandler, Activity.RESULT_CANCELED, null, null);
+
+ assertTrue("Request for binder timed out", resultLatch.await(5, TimeUnit.SECONDS));
+ assertEquals(Activity.RESULT_OK, resultCode.get());
+ return resultExtras.get().getBinder(TestCommsReceiver.EXTRA_KEY_BINDER);
+ }
+
+ @Test
+ public void binderDied_noArgs() throws Exception {
+ final IBinder testAppBinder = getNewRemoteBinder(TEST_PACKAGE_NAME_1);
+ final CountDownLatch deathNotificationLatch = new CountDownLatch(1);
+ final IBinder.DeathRecipient simpleDeathRecipient =
+ () -> deathNotificationLatch.countDown();
+ testAppBinder.linkToDeath(simpleDeathRecipient, 0);
+ mLinkedDeathRecipients.add(Pair.create(testAppBinder, simpleDeathRecipient));
+
+ mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1);
+ assertTrue("Death notification did not arrive",
+ deathNotificationLatch.await(10, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void binderDied_iBinderArg() throws Exception {
+ final IBinder testApp1Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_1);
+ final IBinder testApp2Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_2);
+ final CyclicBarrier barrier = new CyclicBarrier(2);
+
+ final AtomicReference<IBinder> binderThatDied = new AtomicReference<>();
+ final IBinder.DeathRecipient sameDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Log.e(TAG, "Should not have been called!");
+ }
+
+ @Override
+ public void binderDied(IBinder who) {
+ binderThatDied.set(who);
+ try {
+ barrier.await();
+ } catch (InterruptedException | BrokenBarrierException e) {
+ Log.e(TAG, "Unexpected exception while waiting on CyclicBarrier", e);
+ }
+ }
+ };
+ testApp1Binder.linkToDeath(sameDeathRecipient, 0);
+ mLinkedDeathRecipients.add(Pair.create(testApp1Binder, sameDeathRecipient));
+
+ testApp2Binder.linkToDeath(sameDeathRecipient, 0);
+ mLinkedDeathRecipients.add(Pair.create(testApp2Binder, sameDeathRecipient));
+
+ mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1);
+ try {
+ barrier.await(10, TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ fail("Timed out while waiting for 1st death notification: " + e.getMessage());
+ }
+ assertEquals("Different binder received", testApp1Binder, binderThatDied.get());
+
+ barrier.reset();
+ mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_2);
+ try {
+ barrier.await(10, TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ fail("Timed out while waiting for 2nd death notification: " + e.getMessage());
+ }
+ assertEquals("Different binder received", testApp2Binder, binderThatDied.get());
+ }
+
+ @After
+ public void tearDown() {
+ for (Pair<IBinder, IBinder.DeathRecipient> linkedPair : mLinkedDeathRecipients) {
+ linkedPair.first.unlinkToDeath(linkedPair.second, 0);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index d7f50ba..e3b08bb 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -108,5 +108,30 @@
assertEquals(Insets.of(0, 100, 0, 0), insets);
}
+ @Test
+ public void testCalculateVisibleInsets_default() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+ assertEquals(Insets.of(0, 100, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateVisibleInsets_override() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+ Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+ assertEquals(Insets.of(0, 200, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateVisibleInsets_invisible() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+ mSource.setVisible(false);
+ Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+ assertEquals(Insets.of(0, 0, 0, 0), insets);
+ }
+
+
// Parcel and equals already tested via InsetsStateTest
}
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index fa2ffcca..4b76fee 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -18,12 +18,14 @@
import static android.view.InsetsState.ISIDE_BOTTOM;
import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static org.junit.Assert.assertEquals;
@@ -209,6 +211,42 @@
assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME));
}
+ @Test
+ public void testCalculateVisibleInsets() throws Exception {
+ try (final InsetsModeSession session =
+ new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+
+ // Make sure bottom gestures are ignored
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+ Rect visibleInsets = mState.calculateVisibleInsets(
+ new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_PAN);
+ assertEquals(new Rect(0, 100, 0, 100), visibleInsets);
+ }
+ }
+
+ @Test
+ public void testCalculateVisibleInsets_adjustNothing() throws Exception {
+ try (final InsetsModeSession session =
+ new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+
+ // Make sure bottom gestures are ignored
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+ Rect visibleInsets = mState.calculateVisibleInsets(
+ new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_NOTHING);
+ assertEquals(new Rect(0, 100, 0, 0), visibleInsets);
+ }
+ }
+
private void assertEqualsAndHashCode() {
assertEquals(mState, mState2);
assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 1bd52af..ade1e0d 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 35;
+ private static final int NUM_MARSHALLED_PROPERTIES = 38;
/**
* The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 3586216a..f151b81 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -19,9 +19,11 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.graphics.Region;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteCallback;
/**
* Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement
@@ -124,6 +126,10 @@
public void setSoftKeyboardCallbackEnabled(boolean enabled) {}
+ public boolean switchToInputMethod(String imeId) {
+ return false;
+ }
+
public boolean isAccessibilityButtonAvailable() {
return false;
}
@@ -139,4 +145,14 @@
public IBinder getOverlayWindowToken(int displayId) {
return null;
}
+
+ public int getWindowIdForLeashToken(IBinder token) {
+ return -1;
+ }
+
+ public Bitmap takeScreenshot(int displayId) {
+ return null;
+ }
+
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
}
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index 89c2374..a602fa3 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -32,6 +32,7 @@
import android.app.Activity;
import android.app.Instrumentation;
+import android.view.InputDevice;
import android.view.MotionEvent;
import androidx.test.InstrumentationRegistry;
@@ -67,6 +68,7 @@
mOriginalFlagValue = Editor.FLAG_ENABLE_CURSOR_DRAG;
Editor.FLAG_ENABLE_CURSOR_DRAG = true;
}
+
@After
public void after() throws Throwable {
Editor.FLAG_ENABLE_CURSOR_DRAG = mOriginalFlagValue;
@@ -281,6 +283,35 @@
}
@Test
+ public void testEditor_onTouchEvent_mouseDrag() throws Throwable {
+ String text = "testEditor_onTouchEvent_mouseDrag";
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ TextView tv = mActivity.findViewById(R.id.textview);
+ Editor editor = tv.getEditorForTesting();
+
+ // Simulate a mouse click and drag. This should NOT trigger a cursor drag.
+ long event1Time = 1001;
+ MotionEvent event1 = mouseDownEvent(event1Time, event1Time, 20f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event2Time = 1002;
+ MotionEvent event2 = mouseMoveEvent(event1Time, event2Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+ long event3Time = 1003;
+ MotionEvent event3 = mouseUpEvent(event1Time, event3Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+ }
+
+ @Test
public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
String text = "testEditor_onTouchEvent_cursorDrag";
onView(withId(R.id.textview)).perform(replaceText(text));
@@ -356,6 +387,23 @@
assertFalse(editor.getSelectionController().isCursorBeingModified());
}
+ @Test // Reproduces b/147366705
+ public void testCursorDrag_nonSelectableTextView() throws Throwable {
+ String text = "Hello world!";
+ TextView tv = mActivity.findViewById(R.id.nonselectable_textview);
+ tv.setText(text);
+ Editor editor = tv.getEditorForTesting();
+
+ // Simulate a tap. No error should be thrown.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+
+ // Swipe left to right. No error should be thrown.
+ onView(withId(R.id.nonselectable_textview)).perform(
+ dragOnText(text.indexOf("llo"), text.indexOf("!")));
+ }
+
private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
}
@@ -367,4 +415,25 @@
private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) {
return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
}
+
+ private static MotionEvent mouseDownEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = downEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+ return event;
+ }
+
+ private static MotionEvent mouseUpEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = upEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(0);
+ return event;
+ }
+
+ private static MotionEvent mouseMoveEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = moveEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+ return event;
+ }
}
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 215d0b8..3dc001d 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -48,24 +48,32 @@
}
@Test
+ public void testIsDistanceWithin() throws Exception {
+ assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
+ assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
+ assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
+ assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+ }
+
+ @Test
public void testUpdate_singleTap() throws Exception {
// Simulate an ACTION_DOWN event.
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is after the double-tap timeout.
long event3Time = event2Time + ViewConfiguration.getDoubleTapTimeout() + 1;
MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
}
@Test
@@ -74,13 +82,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -96,13 +104,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -125,13 +133,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout when
// calculated from the last ACTION_UP event time. Even though the time between the last up
@@ -140,7 +148,7 @@
long event3Time = event2Time + 1;
MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
}
@Test
@@ -149,19 +157,19 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_MOVE event.
long event2Time = 1001;
MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
// Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
long event3Time = 5000;
MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 200f, 31f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 200f, 31f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout when
// calculated from the last ACTION_UP event time. Even though the time between the last up
@@ -170,7 +178,7 @@
long event4Time = event3Time + 1;
MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f);
mTouchState.update(event4, mConfig);
- assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false);
+ assertSingleTap(mTouchState, 200f, 31f, 200f, 31f);
}
@Test
@@ -180,14 +188,14 @@
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
event1.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
event2.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -220,13 +228,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -246,7 +254,7 @@
long event5Time = 1004;
MotionEvent event5 = downEvent(event5Time, event5Time, 22f, 32f);
mTouchState.update(event5, mConfig);
- assertSingleTap(mTouchState, 22f, 32f, 21f, 31f, false);
+ assertSingleTap(mTouchState, 22f, 32f, 21f, 31f);
}
@Test
@@ -255,13 +263,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_MOVE event whose location is not far enough to start a drag.
long event2Time = 1001;
MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate another ACTION_MOVE event whose location is far enough to start a drag.
int touchSlop = mConfig.getScaledTouchSlop();
@@ -270,21 +278,135 @@
long event3Time = 1002;
MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
// Simulate an ACTION_UP event.
long event4Time = 1003;
MotionEvent event4 = upEvent(event3Time, event4Time, 200f, 300f);
mTouchState.update(event4, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 200f, 300f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 200f, 300f);
}
@Test
- public void testIsDistanceWithin() throws Exception {
- assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
- assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
- assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
- assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+ public void testUpdate_drag_startsCloseToVerticalThenHorizontal() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+ // Simulate an ACTION_MOVE event that is < 30 deg from vertical.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 174f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+ // Simulate another ACTION_MOVE event that is horizontal from the original down event.
+ // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+ // initial direction of movement.
+ long event3Time = 1003;
+ MotionEvent event3 = moveEvent(event1Time, event3Time, 200f, 0f);
+ mTouchState.update(event3, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+ // Simulate an ACTION_UP event.
+ long event4Time = 1004;
+ MotionEvent event4 = upEvent(event1Time, event4Time, 200f, 0f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 200f, 0f);
+ }
+
+ @Test
+ public void testUpdate_drag_startsHorizontalThenVertical() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+ // Simulate an ACTION_MOVE event that is > 30 deg from vertical.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 173f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+ // Simulate another ACTION_MOVE event that is vertical from the original down event.
+ // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+ // initial direction of movement.
+ long event3Time = 1003;
+ MotionEvent event3 = moveEvent(event1Time, event3Time, 0f, 200f);
+ mTouchState.update(event3, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+ // Simulate an ACTION_UP event.
+ long event4Time = 1004;
+ MotionEvent event4 = upEvent(event1Time, event4Time, 0f, 200f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0f, 200f);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterDown() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate an ACTION_CANCEL event.
+ long event2Time = 1002;
+ MotionEvent event2 = cancelEvent(event1Time, event2Time, 20f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterDrag() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate another ACTION_MOVE event whose location is far enough to start a drag.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event2Time, event2Time, 200f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+
+ // Simulate an ACTION_CANCEL event.
+ long event3Time = 1003;
+ MotionEvent event3 = cancelEvent(event1Time, event3Time, 200f, 30f);
+ mTouchState.update(event3, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterMultitap() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate an ACTION_UP event.
+ long event2Time = 1002;
+ MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
+
+ // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
+ long event3Time = 1003;
+ MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+ mTouchState.update(event3, mConfig);
+ assertMultiTap(mTouchState, 22f, 33f, 20f, 30f,
+ MultiTapStatus.DOUBLE_TAP, true);
+
+ // Simulate an ACTION_CANCEL event.
+ long event4Time = 1004;
+ MotionEvent event4 = cancelEvent(event3Time, event4Time, 20f, 30f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
}
private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
@@ -299,8 +421,12 @@
return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
}
+ private static MotionEvent cancelEvent(long downTime, long eventTime, float x, float y) {
+ return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL, x, y, 0);
+ }
+
private static void assertSingleTap(EditorTouchState touchState, float lastDownX,
- float lastDownY, float lastUpX, float lastUpY, boolean isMovedEnoughForDrag) {
+ float lastDownY, float lastUpX, float lastUpY) {
assertThat(touchState.getLastDownX(), is(lastDownX));
assertThat(touchState.getLastDownY(), is(lastDownY));
assertThat(touchState.getLastUpX(), is(lastUpX));
@@ -309,7 +435,21 @@
assertThat(touchState.isTripleClick(), is(false));
assertThat(touchState.isMultiTap(), is(false));
assertThat(touchState.isMultiTapInSameArea(), is(false));
- assertThat(touchState.isMovedEnoughForDrag(), is(isMovedEnoughForDrag));
+ assertThat(touchState.isMovedEnoughForDrag(), is(false));
+ }
+
+ private static void assertDrag(EditorTouchState touchState, float lastDownX,
+ float lastDownY, float lastUpX, float lastUpY, boolean isDragCloseToVertical) {
+ assertThat(touchState.getLastDownX(), is(lastDownX));
+ assertThat(touchState.getLastDownY(), is(lastDownY));
+ assertThat(touchState.getLastUpX(), is(lastUpX));
+ assertThat(touchState.getLastUpY(), is(lastUpY));
+ assertThat(touchState.isDoubleTap(), is(false));
+ assertThat(touchState.isTripleClick(), is(false));
+ assertThat(touchState.isMultiTap(), is(false));
+ assertThat(touchState.isMultiTapInSameArea(), is(false));
+ assertThat(touchState.isMovedEnoughForDrag(), is(true));
+ assertThat(touchState.isDragCloseToVertical(), is(isDragCloseToVertical));
}
private static void assertMultiTap(EditorTouchState touchState,
@@ -325,5 +465,6 @@
|| multiTapStatus == MultiTapStatus.TRIPLE_CLICK));
assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea));
assertThat(touchState.isMovedEnoughForDrag(), is(false));
+ assertThat(touchState.isDragCloseToVertical(), is(false));
}
}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index b411668..a0cfb31 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -48,7 +48,6 @@
import android.view.textclassifier.TextClassifier;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.Suppress;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -64,7 +63,6 @@
*/
@RunWith(AndroidJUnit4.class)
@MediumTest
-@Suppress // Consistently failing. b/29591177
public class TextViewActivityMouseTest {
@Rule
@@ -86,22 +84,12 @@
onView(withId(R.id.textview)).perform(mouseClick());
onView(withId(R.id.textview)).perform(replaceText(helloWorld));
- assertNoSelectionHandles();
-
onView(withId(R.id.textview)).perform(
mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
-
onView(withId(R.id.textview)).check(hasSelection("llo wor"));
- onHandleView(com.android.internal.R.id.selection_start_handle)
- .check(matches(isDisplayed()));
- onHandleView(com.android.internal.R.id.selection_end_handle)
- .check(matches(isDisplayed()));
-
onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
onView(withId(R.id.textview)).check(hasSelection(""));
-
- assertNoSelectionHandles();
}
@Test
@@ -196,7 +184,6 @@
onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
onView(withId(R.id.textview)).check(hasSelection(""));
- assertNoSelectionHandles();
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
}
@@ -213,7 +200,6 @@
onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
onView(withId(R.id.textview)).check(hasSelection(""));
- assertNoSelectionHandles();
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
}
@@ -403,4 +389,29 @@
mouseTripleClickAndDragOnText(text.indexOf("ird"), text.indexOf("First")));
onView(withId(R.id.textview)).check(hasSelection(text));
}
+
+ @Test
+ public void testSelectionHandlesDisplay() {
+ final String helloWorld = "Hello world!";
+ onView(withId(R.id.textview)).perform(mouseClick());
+ onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+
+ onView(withId(R.id.textview)).perform(
+ mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
+ onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+
+ // Confirm that selection handles are shown when there is a selection.
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .check(matches(isDisplayed()));
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .check(matches(isDisplayed()));
+
+ onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
+ onView(withId(R.id.textview)).check(hasSelection(""));
+
+ // Confirm that the selection handles are not shown when there is no selection. This
+ // assertion is slow so we only do it in this one test case. The rest of the tests just
+ // assert via `hasSelection("")`.
+ assertNoSelectionHandles();
+ }
}
diff --git a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
index abee736..1928d25 100644
--- a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
+++ b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
@@ -70,6 +70,8 @@
event.setSource(InputDevice.SOURCE_MOUSE);
if (event.getActionMasked() != MotionEvent.ACTION_UP) {
event.setButtonState(mButton);
+ } else {
+ event.setButtonState(0);
}
return mUiController.injectMotionEvent(event);
}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 82854e5..6784ede 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -348,7 +348,7 @@
verify(mAlertDialog).show();
verify(mAccessibilityManagerService, atLeastOnce()).getInstalledAccessibilityServiceList(
anyInt());
- verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut(null);
verify(mFrameworkObjectProvider, times(0)).getTextToSpeech(any(), any());
}
@@ -365,7 +365,7 @@
assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
mLayoutParams.privateFlags
& WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
- verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut(null);
}
@Test
@@ -433,7 +433,7 @@
verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
verify(mToast).show();
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
@@ -459,7 +459,7 @@
when(mServiceInfo.loadSummary(any())).thenReturn(null);
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
getController().performAccessibilityShortcut();
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
@@ -471,7 +471,7 @@
getController().performAccessibilityShortcut();
verifyZeroInteractions(mToast);
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
@@ -485,7 +485,7 @@
getController().performAccessibilityShortcut();
verifyZeroInteractions(mToast);
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
diff --git a/core/tests/overlaytests/remount/TEST_MAPPING b/core/tests/overlaytests/remount/TEST_MAPPING
new file mode 100644
index 0000000..54dd431
--- /dev/null
+++ b/core/tests/overlaytests/remount/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name" : "OverlayRemountedTest"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/host/Android.bp
new file mode 100644
index 0000000..3825c55
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+ name: "OverlayRemountedTest",
+ srcs: ["src/**/*.java"],
+ libs: [
+ "tradefed",
+ "junit",
+ ],
+ test_suites: ["general-tests"],
+ java_resources: [
+ ":OverlayRemountedTest_SharedLibrary",
+ ":OverlayRemountedTest_SharedLibraryOverlay",
+ ":OverlayRemountedTest_Target",
+ ],
+}
diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/host/AndroidTest.xml
new file mode 100644
index 0000000..11eadf1a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Test module config for OverlayRemountedTest">
+ <option name="test-tag" value="OverlayRemountedTest" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="remount" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.HostTest">
+ <option name="jar" value="OverlayRemountedTest.jar" />
+ </test>
+</configuration>
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java
new file mode 100644
index 0000000..84af187
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class OverlayHostTest extends BaseHostJUnit4Test {
+ private static final long TIME_OUT_MS = 30000;
+ private static final String RES_INSTRUMENTATION_ARG = "res";
+ private static final String OVERLAY_INSTRUMENTATION_ARG = "overlays";
+ private static final String RESOURCES_TYPE_SUFFIX = "_type";
+ private static final String RESOURCES_DATA_SUFFIX = "_data";
+
+ public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ public final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice);
+
+ @Rule
+ public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer);
+ private Map<String, String> mLastResults;
+
+ /**
+ * Retrieves the values of the resources in the test package. The test package must use the
+ * {@link com.android.overlaytest.remounted.target.ResourceRetrievalRunner} instrumentation.
+ **/
+ void retrieveResource(String testPackageName, List<String> requiredOverlayPaths,
+ String... resourceNames) throws DeviceNotAvailableException {
+ final HashMap<String, String> args = new HashMap<>();
+ if (!requiredOverlayPaths.isEmpty()) {
+ // Enclose the require overlay paths in quotes so the arguments will be string arguments
+ // rather than file arguments.
+ args.put(OVERLAY_INSTRUMENTATION_ARG,
+ String.format("\"%s\"", String.join(" ", requiredOverlayPaths)));
+ }
+
+ if (resourceNames.length == 0) {
+ throw new IllegalArgumentException("Must specify at least one resource to retrieve.");
+ }
+
+ // Pass the names of the resources to retrieve into the test as one string.
+ args.put(RES_INSTRUMENTATION_ARG,
+ String.format("\"%s\"", String.join(" ", resourceNames)));
+
+ runDeviceTests(getDevice(), null, testPackageName, null, null, null, TIME_OUT_MS,
+ TIME_OUT_MS, TIME_OUT_MS, false, false, args);
+
+ // Retrieve the results of the most recently run test.
+ mLastResults = (getLastDeviceRunResults().getRunMetrics() == mLastResults) ? null :
+ getLastDeviceRunResults().getRunMetrics();
+ }
+
+ /** Returns the base resource directories of the specified packages. */
+ List<String> getPackagePaths(String... packageNames)
+ throws DeviceNotAvailableException {
+ final ArrayList<String> paths = new ArrayList<>();
+ for (String packageName : packageNames) {
+ // Use the package manager shell command to find the path of the package.
+ final String result = getDevice().executeShellCommand(
+ String.format("pm dump %s | grep \"resourcePath=\"", packageName));
+ assertNotNull("Failed to find path for package " + packageName, result);
+ int splitIndex = result.indexOf('=');
+ assertTrue(splitIndex >= 0);
+ paths.add(result.substring(splitIndex + 1).trim());
+ }
+ return paths;
+ }
+
+ /** Builds the full name of a resource in the form package:type/entry. */
+ String resourceName(String pkg, String type, String entry) {
+ return String.format("%s:%s/%s", pkg, type, entry);
+ }
+
+ /**
+ * Asserts that the type and data of a a previously retrieved is the same as expected.
+ * @param resourceName the full name of the resource in the form package:type/entry
+ * @param type the expected {@link android.util.TypedValue} type of the resource
+ * @param data the expected value of the resource when coerced to a string using
+ * {@link android.util.TypedValue#coerceToString()}
+ **/
+ void assertResource(String resourceName, int type, String data) {
+ assertNotNull("Failed to get test results", mLastResults);
+ assertNotEquals("No resource values were retrieved", mLastResults.size(), 0);
+ assertEquals("" + type, mLastResults.get(resourceName + RESOURCES_TYPE_SUFFIX));
+ assertEquals("" + data, mLastResults.get(resourceName + RESOURCES_DATA_SUFFIX));
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
new file mode 100644
index 0000000..4939e16
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OverlaySharedLibraryTest extends OverlayHostTest {
+ private static final String TARGET_APK = "OverlayRemountedTest_Target.apk";
+ private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target";
+ private static final String SHARED_LIBRARY_APK =
+ "OverlayRemountedTest_SharedLibrary.apk";
+ private static final String SHARED_LIBRARY_PACKAGE =
+ "com.android.overlaytest.remounted.shared_library";
+ private static final String SHARED_LIBRARY_OVERLAY_APK =
+ "OverlayRemountedTest_SharedLibraryOverlay.apk";
+ private static final String SHARED_LIBRARY_OVERLAY_PACKAGE =
+ "com.android.overlaytest.remounted.shared_library.overlay";
+
+ @Test
+ public void testSharedLibrary() throws Exception {
+ final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+ "uses_shared_library_overlaid");
+ final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+ "shared_library_overlaid");
+
+ mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+ .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+ .reboot()
+ .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false)
+ .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+ // The shared library resource is not currently overlaid.
+ retrieveResource(Collections.emptyList(), targetResource, libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
+
+ // Overlay the shared library resource.
+ mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
+ retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
+ libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ }
+
+ @Test
+ public void testSharedLibraryPreEnabled() throws Exception {
+ final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+ "uses_shared_library_overlaid");
+ final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+ "shared_library_overlaid");
+
+ mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+ .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+ .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true)
+ .reboot()
+ .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+ retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
+ libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ }
+
+ private void retrieveResource(List<String> requiredOverlayPaths, String... resourceNames)
+ throws DeviceNotAvailableException {
+ retrieveResource(TARGET_PACKAGE, requiredOverlayPaths, resourceNames);
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
new file mode 100644
index 0000000..7028f2f
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import org.junit.Assert;
+import org.junit.rules.ExternalResource;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeoutException;
+
+class SystemPreparer extends ExternalResource {
+ private static final long REBOOT_SLEEP_MS = 30000;
+ private static final long OVERLAY_ENABLE_TIMEOUT_MS = 20000;
+
+ // The paths of the files pushed onto the device through this rule.
+ private ArrayList<String> mPushedFiles = new ArrayList<>();
+
+ // The package names of packages installed through this rule.
+ private ArrayList<String> mInstalledPackages = new ArrayList<>();
+
+ private final TemporaryFolder mHostTempFolder;
+ private final DeviceProvider mDeviceProvider;
+
+ SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) {
+ mHostTempFolder = hostTempFolder;
+ mDeviceProvider = deviceProvider;
+ }
+
+ /** Copies a file within the host test jar to a path on device. */
+ SystemPreparer pushResourceFile(String resourcePath,
+ String outputPath) throws DeviceNotAvailableException, IOException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath));
+ mPushedFiles.add(outputPath);
+ return this;
+ }
+
+ /** Installs an APK within the host test jar onto the device. */
+ SystemPreparer installResourceApk(String resourcePath, String packageName)
+ throws DeviceNotAvailableException, IOException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ final File tmpFile = copyResourceToTemp(resourcePath);
+ final String result = device.installPackage(tmpFile, true);
+ Assert.assertNull(result);
+ mInstalledPackages.add(packageName);
+ return this;
+ }
+
+ /** Sets the enable state of an overlay pacakage. */
+ SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
+ throws ExecutionException, TimeoutException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+
+ // Wait for the overlay to change its enabled state.
+ final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> {
+ while (true) {
+ device.executeShellCommand(String.format("cmd overlay %s %s",
+ enabled ? "enable" : "disable", packageName));
+
+ final String pattern = (enabled ? "[x]" : "[ ]") + " " + packageName;
+ if (device.executeShellCommand("cmd overlay list").contains(pattern)) {
+ return true;
+ }
+ }
+ });
+
+ final Executor executor = (cmd) -> new Thread(cmd).start();
+ executor.execute(enabledListener);
+ try {
+ enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS);
+ } catch (InterruptedException ignored) {
+ }
+
+ return this;
+ }
+
+ /** Restarts the device and waits until after boot is completed. */
+ SystemPreparer reboot() throws DeviceNotAvailableException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ device.executeShellCommand("stop");
+ device.executeShellCommand("start");
+ try {
+ // Sleep until the device is ready for test execution.
+ Thread.sleep(REBOOT_SLEEP_MS);
+ } catch (InterruptedException ignored) {
+ }
+
+ return this;
+ }
+
+ /** Copies a file within the host test jar to a temporary file on the host machine. */
+ private File copyResourceToTemp(String resourcePath) throws IOException {
+ final File tempFile = mHostTempFolder.newFile(resourcePath);
+ final ClassLoader classLoader = getClass().getClassLoader();
+ try (InputStream assetIs = classLoader.getResource(resourcePath).openStream();
+ FileOutputStream assetOs = new FileOutputStream(tempFile)) {
+ if (assetIs == null) {
+ throw new IllegalStateException("Failed to find resource " + resourcePath);
+ }
+
+ int b;
+ while ((b = assetIs.read()) >= 0) {
+ assetOs.write(b);
+ }
+ }
+
+ return tempFile;
+ }
+
+ /** Removes installed packages and files that were pushed to the device. */
+ @Override
+ protected void after() {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ try {
+ for (final String file : mPushedFiles) {
+ device.deleteFile(file);
+ }
+ for (final String packageName : mInstalledPackages) {
+ device.uninstallPackage(packageName);
+ }
+ } catch (DeviceNotAvailableException e) {
+ Assert.fail(e.toString());
+ }
+ }
+
+ interface DeviceProvider {
+ ITestDevice getDevice();
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
new file mode 100644
index 0000000..ffb0572
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
@@ -0,0 +1,19 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+ name: "OverlayRemountedTest_SharedLibrary",
+ sdk_version: "current",
+ aaptflags: ["--shared-lib"],
+}
diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
similarity index 71%
copy from core/res/res/interpolator/screen_rotation_alpha_in.xml
copy to core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
index 9c566a7..06e3f6a 100644
--- a/core/res/res/interpolator/screen_rotation_alpha_in.xml
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
@@ -15,8 +15,9 @@
~ limitations under the License.
-->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.15"
- android:controlY1="0.45"
- android:controlX2="0.33"
- android:controlY2="1"/>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.remounted.shared_library">
+ <application>
+ <library android:name="com.android.overlaytest.remounted.shared_library" />
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
similarity index 76%
copy from core/res/res/interpolator/screen_rotation_alpha_in.xml
copy to core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
index 9c566a7..1b06f6d 100644
--- a/core/res/res/interpolator/screen_rotation_alpha_in.xml
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
@@ -15,8 +15,10 @@
~ limitations under the License.
-->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.15"
- android:controlY1="0.45"
- android:controlX2="0.33"
- android:controlY2="1"/>
+<resources>
+ <overlayable name="TestResources">
+ <policy type="public">
+ <item type="bool" name="shared_library_overlaid" />
+ </policy>
+ </overlayable>
+</resources>
diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
similarity index 77%
copy from core/res/res/interpolator/screen_rotation_alpha_in.xml
copy to core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
index 9c566a7..5b9db16 100644
--- a/core/res/res/interpolator/screen_rotation_alpha_in.xml
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
@@ -15,8 +15,6 @@
~ limitations under the License.
-->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.15"
- android:controlY1="0.45"
- android:controlX2="0.33"
- android:controlY2="1"/>
+<resources>
+ <public type="bool" name="shared_library_overlaid" id="0x00050001"/>
+</resources>
\ No newline at end of file
diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
similarity index 77%
rename from core/res/res/interpolator/screen_rotation_alpha_in.xml
rename to core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
index 9c566a7..2dc47a7 100644
--- a/core/res/res/interpolator/screen_rotation_alpha_in.xml
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
@@ -15,8 +15,6 @@
~ limitations under the License.
-->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.15"
- android:controlY1="0.45"
- android:controlX2="0.33"
- android:controlY2="1"/>
+<resources>
+ <bool name="shared_library_overlaid">false</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
new file mode 100644
index 0000000..0d29aec
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
@@ -0,0 +1,18 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+ name: "OverlayRemountedTest_SharedLibraryOverlay",
+ sdk_version: "current",
+}
diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
similarity index 66%
copy from core/res/res/interpolator/screen_rotation_alpha_in.xml
copy to core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
index 9c566a7..53a4e61 100644
--- a/core/res/res/interpolator/screen_rotation_alpha_in.xml
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
@@ -15,8 +15,9 @@
~ limitations under the License.
-->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.15"
- android:controlY1="0.45"
- android:controlX2="0.33"
- android:controlY2="1"/>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.remounted.shared_library.overlay">
+ <application android:hasCode="false" />
+ <overlay android:targetPackage="com.android.overlaytest.remounted.shared_library"
+ android:targetName="TestResources" />
+</manifest>
\ No newline at end of file
diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
similarity index 77%
copy from core/res/res/interpolator/screen_rotation_alpha_in.xml
copy to core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
index 9c566a7..f66448a 100644
--- a/core/res/res/interpolator/screen_rotation_alpha_in.xml
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
@@ -15,8 +15,6 @@
~ limitations under the License.
-->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.15"
- android:controlY1="0.45"
- android:controlX2="0.33"
- android:controlY2="1"/>
+<resources>
+ <bool name="shared_library_overlaid">true</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/target/Android.bp
new file mode 100644
index 0000000..83f9f28
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+ name: "OverlayRemountedTest_Target",
+ srcs: ["src/**/*.java"],
+ sdk_version: "test_current",
+ libs: ["OverlayRemountedTest_SharedLibrary"],
+}
diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/target/AndroidManifest.xml
new file mode 100644
index 0000000..32fec43
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.remounted.target">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <uses-library android:name="com.android.overlaytest.remounted.shared_library"
+ android:required="true" />
+ </application>
+
+ <instrumentation android:name="com.android.overlaytest.remounted.target.ResourceRetrievalRunner"
+ android:targetPackage="com.android.overlaytest.remounted.target"
+ android:label="Remounted system RRO tests" />
+</manifest>
diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/tests/overlaytests/remount/target/res/values/values.xml
similarity index 74%
copy from core/res/res/interpolator/screen_rotation_alpha_in.xml
copy to core/tests/overlaytests/remount/target/res/values/values.xml
index 9c566a7..b5f444a 100644
--- a/core/res/res/interpolator/screen_rotation_alpha_in.xml
+++ b/core/tests/overlaytests/remount/target/res/values/values.xml
@@ -15,8 +15,6 @@
~ limitations under the License.
-->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.15"
- android:controlY1="0.45"
- android:controlX2="0.33"
- android:controlY2="1"/>
+<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
+ <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
new file mode 100644
index 0000000..2e4c211
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted.target;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An {@link Instrumentation} that retrieves the value of specified resources within the
+ * application.
+ **/
+public class ResourceRetrievalRunner extends Instrumentation {
+ private static final String TAG = ResourceRetrievalRunner.class.getSimpleName();
+
+ // A list of whitespace separated resource names of which to retrieve the resource values.
+ private static final String RESOURCE_LIST_TAG = "res";
+
+ // A list of whitespace separated overlay package paths that must be present before retrieving
+ // resource values.
+ private static final String REQUIRED_OVERLAYS_LIST_TAG = "overlays";
+
+ // The suffixes of the keys returned from the instrumentation. To retrieve the type of a
+ // resource looked up with the instrumentation, append the {@link #RESOURCES_TYPE_SUFFIX} suffix
+ // to the end of the name of the resource. For the value of a resource, use
+ // {@link #RESOURCES_DATA_SUFFIX} instead.
+ private static final String RESOURCES_TYPE_SUFFIX = "_type";
+ private static final String RESOURCES_DATA_SUFFIX = "_data";
+
+ // The amount of time in seconds to wait for the overlays to be present in the AssetManager.
+ private static final int OVERLAY_PATH_TIMEOUT = 60;
+
+ private final ArrayList<String> mResourceNames = new ArrayList<>();
+ private final ArrayList<String> mOverlayPaths = new ArrayList<>();
+ private final Bundle mResult = new Bundle();
+
+ /**
+ * Receives the instrumentation arguments and runs the resource retrieval.
+ * The entry with key {@link #RESOURCE_LIST_TAG} in the {@link Bundle} arguments is a
+ * whitespace separated string of resource names of which to retrieve the resource values.
+ * The entry with key {@link #REQUIRED_OVERLAYS_LIST_TAG} in the {@link Bundle} arguments is a
+ * whitespace separated string of overlay package paths prefixes that must be present before
+ * retrieving the resource values.
+ */
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ mResourceNames.addAll(Arrays.asList(arguments.getString(RESOURCE_LIST_TAG).split(" ")));
+ if (arguments.containsKey(REQUIRED_OVERLAYS_LIST_TAG)) {
+ mOverlayPaths.addAll(Arrays.asList(
+ arguments.getString(REQUIRED_OVERLAYS_LIST_TAG).split(" ")));
+ }
+ start();
+ }
+
+ @Override
+ public void onStart() {
+ final Resources res = getContext().getResources();
+ res.getAssets().setResourceResolutionLoggingEnabled(true);
+
+ if (!mOverlayPaths.isEmpty()) {
+ Log.d(TAG, String.format("Waiting for overlay paths [%s]",
+ String.join(",", mOverlayPaths)));
+
+ // Wait for all required overlays to be present in the AssetManager.
+ final FutureTask<Boolean> overlayListener = new FutureTask<>(() -> {
+ while (!mOverlayPaths.isEmpty()) {
+ final String[] apkPaths = res.getAssets().getApkPaths();
+ for (String path : apkPaths) {
+ for (String overlayPath : mOverlayPaths) {
+ if (path.startsWith(overlayPath)) {
+ mOverlayPaths.remove(overlayPath);
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ });
+
+ try {
+ final Executor executor = (t) -> new Thread(t).start();
+ executor.execute(overlayListener);
+ overlayListener.get(OVERLAY_PATH_TIMEOUT, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ Log.e(TAG, String.format("Failed to wait for required overlays [%s]",
+ String.join(",", mOverlayPaths)), e);
+ finish(Activity.RESULT_CANCELED, mResult);
+ }
+ }
+
+ // Retrieve the values for each resource passed in.
+ final TypedValue typedValue = new TypedValue();
+ for (final String resourceName : mResourceNames) {
+ try {
+ final int resId = res.getIdentifier(resourceName, null, null);
+ res.getValue(resId, typedValue, true);
+ Log.d(TAG, String.format("Resolution for 0x%s: %s", Integer.toHexString(resId),
+ res.getAssets().getLastResourceResolution()));
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Failed to retrieve value for resource " + resourceName, e);
+ finish(Activity.RESULT_CANCELED, mResult);
+ }
+
+ putValue(resourceName, typedValue);
+ }
+
+ finish(Activity.RESULT_OK, mResult);
+ }
+
+ private void putValue(String resourceName, TypedValue value) {
+ mResult.putInt(resourceName + RESOURCES_TYPE_SUFFIX, value.type);
+ final CharSequence textValue = value.coerceToString();
+ mResult.putString(resourceName + RESOURCES_DATA_SUFFIX,
+ textValue == null ? "null" : textValue.toString());
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java b/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java
new file mode 100644
index 0000000..0809f69
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.util;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+
+/** Unit tests for {@link ConnectivityUtil}. */
+public class ConnectivityUtilTest {
+
+ public static final String TAG = "ConnectivityUtilTest";
+
+ // Mock objects for testing
+ @Mock private Context mMockContext;
+ @Mock private PackageManager mMockPkgMgr;
+ @Mock private ApplicationInfo mMockApplInfo;
+ @Mock private AppOpsManager mMockAppOps;
+ @Mock private UserManager mMockUserManager;
+ @Mock private LocationManager mLocationManager;
+
+ private static final String TEST_PKG_NAME = "com.google.somePackage";
+ private static final String TEST_FEATURE_ID = "com.google.someFeature";
+ private static final int MANAGED_PROFILE_UID = 1100000;
+ private static final int OTHER_USER_UID = 1200000;
+
+ private final String mInteractAcrossUsersFullPermission =
+ "android.permission.INTERACT_ACROSS_USERS_FULL";
+ private final String mManifestStringCoarse =
+ Manifest.permission.ACCESS_COARSE_LOCATION;
+ private final String mManifestStringFine =
+ Manifest.permission.ACCESS_FINE_LOCATION;
+
+ // Test variables
+ private int mWifiScanAllowApps;
+ private int mUid;
+ private int mCoarseLocationPermission;
+ private int mAllowCoarseLocationApps;
+ private int mFineLocationPermission;
+ private int mAllowFineLocationApps;
+ private int mCurrentUser;
+ private boolean mIsLocationEnabled;
+ private boolean mThrowSecurityException;
+ private Answer<Integer> mReturnPermission;
+ private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();
+
+ private class TestConnectivityUtil extends ConnectivityUtil {
+
+ TestConnectivityUtil(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected int getCurrentUser() {
+ return mCurrentUser;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ initTestVars();
+ }
+
+ private void setupMocks() throws Exception {
+ when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PKG_NAME), eq(0), any()))
+ .thenReturn(mMockApplInfo);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr);
+ when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME,
+ TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps);
+ when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid),
+ eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
+ .thenReturn(mAllowCoarseLocationApps);
+ when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid),
+ eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
+ .thenReturn(mAllowFineLocationApps);
+ if (mThrowSecurityException) {
+ doThrow(new SecurityException("Package " + TEST_PKG_NAME + " doesn't belong"
+ + " to application bound to user " + mUid))
+ .when(mMockAppOps).checkPackage(mUid, TEST_PKG_NAME);
+ }
+ when(mMockContext.getSystemService(Context.APP_OPS_SERVICE))
+ .thenReturn(mMockAppOps);
+ when(mMockContext.getSystemService(Context.USER_SERVICE))
+ .thenReturn(mMockUserManager);
+ when(mMockContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
+ }
+
+ private void setupTestCase() throws Exception {
+ setupMocks();
+ setupMockInterface();
+ }
+
+ private void initTestVars() {
+ mPermissionsList.clear();
+ mReturnPermission = createPermissionAnswer();
+ mWifiScanAllowApps = AppOpsManager.MODE_ERRORED;
+ mUid = OTHER_USER_UID;
+ mThrowSecurityException = true;
+ mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M;
+ mIsLocationEnabled = false;
+ mCurrentUser = UserHandle.USER_SYSTEM;
+ mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
+ mFineLocationPermission = PackageManager.PERMISSION_DENIED;
+ mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
+ }
+
+ private void setupMockInterface() {
+ Binder.restoreCallingIdentity((((long) mUid) << 32) | Binder.getCallingPid());
+ doAnswer(mReturnPermission).when(mMockContext).checkPermission(
+ anyString(), anyInt(), anyInt());
+ when(mMockUserManager.isSameProfileGroup(UserHandle.SYSTEM,
+ UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID)))
+ .thenReturn(true);
+ when(mMockContext.checkPermission(mManifestStringCoarse, -1, mUid))
+ .thenReturn(mCoarseLocationPermission);
+ when(mMockContext.checkPermission(mManifestStringFine, -1, mUid))
+ .thenReturn(mFineLocationPermission);
+ when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(mIsLocationEnabled);
+ }
+
+ private Answer<Integer> createPermissionAnswer() {
+ return new Answer<Integer>() {
+ @Override
+ public Integer answer(InvocationOnMock invocation) {
+ int myUid = (int) invocation.getArguments()[1];
+ String myPermission = (String) invocation.getArguments()[0];
+ mPermissionsList.get(myPermission);
+ if (mPermissionsList.containsKey(myPermission)) {
+ int uid = mPermissionsList.get(myPermission);
+ if (myUid == uid) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ return PackageManager.PERMISSION_DENIED;
+ }
+ };
+ }
+
+ @Test
+ public void testEnforceLocationPermission_HasAllPermissions_BeforeQ() throws Exception {
+ mIsLocationEnabled = true;
+ mThrowSecurityException = false;
+ mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ mUid = mCurrentUser;
+ setupTestCase();
+ new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+ }
+
+ @Test
+ public void testEnforceLocationPermission_HasAllPermissions_AfterQ() throws Exception {
+ mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+ mIsLocationEnabled = true;
+ mThrowSecurityException = false;
+ mUid = mCurrentUser;
+ mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ setupTestCase();
+ new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+ }
+
+ @Test
+ public void testEnforceLocationPermission_PkgNameAndUidMismatch() throws Exception {
+ mThrowSecurityException = true;
+ mIsLocationEnabled = true;
+ mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ setupTestCase();
+
+ assertThrows(SecurityException.class,
+ () -> new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_UserOrProfileNotCurrent() throws Exception {
+ mIsLocationEnabled = true;
+ mThrowSecurityException = false;
+ mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ setupTestCase();
+
+ assertThrows(SecurityException.class,
+ () -> new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_NoCoarseLocationPermission() throws Exception {
+ mThrowSecurityException = false;
+ mIsLocationEnabled = true;
+ setupTestCase();
+ assertThrows(SecurityException.class,
+ () -> new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_NoFineLocationPermission() throws Exception {
+ mThrowSecurityException = false;
+ mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+ mIsLocationEnabled = true;
+ mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
+ mUid = MANAGED_PROFILE_UID;
+ setupTestCase();
+
+ assertThrows(SecurityException.class,
+ () -> new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+ verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString());
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_LocationModeDisabled() throws Exception {
+ mThrowSecurityException = false;
+ mUid = MANAGED_PROFILE_UID;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+ mIsLocationEnabled = false;
+
+ setupTestCase();
+
+ assertThrows(SecurityException.class,
+ () -> new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+ }
+
+ private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) {
+ try {
+ r.run();
+ Assert.fail("Expected " + exceptionClass + " to be thrown.");
+ } catch (Exception exception) {
+ assertTrue(exceptionClass.isInstance(exception));
+ }
+ }
+}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index ea66ee3..70d4678 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1 +1 @@
-per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com
+per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com, lorenzo@google.com
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 26a40d3..dfb7a16 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -135,3 +135,10 @@
src: "com.android.car.floatingcardslauncher.xml",
filename_from_src: true,
}
+
+prebuilt_etc {
+ name: "privapp_whitelist_com.android.car.ui.paintbooth",
+ sub_dir: "permissions",
+ src: "com.android.car.ui.paintbooth.xml",
+ filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml
index 5f5e908..7204898 100644
--- a/data/etc/car/com.android.car.developeroptions.xml
+++ b/data/etc/car/com.android.car.developeroptions.xml
@@ -40,6 +40,7 @@
<permission name="android.permission.MOVE_PACKAGE"/>
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.READ_SEARCH_INDEXABLES"/>
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
diff --git a/data/etc/car/com.android.car.ui.paintbooth.xml b/data/etc/car/com.android.car.ui.paintbooth.xml
new file mode 100644
index 0000000..11bf304
--- /dev/null
+++ b/data/etc/car/com.android.car.ui.paintbooth.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<permissions>
+
+ <privapp-permissions package="com.android.car.ui.paintbooth">
+ <!-- For enabling/disabling, and getting list of RROs -->
+ <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
+ <!-- For showing the current active activity -->
+ <permission name="android.permission.REAL_GET_TASKS"/>
+ <!-- For getting list of RROs for current user -->
+ <permission name="android.permission.MANAGE_USERS"/>
+ <!-- For getting list of RROs for current user-->
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 1d735af..40de83a 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -22,7 +22,6 @@
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
<permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
- <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
<permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
<permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
<permission name="android.permission.CONTROL_VPN"/>
@@ -38,6 +37,7 @@
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
+ <permission name="android.permission.OBSERVE_NETWORK_POLICY"/>
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.READ_DREAM_STATE"/>
<permission name="android.permission.READ_FRAME_BUFFER"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 9930ea2..ad99ab3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -245,6 +245,7 @@
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
<permission name="android.permission.TETHER_PRIVILEGED"/>
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+ <permission name="android.permission.UPDATE_DEVICE_STATS"/>
</privapp-permissions>
<privapp-permissions package="com.android.server.telecom">
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index ae90995..447f043 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -20,6 +20,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.graphics.fonts.FontVariationAxis;
+import android.os.Build;
import android.text.TextUtils;
import dalvik.annotation.optimization.CriticalNative;
@@ -58,7 +59,8 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public long mNativePtr;
// Points native font family builder. Must be zero after freezing this family.
@@ -67,7 +69,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public FontFamily() {
mBuilderPtr = nInitBuilder(null, 0);
mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
@@ -76,7 +79,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public FontFamily(@Nullable String[] langs, int variant) {
final String langsString;
if (langs == null || langs.length == 0) {
@@ -98,7 +102,8 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean freeze() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen");
@@ -115,7 +120,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public void abortCreation() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen or abandoned");
@@ -127,7 +133,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
int italic) {
if (mBuilderPtr == 0) {
@@ -151,7 +158,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
int weight, int italic) {
if (mBuilderPtr == 0) {
@@ -179,7 +187,8 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
boolean isAsset, int ttcIndex, int weight, int isItalic,
FontVariationAxis[] axes) {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 17aacb9..fedde42 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -308,6 +308,9 @@
if (spec.isStrongBoxBacked()) {
flags |= KeyStore.FLAG_STRONGBOX;
}
+ if (spec.isCriticalToDeviceEncryption()) {
+ flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
+ }
String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias();
KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
boolean success = false;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 91aac83..c52fd48 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -18,10 +18,8 @@
import android.annotation.Nullable;
import android.security.Credentials;
-import android.security.GateKeeper;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
-import android.security.KeyStoreException;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterCertificateChain;
@@ -458,6 +456,9 @@
if (mSpec.isStrongBoxBacked()) {
flags |= KeyStore.FLAG_STRONGBOX;
}
+ if (mSpec.isCriticalToDeviceEncryption()) {
+ flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
+ }
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 52ff9e0..450dd33 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -271,6 +271,7 @@
private final boolean mIsStrongBoxBacked;
private final boolean mUserConfirmationRequired;
private final boolean mUnlockedDeviceRequired;
+ private final boolean mCriticalToDeviceEncryption;
/*
* ***NOTE***: All new fields MUST also be added to the following:
* ParcelableKeyGenParameterSpec class.
@@ -307,7 +308,8 @@
boolean invalidatedByBiometricEnrollment,
boolean isStrongBoxBacked,
boolean userConfirmationRequired,
- boolean unlockedDeviceRequired) {
+ boolean unlockedDeviceRequired,
+ boolean criticalToDeviceEncryption) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
}
@@ -357,6 +359,7 @@
mIsStrongBoxBacked = isStrongBoxBacked;
mUserConfirmationRequired = userConfirmationRequired;
mUnlockedDeviceRequired = unlockedDeviceRequired;
+ mCriticalToDeviceEncryption = criticalToDeviceEncryption;
}
/**
@@ -710,6 +713,16 @@
}
/**
+ * Return whether this key is critical to the device encryption flow.
+ *
+ * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+ * @hide
+ */
+ public boolean isCriticalToDeviceEncryption() {
+ return mCriticalToDeviceEncryption;
+ }
+
+ /**
* Builder of {@link KeyGenParameterSpec} instances.
*/
public final static class Builder {
@@ -741,6 +754,7 @@
private boolean mIsStrongBoxBacked = false;
private boolean mUserConfirmationRequired;
private boolean mUnlockedDeviceRequired = false;
+ private boolean mCriticalToDeviceEncryption = false;
/**
* Creates a new instance of the {@code Builder}.
@@ -804,6 +818,7 @@
mIsStrongBoxBacked = sourceSpec.isStrongBoxBacked();
mUserConfirmationRequired = sourceSpec.isUserConfirmationRequired();
mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
+ mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
}
/**
@@ -1339,6 +1354,20 @@
}
/**
+ * Set whether this key is critical to the device encryption flow
+ *
+ * This is a special flag only available to system servers to indicate the current key
+ * is part of the device encryption flow.
+ *
+ * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+ * @hide
+ */
+ public Builder setCriticalToDeviceEncryption(boolean critical) {
+ mCriticalToDeviceEncryption = critical;
+ return this;
+ }
+
+ /**
* Builds an instance of {@code KeyGenParameterSpec}.
*/
@NonNull
@@ -1370,7 +1399,8 @@
mInvalidatedByBiometricEnrollment,
mIsStrongBoxBacked,
mUserConfirmationRequired,
- mUnlockedDeviceRequired);
+ mUnlockedDeviceRequired,
+ mCriticalToDeviceEncryption);
}
}
}
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index d8030fb..98e4589 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -16,8 +16,8 @@
package android.security.keystore;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import java.math.BigInteger;
import java.security.spec.AlgorithmParameterSpec;
@@ -105,6 +105,7 @@
out.writeBoolean(mSpec.isStrongBoxBacked());
out.writeBoolean(mSpec.isUserConfirmationRequired());
out.writeBoolean(mSpec.isUnlockedDeviceRequired());
+ out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
}
private static Date readDateOrNull(Parcel in) {
@@ -160,6 +161,7 @@
final boolean isStrongBoxBacked = in.readBoolean();
final boolean userConfirmationRequired = in.readBoolean();
final boolean unlockedDeviceRequired = in.readBoolean();
+ final boolean criticalToDeviceEncryption = in.readBoolean();
// The KeyGenParameterSpec is intentionally not constructed using a Builder here:
// The intention is for this class to break if new parameters are added to the
// KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
@@ -190,7 +192,8 @@
invalidatedByBiometricEnrollment,
isStrongBoxBacked,
userConfirmationRequired,
- unlockedDeviceRequired);
+ unlockedDeviceRequired,
+ criticalToDeviceEncryption);
}
public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
diff --git a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
index fca2775..b7d72fc 100644
--- a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
+++ b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
@@ -84,6 +84,7 @@
.setIsStrongBoxBacked(true)
.setUserConfirmationRequired(true)
.setUnlockedDeviceRequired(true)
+ .setCriticalToDeviceEncryption(true)
.build();
}
@@ -115,6 +116,7 @@
assertThat(spec.isStrongBoxBacked(), is(true));
assertThat(spec.isUserConfirmationRequired(), is(true));
assertThat(spec.isUnlockedDeviceRequired(), is(true));
+ assertThat(spec.isCriticalToDeviceEncryption(), is(true));
}
private Parcel parcelForReading(ParcelableKeyGenParameterSpec spec) {
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 8765719..3f2f349 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -44,6 +44,7 @@
"AttributeResolution.cpp",
"ChunkIterator.cpp",
"ConfigDescription.cpp",
+ "DynamicLibManager.cpp",
"Idmap.cpp",
"LoadedArsc.cpp",
"Locale.cpp",
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index ca4143f..8cfd2d8 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -25,6 +25,7 @@
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
+#include "androidfw/DynamicLibManager.h"
#include "androidfw/ResourceUtils.h"
#include "androidfw/Util.h"
#include "utils/ByteOrder.h"
@@ -66,7 +67,12 @@
StringPoolRef entry_string_ref;
};
-AssetManager2::AssetManager2() {
+AssetManager2::AssetManager2() : dynamic_lib_manager_(std::make_unique<DynamicLibManager>()) {
+ memset(&configuration_, 0, sizeof(configuration_));
+}
+
+AssetManager2::AssetManager2(DynamicLibManager* dynamic_lib_manager)
+ : dynamic_lib_manager_(dynamic_lib_manager) {
memset(&configuration_, 0, sizeof(configuration_));
}
@@ -85,25 +91,45 @@
package_groups_.clear();
package_ids_.fill(0xff);
- // A mapping from apk assets path to the runtime package id of its first loaded package.
+ // Overlay resources are not directly referenced by an application so their resource ids
+ // can change throughout the application's lifetime. Assign overlay package ids last.
+ std::vector<const ApkAssets*> sorted_apk_assets(apk_assets_);
+ std::stable_partition(sorted_apk_assets.begin(), sorted_apk_assets.end(), [](const ApkAssets* a) {
+ return !a->IsOverlay();
+ });
+
std::unordered_map<std::string, uint8_t> apk_assets_package_ids;
+ std::unordered_map<std::string, uint8_t> package_name_package_ids;
- // 0x01 is reserved for the android package.
- int next_package_id = 0x02;
- const size_t apk_assets_count = apk_assets_.size();
- for (size_t i = 0; i < apk_assets_count; i++) {
- const ApkAssets* apk_assets = apk_assets_[i];
- const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
-
- for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
- // Get the package ID or assign one if a shared library.
- int package_id;
- if (package->IsDynamic()) {
- package_id = next_package_id++;
- } else {
- package_id = package->GetPackageId();
+ // Assign stable package ids to application packages.
+ uint8_t next_available_package_id = 0U;
+ for (const auto& apk_assets : sorted_apk_assets) {
+ for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) {
+ uint8_t package_id = package->GetPackageId();
+ if (package->IsOverlay()) {
+ package_id = GetDynamicLibManager()->FindUnassignedId(next_available_package_id);
+ next_available_package_id = package_id + 1;
+ } else if (package->IsDynamic()) {
+ package_id = GetDynamicLibManager()->GetAssignedId(package->GetPackageName());
}
+ // Map the path of the apk assets to the package id of its first loaded package.
+ apk_assets_package_ids[apk_assets->GetPath()] = package_id;
+
+ // Map the package name of the package to the first loaded package with that package id.
+ package_name_package_ids[package->GetPackageName()] = package_id;
+ }
+ }
+
+ const int apk_assets_count = apk_assets_.size();
+ for (int i = 0; i < apk_assets_count; i++) {
+ const auto& apk_assets = apk_assets_[i];
+ for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) {
+ const auto package_id_entry = package_name_package_ids.find(package->GetPackageName());
+ CHECK(package_id_entry != package_name_package_ids.end())
+ << "no package id assgined to package " << package->GetPackageName();
+ const uint8_t package_id = package_id_entry->second;
+
// Add the mapping for package ID to index if not present.
uint8_t idx = package_ids_[package_id];
if (idx == 0xff) {
@@ -115,7 +141,10 @@
// to take effect.
const auto& loaded_idmap = apk_assets->GetLoadedIdmap();
auto target_package_iter = apk_assets_package_ids.find(loaded_idmap->TargetApkPath());
- if (target_package_iter != apk_assets_package_ids.end()) {
+ if (target_package_iter == apk_assets_package_ids.end()) {
+ LOG(INFO) << "failed to find target package for overlay "
+ << loaded_idmap->OverlayApkPath();
+ } else {
const uint8_t target_package_id = target_package_iter->second;
const uint8_t target_idx = package_ids_[target_package_id];
CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not"
@@ -123,7 +152,7 @@
PackageGroup& target_package_group = package_groups_[target_idx];
- // Create a special dynamic reference table for the overlay to rewite references to
+ // Create a special dynamic reference table for the overlay to rewrite references to
// overlay resources as references to the target resources they overlay.
auto overlay_table = std::make_shared<OverlayDynamicRefTable>(
loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
@@ -153,8 +182,6 @@
package_group->dynamic_ref_table->mEntries.replaceValueFor(
package_name, static_cast<uint8_t>(entry.package_id));
}
-
- apk_assets_package_ids.insert(std::make_pair(apk_assets->GetPath(), package_id));
}
}
@@ -567,7 +594,7 @@
if (resource_resolution_logging_enabled_) {
last_resolution_.steps.push_back(
Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result.config.toString(),
- &package_group.packages_[0].loaded_package_->GetPackageName()});
+ overlay_result.package_name});
}
}
}
@@ -1279,6 +1306,16 @@
return 0;
}
+DynamicLibManager* AssetManager2::GetDynamicLibManager() const {
+ auto dynamic_lib_manager =
+ std::get_if<std::unique_ptr<DynamicLibManager>>(&dynamic_lib_manager_);
+ if (dynamic_lib_manager) {
+ return (*dynamic_lib_manager).get();
+ } else {
+ return *std::get_if<DynamicLibManager*>(&dynamic_lib_manager_);
+ }
+}
+
std::unique_ptr<Theme> AssetManager2::NewTheme() {
return std::unique_ptr<Theme>(new Theme(this));
}
diff --git a/libs/androidfw/DynamicLibManager.cpp b/libs/androidfw/DynamicLibManager.cpp
new file mode 100644
index 0000000..895b769
--- /dev/null
+++ b/libs/androidfw/DynamicLibManager.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "androidfw/DynamicLibManager.h"
+
+namespace android {
+
+uint8_t DynamicLibManager::GetAssignedId(const std::string& library_package_name) {
+ auto lib_entry = shared_lib_package_ids_.find(library_package_name);
+ if (lib_entry != shared_lib_package_ids_.end()) {
+ return lib_entry->second;
+ }
+
+ return shared_lib_package_ids_[library_package_name] = next_package_id_++;
+}
+
+uint8_t DynamicLibManager::FindUnassignedId(uint8_t start_package_id) {
+ return (start_package_id < next_package_id_) ? next_package_id_ : start_package_id;
+}
+
+} // namespace android
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 00cbbca..b2cec2a 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -27,6 +27,7 @@
#include "androidfw/ApkAssets.h"
#include "androidfw/Asset.h"
#include "androidfw/AssetManager.h"
+#include "androidfw/DynamicLibManager.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
@@ -94,6 +95,7 @@
};
AssetManager2();
+ explicit AssetManager2(DynamicLibManager* dynamic_lib_manager);
// Sets/resets the underlying ApkAssets for this AssetManager. The ApkAssets
// are not owned by the AssetManager, and must have a longer lifetime.
@@ -371,6 +373,8 @@
// Retrieve the assigned package id of the package if loaded into this AssetManager
uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
+ DynamicLibManager* GetDynamicLibManager() const;
+
// The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
// have a longer lifetime.
std::vector<const ApkAssets*> apk_assets_;
@@ -389,6 +393,9 @@
// may need to be purged.
ResTable_config configuration_;
+ // Component responsible for assigning package ids to shared libraries.
+ std::variant<std::unique_ptr<DynamicLibManager>, DynamicLibManager*> dynamic_lib_manager_;
+
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
diff --git a/libs/androidfw/include/androidfw/DynamicLibManager.h b/libs/androidfw/include/androidfw/DynamicLibManager.h
new file mode 100644
index 0000000..1ff7079
--- /dev/null
+++ b/libs/androidfw/include/androidfw/DynamicLibManager.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROIDFW_DYNAMICLIBMANAGER_H
+#define ANDROIDFW_DYNAMICLIBMANAGER_H
+
+#include <string>
+#include <unordered_map>
+
+#include "android-base/macros.h"
+
+namespace android {
+
+// Manages assigning resource ids for dynamic resources.
+class DynamicLibManager {
+ public:
+ DynamicLibManager() = default;
+
+ // Retrieves the assigned package id for the library.
+ uint8_t GetAssignedId(const std::string& library_package_name);
+
+ // Queries in ascending order for the first available package id that is not currently assigned to
+ // a library.
+ uint8_t FindUnassignedId(uint8_t start_package_id);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DynamicLibManager);
+
+ uint8_t next_package_id_ = 0x02;
+ std::unordered_map<std::string, uint8_t> shared_lib_package_ids_;
+};
+
+} // namespace android
+
+#endif //ANDROIDFW_DYNAMICLIBMANAGER_H
diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h
index 64924f4..8891512 100644
--- a/libs/androidfw/include/androidfw/MutexGuard.h
+++ b/libs/androidfw/include/androidfw/MutexGuard.h
@@ -47,7 +47,8 @@
static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer");
public:
- explicit Guarded() : guarded_() {
+ template <typename ...Args>
+ explicit Guarded(Args&& ...args) : guarded_(std::forward<Args>(args)...) {
}
template <typename U = T>
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index b3190be..2f6f3df 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -214,6 +214,25 @@
EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data);
}
+TEST_F(AssetManager2Test, AssignsUnchangingPackageIdToSharedLibrary) {
+ DynamicLibManager lib_manager;
+ AssetManager2 assetmanager(&lib_manager);
+ assetmanager.SetApkAssets(
+ {lib_one_assets_.get(), lib_two_assets_.get(), libclient_assets_.get()});
+
+ AssetManager2 assetmanager2(&lib_manager);
+ assetmanager2.SetApkAssets(
+ {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
+ uint32_t res_id = assetmanager.GetResourceId("com.android.lib_one:string/foo");
+ ASSERT_NE(0U, res_id);
+
+ uint32_t res_id_2 = assetmanager2.GetResourceId("com.android.lib_one:string/foo");
+ ASSERT_NE(0U, res_id_2);
+
+ ASSERT_EQ(res_id, res_id_2);
+}
+
TEST_F(AssetManager2Test, GetSharedLibraryResourceName) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({lib_one_assets_.get()});
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d945fc4..51270f5 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -25,11 +25,6 @@
// GCC false-positives on this warning, and since we -Werror that's
// a problem
"-Wno-free-nonheap-object",
-
- // Clang is producing non-determistic binary when the new pass manager is
- // enabled. Disable the new PM as a temporary workaround.
- // b/142372146
- "-fno-experimental-new-pass-manager",
],
include_dirs: [
@@ -215,6 +210,7 @@
android: {
srcs: [
+ "pipeline/skia/ATraceMemoryDump.cpp",
"pipeline/skia/GLFunctorDrawable.cpp",
"pipeline/skia/LayerDrawable.cpp",
"pipeline/skia/ShaderCache.cpp",
@@ -244,7 +240,6 @@
"DeviceInfo.cpp",
"FrameInfo.cpp",
"FrameInfoVisualizer.cpp",
- "GpuMemoryTracker.cpp",
"HardwareBitmapUploader.cpp",
"HWUIProperties.sysprop",
"JankTracker.cpp",
@@ -325,7 +320,6 @@
"tests/unit/DamageAccumulatorTests.cpp",
"tests/unit/DeferredLayerUpdaterTests.cpp",
"tests/unit/FatVectorTests.cpp",
- "tests/unit/GpuMemoryTrackerTests.cpp",
"tests/unit/GraphicsStatsServiceTests.cpp",
"tests/unit/LayerUpdateQueueTests.cpp",
"tests/unit/LinearAllocatorTests.cpp",
diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp
deleted file mode 100644
index a9a7af8..0000000
--- a/libs/hwui/GpuMemoryTracker.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "utils/StringUtils.h"
-
-#include <GpuMemoryTracker.h>
-#include <cutils/compiler.h>
-#include <utils/Trace.h>
-#include <array>
-#include <sstream>
-#include <unordered_set>
-#include <vector>
-
-namespace android {
-namespace uirenderer {
-
-pthread_t gGpuThread = 0;
-
-#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount)
-
-const char* TYPE_NAMES[] = {
- "Texture", "OffscreenBuffer", "Layer",
-};
-
-struct TypeStats {
- int totalSize = 0;
- int count = 0;
-};
-
-static std::array<TypeStats, NUM_TYPES> gObjectStats;
-static std::unordered_set<GpuMemoryTracker*> gObjectSet;
-
-void GpuMemoryTracker::notifySizeChanged(int newSize) {
- int delta = newSize - mSize;
- mSize = newSize;
- gObjectStats[static_cast<int>(mType)].totalSize += delta;
-}
-
-void GpuMemoryTracker::startTrackingObject() {
- auto result = gObjectSet.insert(this);
- LOG_ALWAYS_FATAL_IF(!result.second,
- "startTrackingObject() on %p failed, already being tracked!", this);
- gObjectStats[static_cast<int>(mType)].count++;
-}
-
-void GpuMemoryTracker::stopTrackingObject() {
- size_t removed = gObjectSet.erase(this);
- LOG_ALWAYS_FATAL_IF(removed != 1, "stopTrackingObject removed %zd, is %p not being tracked?",
- removed, this);
- gObjectStats[static_cast<int>(mType)].count--;
-}
-
-void GpuMemoryTracker::onGpuContextCreated() {
- LOG_ALWAYS_FATAL_IF(gGpuThread != 0,
- "We already have a gpu thread? "
- "current = %lu, gpu thread = %lu",
- pthread_self(), gGpuThread);
- gGpuThread = pthread_self();
-}
-
-void GpuMemoryTracker::onGpuContextDestroyed() {
- gGpuThread = 0;
- if (CC_UNLIKELY(gObjectSet.size() > 0)) {
- std::stringstream os;
- dump(os);
- ALOGE("%s", os.str().c_str());
- LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size());
- }
-}
-
-void GpuMemoryTracker::dump() {
- std::stringstream strout;
- dump(strout);
- ALOGD("%s", strout.str().c_str());
-}
-
-void GpuMemoryTracker::dump(std::ostream& stream) {
- for (int type = 0; type < NUM_TYPES; type++) {
- const TypeStats& stats = gObjectStats[type];
- stream << TYPE_NAMES[type];
- stream << " is using " << SizePrinter{stats.totalSize};
- stream << ", count = " << stats.count;
- stream << std::endl;
- }
-}
-
-int GpuMemoryTracker::getInstanceCount(GpuObjectType type) {
- return gObjectStats[static_cast<int>(type)].count;
-}
-
-int GpuMemoryTracker::getTotalSize(GpuObjectType type) {
- return gObjectStats[static_cast<int>(type)].totalSize;
-}
-
-void GpuMemoryTracker::onFrameCompleted() {
- if (ATRACE_ENABLED()) {
- char buf[128];
- for (int type = 0; type < NUM_TYPES; type++) {
- snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]);
- const TypeStats& stats = gObjectStats[type];
- ATRACE_INT(buf, stats.totalSize);
- snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]);
- ATRACE_INT(buf, stats.count);
- }
- }
-}
-
-} // namespace uirenderer
-} // namespace android;
diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h
deleted file mode 100644
index de3ca99..0000000
--- a/libs/hwui/GpuMemoryTracker.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <pthread.h>
-#include <ostream>
-
-#include <log/log.h>
-
-namespace android {
-namespace uirenderer {
-
-extern pthread_t gGpuThread;
-
-#define ASSERT_GPU_THREAD() \
- LOG_ALWAYS_FATAL_IF(!pthread_equal(gGpuThread, pthread_self()), \
- "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \
- "!= gpu thread %lu", \
- this, static_cast<int>(mType), mSize, pthread_self(), gGpuThread)
-
-enum class GpuObjectType {
- Texture = 0,
- OffscreenBuffer,
- Layer,
-
- TypeCount,
-};
-
-class GpuMemoryTracker {
-public:
- GpuObjectType objectType() { return mType; }
- int objectSize() { return mSize; }
-
- static void onGpuContextCreated();
- static void onGpuContextDestroyed();
- static void dump();
- static void dump(std::ostream& stream);
- static int getInstanceCount(GpuObjectType type);
- static int getTotalSize(GpuObjectType type);
- static void onFrameCompleted();
-
-protected:
- explicit GpuMemoryTracker(GpuObjectType type) : mType(type) {
- ASSERT_GPU_THREAD();
- startTrackingObject();
- }
-
- ~GpuMemoryTracker() {
- notifySizeChanged(0);
- stopTrackingObject();
- }
-
- void notifySizeChanged(int newSize);
-
-private:
- void startTrackingObject();
- void stopTrackingObject();
-
- int mSize = 0;
- GpuObjectType mType;
-};
-
-} // namespace uirenderer
-} // namespace android;
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index 7921662..a8e36e3 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -15,6 +15,7 @@
*/
#include "ProfileData.h"
+#include "Properties.h"
#include <cinttypes>
@@ -102,6 +103,7 @@
mGPUFrameCounts[i] >>= divider;
mGPUFrameCounts[i] += other.mGPUFrameCounts[i];
}
+ mPipelineType = other.mPipelineType;
}
void ProfileData::dump(int fd) const {
@@ -157,6 +159,7 @@
mTotalFrameCount = 0;
mJankFrameCount = 0;
mStatStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mPipelineType = Properties::getRenderPipelineType();
}
void ProfileData::reportFrame(int64_t duration) {
diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h
index ccbffc6..dd3ba66 100644
--- a/libs/hwui/ProfileData.h
+++ b/libs/hwui/ProfileData.h
@@ -16,6 +16,7 @@
#pragma once
+#include "Properties.h"
#include "utils/Macros.h"
#include <utils/Timers.h>
@@ -65,6 +66,7 @@
uint32_t jankFrameCount() const { return mJankFrameCount; }
nsecs_t statsStartTime() const { return mStatStartTime; }
uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; }
+ RenderPipelineType pipelineType() const { return mPipelineType; }
struct HistogramEntry {
uint32_t renderTimeMs;
@@ -103,6 +105,9 @@
uint32_t mTotalFrameCount;
uint32_t mJankFrameCount;
nsecs_t mStatStartTime;
+
+ // true if HWUI renders with Vulkan pipeline
+ RenderPipelineType mPipelineType;
};
// For testing
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 2314072..5790150 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -217,13 +217,16 @@
canvas->setMatrix(mMatrix);
switch (mType) {
case Type::Rect:
- canvas->clipRect(mRRect.rect(), mOp);
+ // Don't anti-alias rectangular clips
+ canvas->clipRect(mRRect.rect(), mOp, false);
break;
case Type::RRect:
- canvas->clipRRect(mRRect, mOp);
+ // Ensure rounded rectangular clips are anti-aliased
+ canvas->clipRRect(mRRect, mOp, true);
break;
case Type::Path:
- canvas->clipPath(mPath.value(), mOp);
+ // Ensure path clips are anti-aliased
+ canvas->clipPath(mPath.value(), mOp, true);
break;
}
}
@@ -392,7 +395,7 @@
bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) {
this->recordClip(*path, op);
- mCanvas->clipPath(*path, op);
+ mCanvas->clipPath(*path, op, true);
return !mCanvas->isClipEmpty();
}
@@ -746,7 +749,10 @@
glyphFunc(buffer.glyphs, buffer.pos);
sk_sp<SkTextBlob> textBlob(builder.make());
- mCanvas->drawTextBlob(textBlob, 0, 0, paintCopy);
+
+ apply_looper(&paintCopy, [&](const SkPaint& p) {
+ mCanvas->drawTextBlob(textBlob, 0, 0, p);
+ });
drawTextDecorations(x, y, totalAdvance, paintCopy);
}
@@ -783,8 +789,10 @@
xform[i - start].fTx = pos.x() - tan.y() * y - halfWidth * tan.x();
xform[i - start].fTy = pos.y() + tan.x() * y - halfWidth * tan.y();
}
-
- this->asSkCanvas()->drawTextBlob(builder.make(), 0, 0, paintCopy);
+ auto* finalCanvas = this->asSkCanvas();
+ apply_looper(&paintCopy, [&](const SkPaint& p) {
+ finalCanvas->drawTextBlob(builder.make(), 0, 0, paintCopy);
+ });
}
// ----------------------------------------------------------------------------
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
new file mode 100644
index 0000000..2c78b02
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ATraceMemoryDump.h"
+
+#include <utils/Trace.h>
+
+#include <cstring>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+// When purgeable is INVALID_TIME it won't be logged at all.
+#define INVALID_TIME -1
+
+/**
+ * Skia invokes the following SkTraceMemoryDump functions:
+ * 1. dumpNumericValue (dumpName, units="bytes", valueName="size")
+ * 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not
+ * invoke dumpStringValue]
+ * 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional]
+ * 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not
+ * invoke setMemoryBacking]
+ *
+ * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to
+ * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking.
+ * Only GPU Texture memory is tracked separately and everything else is grouped as one
+ * "GPU Memory" category.
+ */
+static std::unordered_map<const char*, const char*> sResourceMap = {
+ {"malloc", "Graphics CPU Memory"}, // taken from setMemoryBacking(backingType)
+ {"gl_texture", "Graphics Texture Memory"}, // taken from setMemoryBacking(backingType)
+ {"Texture",
+ "Graphics Texture Memory"}, // taken from dumpStringValue(value, valueName="type")
+ // Uncomment categories below to split "GPU Memory" into more brackets for debugging.
+ /*{"vk_buffer", "vk_buffer"},
+ {"gl_renderbuffer", "gl_renderbuffer"},
+ {"gl_buffer", "gl_buffer"},
+ {"RenderTarget", "RenderTarget"},
+ {"Stencil", "Stencil"},
+ {"Path Data", "Path Data"},
+ {"Buffer Object", "Buffer Object"},
+ {"Surface", "Surface"},*/
+};
+
+ATraceMemoryDump::ATraceMemoryDump() {
+ mLastDumpName.reserve(100);
+ mCategory.reserve(100);
+}
+
+void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName,
+ const char* units, uint64_t value) {
+ if (!strcmp(units, "bytes")) {
+ recordAndResetCountersIfNeeded(dumpName);
+ if (!strcmp(valueName, "size")) {
+ mLastDumpValue = value;
+ } else if (!strcmp(valueName, "purgeable_size")) {
+ mLastPurgeableDumpValue = value;
+ }
+ }
+}
+
+void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName,
+ const char* value) {
+ if (!strcmp(valueName, "type")) {
+ recordAndResetCountersIfNeeded(dumpName);
+ auto categoryIt = sResourceMap.find(value);
+ if (categoryIt != sResourceMap.end()) {
+ mCategory = categoryIt->second;
+ }
+ }
+}
+
+void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType,
+ const char* backingObjectId) {
+ recordAndResetCountersIfNeeded(dumpName);
+ auto categoryIt = sResourceMap.find(backingType);
+ if (categoryIt != sResourceMap.end()) {
+ mCategory = categoryIt->second;
+ }
+}
+
+/**
+ * startFrame is invoked before dumping anything. It resets counters from the previous frame.
+ * This is important, because if there is no new data for a given category trace would assume
+ * usage has not changed (instead of reporting 0).
+ */
+void ATraceMemoryDump::startFrame() {
+ resetCurrentCounter("");
+ for (auto& it : mCurrentValues) {
+ // Once a category is observed in at least one frame, it is always reported in subsequent
+ // frames (even if it is 0). Not logging a category to ATRACE would mean its value has not
+ // changed since the previous frame, which is not what we want.
+ it.second.time = 0;
+ // If purgeableTime is INVALID_TIME, then logTraces won't log it at all.
+ if (it.second.purgeableTime != INVALID_TIME) {
+ it.second.purgeableTime = 0;
+ }
+ }
+}
+
+/**
+ * logTraces reads from mCurrentValues and logs the counters with ATRACE.
+ */
+void ATraceMemoryDump::logTraces() {
+ // Accumulate data from last dumpName
+ recordAndResetCountersIfNeeded("");
+ for (auto& it : mCurrentValues) {
+ ATRACE_INT64(it.first.c_str(), it.second.time);
+ if (it.second.purgeableTime != INVALID_TIME) {
+ ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableTime);
+ }
+ }
+}
+
+/**
+ * recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and
+ * accumulates in mCurrentValues[category]. It makes provision to create a new category and track
+ * purgeable memory only if there is at least one observation.
+ * recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName
+ * is received.
+ */
+void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) {
+ if (!mLastDumpName.compare(dumpName)) {
+ // Still waiting for more data for current dumpName.
+ return;
+ }
+
+ // First invocation will have an empty mLastDumpName.
+ if (!mLastDumpName.empty()) {
+ // A new dumpName observed -> store the data already collected.
+ auto memoryCounter = mCurrentValues.find(mCategory);
+ if (memoryCounter != mCurrentValues.end()) {
+ memoryCounter->second.time += mLastDumpValue;
+ if (mLastPurgeableDumpValue != INVALID_TIME) {
+ if (memoryCounter->second.purgeableTime == INVALID_TIME) {
+ memoryCounter->second.purgeableTime = mLastPurgeableDumpValue;
+ } else {
+ memoryCounter->second.purgeableTime += mLastPurgeableDumpValue;
+ }
+ }
+ } else {
+ mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue};
+ }
+ }
+
+ // Reset counters and default category for the newly observed "dumpName".
+ resetCurrentCounter(dumpName);
+}
+
+void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) {
+ mLastDumpValue = 0;
+ mLastPurgeableDumpValue = INVALID_TIME;
+ mLastDumpName = dumpName;
+ // Categories not listed in sResourceMap are reported as "GPU memory"
+ mCategory = "GPU Memory";
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
new file mode 100644
index 0000000..aa5c401
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <SkString.h>
+#include <SkTraceMemoryDump.h>
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class ATraceMemoryDump : public SkTraceMemoryDump {
+public:
+ ATraceMemoryDump();
+ ~ATraceMemoryDump() override {}
+
+ void dumpNumericValue(const char* dumpName, const char* valueName, const char* units,
+ uint64_t value) override;
+
+ void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override;
+
+ LevelOfDetail getRequestedDetails() const override {
+ return SkTraceMemoryDump::kLight_LevelOfDetail;
+ }
+
+ bool shouldDumpWrappedObjects() const override { return false; }
+
+ void setMemoryBacking(const char* dumpName, const char* backingType,
+ const char* backingObjectId) override;
+
+ void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {}
+
+ void startFrame();
+
+ void logTraces();
+
+private:
+ std::string mLastDumpName;
+
+ uint64_t mLastDumpValue;
+
+ uint64_t mLastPurgeableDumpValue;
+
+ std::string mCategory;
+
+ struct TraceValue {
+ uint64_t time;
+ uint64_t purgeableTime;
+ };
+
+ // keys are define in sResourceMap
+ std::unordered_map<std::string, TraceValue> mCurrentValues;
+
+ void recordAndResetCountersIfNeeded(const char* dumpName);
+
+ void resetCurrentCounter(const char* dumpName);
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto
index 0cd5c62..dd5676c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -29,6 +29,11 @@
}
message GraphicsStatsProto {
+ enum PipelineType {
+ GL = 0;
+ VULKAN = 1;
+ }
+
// The package name of the app
optional string package_name = 1;
@@ -49,6 +54,9 @@
// The gpu frame time histogram for the package
repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+ // HWUI renders pipeline type: GL or Vulkan
+ optional PipelineType pipeline = 8;
}
message GraphicsStatsJankSummaryProto {
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index fad9440..7e8c96d 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -16,7 +16,6 @@
#include "renderstate/RenderState.h"
#include "renderthread/RenderThread.h"
-#include "GpuMemoryTracker.h"
namespace android {
namespace uirenderer {
@@ -25,15 +24,10 @@
mThreadId = pthread_self();
}
-void RenderState::onContextCreated() {
- GpuMemoryTracker::onGpuContextCreated();
-}
-
void RenderState::onContextDestroyed() {
for(auto callback : mContextCallbacks) {
callback->onContextDestroyed();
}
- GpuMemoryTracker::onGpuContextDestroyed();
}
void RenderState::postDecStrong(VirtualLightRefBase* object) {
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index ff5d02f..e08d32a 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -62,7 +62,6 @@
~RenderState() {}
// Context notifications are only to be triggered by renderthread::RenderThread
- void onContextCreated();
void onContextDestroyed();
std::set<IGpuContextCallback*> mContextCallbacks;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index eaed46c..d177855 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -20,10 +20,12 @@
#include "Layer.h"
#include "Properties.h"
#include "RenderThread.h"
+#include "pipeline/skia/ATraceMemoryDump.h"
#include "pipeline/skia/ShaderCache.h"
#include "pipeline/skia/SkiaMemoryTracer.h"
#include "renderstate/RenderState.h"
#include "thread/CommonPool.h"
+#include <utils/Trace.h>
#include <GrContextOptions.h>
#include <SkExecutor.h>
@@ -184,6 +186,18 @@
gpuTracer.logTotals(log);
}
+void CacheManager::onFrameCompleted() {
+ if (ATRACE_ENABLED()) {
+ static skiapipeline::ATraceMemoryDump tracer;
+ tracer.startFrame();
+ SkGraphics::DumpMemoryStatistics(&tracer);
+ if (mGrContext) {
+ mGrContext->dumpMemoryStatistics(&tracer);
+ }
+ tracer.logTraces();
+ }
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 968251e..b009cc4 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -50,6 +50,7 @@
size_t getCacheSize() const { return mMaxResourceBytes; }
size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
+ void onFrameCompleted();
private:
friend class RenderThread;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 8490221..5993e17 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -16,7 +16,6 @@
#include "CanvasContext.h"
-#include <GpuMemoryTracker.h>
#include <apex/window.h>
#include <fcntl.h>
#include <strings.h>
@@ -558,7 +557,7 @@
mJankTracker.finishGpuDraw(*forthBehind);
}
- GpuMemoryTracker::onFrameCompleted();
+ mRenderThread.cacheManager().onFrameCompleted();
}
// Called by choreographer to do an RT-driven animation
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index d78f641..cae3e3b 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -270,7 +270,6 @@
}
mGrContext = std::move(context);
if (mGrContext) {
- mRenderState->onContextCreated();
DeviceInfo::setMaxTextureSize(mGrContext->maxRenderTargetSize());
}
}
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 12c5b83..c418617 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -16,24 +16,28 @@
#include "GraphicsStatsService.h"
-#include "JankTracker.h"
-#include "protos/graphicsstats.pb.h"
-
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-#include <log/log.h>
-
#include <errno.h>
#include <fcntl.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <inttypes.h>
+#include <log/log.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "JankTracker.h"
+#include "protos/graphicsstats.pb.h"
+
namespace android {
namespace uirenderer {
using namespace google::protobuf;
+using namespace uirenderer::protos;
constexpr int32_t sCurrentFileVersion = 1;
constexpr int32_t sHeaderSize = 4;
@@ -42,9 +46,9 @@
constexpr int sHistogramSize = ProfileData::HistogramSize();
constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize();
-static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto,
- const std::string& package, int64_t versionCode,
- int64_t startTime, int64_t endTime, const ProfileData* data);
+static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
+ int64_t versionCode, int64_t startTime, int64_t endTime,
+ const ProfileData* data);
static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
class FileDescriptor {
@@ -57,7 +61,7 @@
}
}
bool valid() { return mFd != -1; }
- operator int() { return mFd; } // NOLINT(google-explicit-constructor)
+ operator int() { return mFd; } // NOLINT(google-explicit-constructor)
private:
int mFd;
@@ -167,6 +171,8 @@
}
proto->set_package_name(package);
proto->set_version_code(versionCode);
+ proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
+ GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN);
auto summary = proto->mutable_summary();
summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
@@ -179,8 +185,8 @@
summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
data->jankTypeCount(kSlowSync));
summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
- summary->set_missed_deadline_count(summary->missed_deadline_count()
- + data->jankTypeCount(kMissedDeadline));
+ summary->set_missed_deadline_count(summary->missed_deadline_count() +
+ data->jankTypeCount(kMissedDeadline));
bool creatingHistogram = false;
if (proto->histogram_size() == 0) {
@@ -365,17 +371,69 @@
class GraphicsStatsService::Dump {
public:
- Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
+ Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {
+ if (mFd == -1 && mType == DumpType::Protobuf) {
+ mType = DumpType::ProtobufStatsd;
+ }
+ }
int fd() { return mFd; }
DumpType type() { return mType; }
protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
+ void mergeStat(const protos::GraphicsStatsProto& stat);
+ void updateProto();
private:
+ // use package name and app version for a key
+ typedef std::pair<std::string, int64_t> DumpKey;
+
+ std::map<DumpKey, protos::GraphicsStatsProto> mStats;
int mFd;
DumpType mType;
protos::GraphicsStatsServiceDumpProto mProto;
};
+void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
+ auto dumpKey = std::make_pair(stat.package_name(), stat.version_code());
+ auto findIt = mStats.find(dumpKey);
+ if (findIt == mStats.end()) {
+ mStats[dumpKey] = stat;
+ } else {
+ auto summary = findIt->second.mutable_summary();
+ summary->set_total_frames(summary->total_frames() + stat.summary().total_frames());
+ summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames());
+ summary->set_missed_vsync_count(summary->missed_vsync_count() +
+ stat.summary().missed_vsync_count());
+ summary->set_high_input_latency_count(summary->high_input_latency_count() +
+ stat.summary().high_input_latency_count());
+ summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
+ stat.summary().slow_ui_thread_count());
+ summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
+ stat.summary().slow_bitmap_upload_count());
+ summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count());
+ summary->set_missed_deadline_count(summary->missed_deadline_count() +
+ stat.summary().missed_deadline_count());
+ for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) {
+ auto bucket = findIt->second.mutable_histogram(bucketIndex);
+ bucket->set_frame_count(bucket->frame_count() +
+ stat.histogram(bucketIndex).frame_count());
+ }
+ for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size();
+ bucketIndex++) {
+ auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex);
+ bucket->set_frame_count(bucket->frame_count() +
+ stat.gpu_histogram(bucketIndex).frame_count());
+ }
+ findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start()));
+ findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end()));
+ }
+}
+
+void GraphicsStatsService::Dump::updateProto() {
+ for (auto& stat : mStats) {
+ mProto.add_stats()->CopyFrom(stat.second);
+ }
+}
+
GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
return new Dump(outFd, type);
}
@@ -396,8 +454,9 @@
path.empty() ? "<empty>" : path.c_str(), data);
return;
}
-
- if (dump->type() == DumpType::Protobuf) {
+ if (dump->type() == DumpType::ProtobufStatsd) {
+ dump->mergeStat(statsProto);
+ } else if (dump->type() == DumpType::Protobuf) {
dump->proto().add_stats()->CopyFrom(statsProto);
} else {
dumpAsTextToFd(&statsProto, dump->fd());
@@ -409,7 +468,9 @@
if (!parseFromFile(path, &statsProto)) {
return;
}
- if (dump->type() == DumpType::Protobuf) {
+ if (dump->type() == DumpType::ProtobufStatsd) {
+ dump->mergeStat(statsProto);
+ } else if (dump->type() == DumpType::Protobuf) {
dump->proto().add_stats()->CopyFrom(statsProto);
} else {
dumpAsTextToFd(&statsProto, dump->fd());
@@ -424,5 +485,79 @@
delete dump;
}
+class MemOutputStreamLite : public io::ZeroCopyOutputStream {
+public:
+ explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {}
+ virtual ~MemOutputStreamLite() {}
+
+ virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); }
+
+ virtual void BackUp(int count) override { mImpl.BackUp(count); }
+
+ virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
+
+ bool Flush() { return mImpl.Flush(); }
+
+ void copyData(const DumpMemoryFn& reader, void* param1, void* param2) {
+ int bufferOffset = 0;
+ int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize;
+ int totalDataLeft = totalSize;
+ for (auto& it : mCopyAdapter.mBuffers) {
+ int bufferSize = std::min(totalDataLeft, (int)it.size()); // last buffer is not full
+ reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2);
+ bufferOffset += bufferSize;
+ totalDataLeft -= bufferSize;
+ }
+ }
+
+private:
+ struct MemAdapter : public io::CopyingOutputStream {
+ // Data is stored in an array of buffers.
+ // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer.
+ std::vector<std::vector<unsigned char>> mBuffers;
+ int mBuffersSize = 0; // total bytes allocated in mBuffers
+ int mCurrentBufferUnusedSize = 0; // unused bytes in the last buffer mBuffers.back()
+ unsigned char* mCurrentBuffer = nullptr; // pointer to next free byte in mBuffers.back()
+
+ explicit MemAdapter() {}
+ virtual ~MemAdapter() {}
+
+ virtual bool Write(const void* buffer, int size) override {
+ while (size > 0) {
+ if (0 == mCurrentBufferUnusedSize) {
+ mCurrentBufferUnusedSize =
+ std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000);
+ mBuffers.emplace_back();
+ mBuffers.back().resize(mCurrentBufferUnusedSize);
+ mCurrentBuffer = mBuffers.back().data();
+ mBuffersSize += mCurrentBufferUnusedSize;
+ }
+ int dataMoved = std::min(mCurrentBufferUnusedSize, size);
+ memcpy(mCurrentBuffer, buffer, dataMoved);
+ mCurrentBufferUnusedSize -= dataMoved;
+ mCurrentBuffer += dataMoved;
+ buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved;
+ size -= dataMoved;
+ }
+ return true;
+ }
+ };
+
+ MemOutputStreamLite::MemAdapter mCopyAdapter;
+ io::CopyingOutputStreamAdaptor mImpl;
+};
+
+void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+ void* param2) {
+ MemOutputStreamLite stream;
+ dump->updateProto();
+ bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush();
+ delete dump;
+ if (!success) {
+ return;
+ }
+ stream.copyData(reader, param1, param2);
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
index 389f599..4bed9633 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -27,6 +27,9 @@
class GraphicsStatsProto;
}
+typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize,
+ void* param1, void* param2);
+
/*
* The exported entry points used by GraphicsStatsService.java in f/b/services/core
*
@@ -40,6 +43,7 @@
enum class DumpType {
Text,
Protobuf,
+ ProtobufStatsd,
};
ANDROID_API static void saveBuffer(const std::string& path, const std::string& package,
@@ -52,6 +56,8 @@
int64_t startTime, int64_t endTime, const ProfileData* data);
ANDROID_API static void addToDump(Dump* dump, const std::string& path);
ANDROID_API static void finishDump(Dump* dump);
+ ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+ void* param2);
// Visible for testing
static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output);
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index 0a54aca..e075d80 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -25,16 +25,16 @@
static const int IDENT_DISPLAYEVENT = 1;
static android::DisplayInfo DUMMY_DISPLAY{
- 1080, // w
- 1920, // h
- 320.0, // xdpi
- 320.0, // ydpi
- 60.0, // fps
- 2.0, // density
- 0, // orientation
- false, // secure?
- 0, // appVsyncOffset
- 0, // presentationDeadline
+ 1080, // w
+ 1920, // h
+ 320.0, // xdpi
+ 320.0, // ydpi
+ 60.0, // fps
+ 2.0, // density
+ ui::ROTATION_0, // orientation
+ false, // secure?
+ 0, // appVsyncOffset
+ 0, // presentationDeadline
};
DisplayInfo getInternalDisplay() {
diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
deleted file mode 100644
index dac888c..0000000
--- a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <GpuMemoryTracker.h>
-#include <gtest/gtest.h>
-
-#include "renderthread/EglManager.h"
-#include "renderthread/RenderThread.h"
-#include "tests/common/TestUtils.h"
-
-#include <utils/StrongPointer.h>
-
-using namespace android;
-using namespace android::uirenderer;
-using namespace android::uirenderer::renderthread;
-
-class TestGPUObject : public GpuMemoryTracker {
-public:
- TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {}
-
- void changeSize(int newSize) { notifySizeChanged(newSize); }
-};
-
-// Other tests may have created a renderthread and EGL context.
-// This will destroy the EGLContext on RenderThread if it exists so that the
-// current thread can spoof being a GPU thread
-static void destroyEglContext() {
- if (TestUtils::isRenderThreadRunning()) {
- TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyRenderingContext(); });
- }
-}
-
-TEST(GpuMemoryTracker, sizeCheck) {
- destroyEglContext();
-
- GpuMemoryTracker::onGpuContextCreated();
- ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
- ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
- {
- TestGPUObject myObj;
- ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
- myObj.changeSize(500);
- ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
- myObj.changeSize(1000);
- ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
- myObj.changeSize(300);
- ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
- }
- ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
- ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
- GpuMemoryTracker::onGpuContextDestroyed();
-}
diff --git a/media/OWNERS b/media/OWNERS
index 8bd037a..be60583 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -5,6 +5,7 @@
etalvala@google.com
gkasten@google.com
hdmoon@google.com
+hkuang@google.com
hunga@google.com
insun@google.com
jaewan@google.com
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 8293b5f..cb132f5 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.util.SparseIntArray;
import java.lang.annotation.Retention;
@@ -127,6 +128,23 @@
* A device type describing a Hearing Aid.
*/
public static final int TYPE_HEARING_AID = 23;
+ /**
+ * A device type describing the speaker system (i.e. a mono speaker or stereo speakers) built
+ * in a device, that is specifically tuned for outputting sounds like notifications and alarms
+ * (i.e. sounds the user couldn't necessarily anticipate).
+ * <p>Note that this physical audio device may be the same as {@link #TYPE_BUILTIN_SPEAKER}
+ * but is driven differently to safely accommodate the different use case.</p>
+ */
+ public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24;
+ /**
+ * @hide
+ * A device type for rerouting audio within the Android framework between mixes and
+ * system applications. Typically created when using
+ * {@link android.media.audiopolicy.AudioPolicy} for mixes created with the
+ * {@link android.media.audiopolicy.AudioMix#ROUTE_FLAG_RENDER} flag.
+ */
+ @SystemApi
+ public static final int TYPE_REMOTE_SUBMIX = 25;
/** @hide */
@IntDef(flag = false, prefix = "TYPE", value = {
@@ -228,6 +246,8 @@
case TYPE_IP:
case TYPE_BUS:
case TYPE_HEARING_AID:
+ case TYPE_BUILTIN_SPEAKER_SAFE:
+ case TYPE_REMOTE_SUBMIX:
return true;
default:
return false;
@@ -253,6 +273,7 @@
case TYPE_LINE_DIGITAL:
case TYPE_IP:
case TYPE_BUS:
+ case TYPE_REMOTE_SUBMIX:
return true;
default:
return false;
@@ -449,6 +470,9 @@
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_IP, TYPE_IP);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BUS, TYPE_BUS);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HEARING_AID, TYPE_HEARING_AID);
+ INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPEAKER_SAFE,
+ TYPE_BUILTIN_SPEAKER_SAFE);
+ INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -468,10 +492,7 @@
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, TYPE_BLUETOOTH_A2DP);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUS, TYPE_BUS);
-
- // not covered here, legacy
- //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
- //AudioSystem.DEVICE_IN_REMOTE_SUBMIX
+ INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
// privileges mapping to output device
EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
@@ -498,6 +519,9 @@
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_OUT_IP);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_OUT_BUS);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_HEARING_AID, AudioSystem.DEVICE_OUT_HEARING_AID);
+ EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUILTIN_SPEAKER_SAFE,
+ AudioSystem.DEVICE_OUT_SPEAKER_SAFE);
+ EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX);
}
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e410882..1b870e8 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4331,6 +4331,26 @@
}
}
+ /**
+ * @hide
+ * Get the audio devices that would be used for the routing of the given audio attributes.
+ * @param attributes the {@link AudioAttributes} for which the routing is being queried
+ * @return an empty list if there was an issue with the request, a list of audio devices
+ * otherwise (typically one device, except for duplicated paths).
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public @NonNull List<AudioDeviceAddress> getDevicesForAttributes(
+ @NonNull AudioAttributes attributes) {
+ Objects.requireNonNull(attributes);
+ final IAudioService service = getService();
+ try {
+ return service.getDevicesForAttributes(attributes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Indicate wired accessory connection state change.
* @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx)
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index e584add..fe57e71 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -33,6 +33,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET
@@ -1077,6 +1078,41 @@
@UnsupportedAppUsage
public static native int getDevicesForStream(int stream);
+ /**
+ * Do not use directly, see {@link AudioManager#getDevicesForAttributes(AudioAttributes)}
+ * Get the audio devices that would be used for the routing of the given audio attributes.
+ * @param attributes the {@link AudioAttributes} for which the routing is being queried
+ * @return an empty list if there was an issue with the request, a list of audio devices
+ * otherwise (typically one device, except for duplicated paths).
+ */
+ public static @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+ @NonNull AudioAttributes attributes) {
+ Objects.requireNonNull(attributes);
+ final AudioDeviceAddress[] devices = new AudioDeviceAddress[MAX_DEVICE_ROUTING];
+ final int res = getDevicesForAttributes(attributes, devices);
+ final ArrayList<AudioDeviceAddress> routeDevices = new ArrayList<>();
+ if (res != SUCCESS) {
+ Log.e(TAG, "error " + res + " in getDevicesForAttributes for " + attributes);
+ return routeDevices;
+ }
+
+ for (AudioDeviceAddress device : devices) {
+ if (device != null) {
+ routeDevices.add(device);
+ }
+ }
+ return routeDevices;
+ }
+
+ /**
+ * Maximum number of audio devices a track is ever routed to, determines the size of the
+ * array passed to {@link #getDevicesForAttributes(AudioAttributes, AudioDeviceAddress[])}
+ */
+ private static final int MAX_DEVICE_ROUTING = 4;
+
+ private static native int getDevicesForAttributes(@NonNull AudioAttributes aa,
+ @NonNull AudioDeviceAddress[] devices);
+
/** @hide returns true if master mono is enabled. */
public static native boolean getMasterMono();
/** @hide enables or disables the master mono mode. */
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index ad7335e..8e8385d 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -272,6 +272,8 @@
AudioDeviceAddress getPreferredDeviceForStrategy(in int strategy);
+ List<AudioDeviceAddress> getDevicesForAttributes(in AudioAttributes attributes);
+
// WARNING: read warning at top of file, new methods that need to be used by native
// code via IAudioManager.h need to be added to the top section.
}
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index 28bf84d..5dd0b1c 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -24,7 +24,8 @@
*/
oneway interface IMediaRoute2Provider {
void setClient(IMediaRoute2ProviderClient client);
- void requestCreateSession(String packageName, String routeId, String routeType, long requestId);
+ void requestCreateSession(String packageName, String routeId,
+ String routeFeature, long requestId);
void releaseSession(String sessionId);
void selectRoute(String sessionId, String routeId);
diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl
index bcb2336..0fccb3a 100644
--- a/media/java/android/media/IMediaRoute2ProviderClient.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl
@@ -18,15 +18,17 @@
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2Info;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.Bundle;
/**
* @hide
*/
oneway interface IMediaRoute2ProviderClient {
- void updateState(in MediaRoute2ProviderInfo providerInfo,
- in List<RouteSessionInfo> sessionInfos);
- void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, long requestId);
- void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
+ // TODO: Change it to updateRoutes?
+ void updateState(in MediaRoute2ProviderInfo providerInfo);
+ void notifySessionCreated(in RoutingSessionInfo sessionInfo, long requestId);
+ void notifySessionCreationFailed(long requestId);
+ void notifySessionUpdated(in RoutingSessionInfo sessionInfo);
+ void notifySessionReleased(in RoutingSessionInfo sessionInfo);
}
diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl
index f90c7c5..bc7ebea 100644
--- a/media/java/android/media/IMediaRouter2Client.aidl
+++ b/media/java/android/media/IMediaRouter2Client.aidl
@@ -17,7 +17,7 @@
package android.media;
import android.media.MediaRoute2Info;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.Bundle;
/**
@@ -28,7 +28,7 @@
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
- void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, int requestId);
- void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
- void notifySessionReleased(in RouteSessionInfo sessionInfo);
+ void notifySessionCreated(in @nullable RoutingSessionInfo sessionInfo, int requestId);
+ void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
+ void notifySessionReleased(in RoutingSessionInfo sessionInfo);
}
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index e8af21e..e9add17 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -24,7 +24,7 @@
*/
oneway interface IMediaRouter2Manager {
void notifyRouteSelected(String packageName, in MediaRoute2Info route);
- void notifyRouteTypesChanged(String packageName, in List<String> routeTypes);
+ void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index b573f64..3cdaa07 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -22,8 +22,8 @@
import android.media.IMediaRouterClient;
import android.media.MediaRoute2Info;
import android.media.MediaRouterClientState;
-import android.media.RouteDiscoveryRequest;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
/**
* {@hide}
@@ -52,8 +52,8 @@
void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction);
void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route,
- String routeType, int requestId);
- void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryRequest request);
+ String routeFeature, int requestId);
+ void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryPreference preference);
void selectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
void deselectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
void transferToRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
@@ -70,5 +70,5 @@
void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
in MediaRoute2Info route, int direction);
- List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
+ List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 1ed53d9..021e23e 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -36,7 +36,6 @@
/**
* Describes the properties of a route.
- * @hide
*/
public final class MediaRoute2Info implements Parcelable {
@NonNull
@@ -56,7 +55,7 @@
@IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
CONNECTION_STATE_CONNECTED})
@Retention(RetentionPolicy.SOURCE)
- private @interface ConnectionState {}
+ public @interface ConnectionState {}
/**
* The default connection state indicating the route is disconnected.
@@ -99,16 +98,15 @@
/** @hide */
@IntDef({
- DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV,
- DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+ DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_REMOTE_TV,
+ DEVICE_TYPE_REMOTE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
@Retention(RetentionPolicy.SOURCE)
- private @interface DeviceType {}
+ public @interface DeviceType {}
/**
* The default receiver device type of the route indicating the type is unknown.
*
* @see #getDeviceType
- * @hide
*/
public static final int DEVICE_TYPE_UNKNOWN = 0;
@@ -118,7 +116,7 @@
*
* @see #getDeviceType
*/
- public static final int DEVICE_TYPE_TV = 1;
+ public static final int DEVICE_TYPE_REMOTE_TV = 1;
/**
* A receiver device type of the route indicating the presentation of the media is happening
@@ -126,14 +124,13 @@
*
* @see #getDeviceType
*/
- public static final int DEVICE_TYPE_SPEAKER = 2;
+ public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2;
/**
* A receiver device type of the route indicating the presentation of the media is happening
* on a bluetooth device such as a bluetooth speaker.
*
* @see #getDeviceType
- * @hide
*/
public static final int DEVICE_TYPE_BLUETOOTH = 3;
@@ -152,7 +149,7 @@
@Nullable
final String mClientPackageName;
@NonNull
- final List<String> mRouteTypes;
+ final List<String> mFeatures;
final int mVolume;
final int mVolumeMax;
final int mVolumeHandling;
@@ -168,7 +165,7 @@
mConnectionState = builder.mConnectionState;
mIconUri = builder.mIconUri;
mClientPackageName = builder.mClientPackageName;
- mRouteTypes = builder.mRouteTypes;
+ mFeatures = builder.mFeatures;
mVolume = builder.mVolume;
mVolumeMax = builder.mVolumeMax;
mVolumeHandling = builder.mVolumeHandling;
@@ -184,7 +181,7 @@
mConnectionState = in.readInt();
mIconUri = in.readParcelable(null);
mClientPackageName = in.readString();
- mRouteTypes = in.createStringArrayList();
+ mFeatures = in.createStringArrayList();
mVolume = in.readInt();
mVolumeMax = in.readInt();
mVolumeHandling = in.readInt();
@@ -223,7 +220,7 @@
&& (mConnectionState == other.mConnectionState)
&& Objects.equals(mIconUri, other.mIconUri)
&& Objects.equals(mClientPackageName, other.mClientPackageName)
- && Objects.equals(mRouteTypes, other.mRouteTypes)
+ && Objects.equals(mFeatures, other.mFeatures)
&& (mVolume == other.mVolume)
&& (mVolumeMax == other.mVolumeMax)
&& (mVolumeHandling == other.mVolumeHandling)
@@ -235,7 +232,7 @@
@Override
public int hashCode() {
return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri,
- mRouteTypes, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
+ mFeatures, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
}
/**
@@ -245,7 +242,7 @@
* In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
* can be different from what was set in {@link MediaRoute2ProviderService}.
*
- * @see Builder#setId(String)
+ * @see Builder#Builder(String, CharSequence)
*/
@NonNull
public String getId() {
@@ -257,7 +254,7 @@
}
/**
- * Gets the original id set by {@link Builder#setId(String)}.
+ * Gets the original id set by {@link Builder#Builder(String, CharSequence)}.
* @hide
*/
@NonNull
@@ -324,16 +321,16 @@
* Gets the supported categories of the route.
*/
@NonNull
- public List<String> getRouteTypes() {
- return mRouteTypes;
+ public List<String> getFeatures() {
+ return mFeatures;
}
- //TODO: once device types are confirmed, reflect those into the comment.
/**
* Gets the type of the receiver device associated with this route.
*
* @return The type of the receiver device associated with this route:
- * {@link #DEVICE_TYPE_TV} or {@link #DEVICE_TYPE_SPEAKER}.
+ * {@link #DEVICE_TYPE_REMOTE_TV}, {@link #DEVICE_TYPE_REMOTE_SPEAKER},
+ * {@link #DEVICE_TYPE_BLUETOOTH}.
*/
@DeviceType
public int getDeviceType() {
@@ -369,15 +366,15 @@
}
/**
- * Returns if the route contains at least one of the specified route types.
+ * Returns if the route has at least one of the specified route features.
*
- * @param routeTypes the list of route types to consider
- * @return true if the route contains at least one type in the list
+ * @param features the list of route features to consider
+ * @return true if the route has at least one feature in the list
*/
- public boolean containsRouteTypes(@NonNull Collection<String> routeTypes) {
- Objects.requireNonNull(routeTypes, "routeTypes must not be null");
- for (String routeType : routeTypes) {
- if (getRouteTypes().contains(routeType)) {
+ public boolean hasAnyFeatures(@NonNull Collection<String> features) {
+ Objects.requireNonNull(features, "features must not be null");
+ for (String feature : features) {
+ if (getFeatures().contains(feature)) {
return true;
}
}
@@ -398,7 +395,7 @@
dest.writeInt(mConnectionState);
dest.writeParcelable(mIconUri, flags);
dest.writeString(mClientPackageName);
- dest.writeStringList(mRouteTypes);
+ dest.writeStringList(mFeatures);
dest.writeInt(mVolume);
dest.writeInt(mVolumeMax);
dest.writeInt(mVolumeHandling);
@@ -428,15 +425,15 @@
* Builder for {@link MediaRoute2Info media route info}.
*/
public static final class Builder {
- String mId;
+ final String mId;
String mProviderId;
- CharSequence mName;
+ final CharSequence mName;
CharSequence mDescription;
@ConnectionState
int mConnectionState;
Uri mIconUri;
String mClientPackageName;
- List<String> mRouteTypes;
+ List<String> mFeatures;
int mVolume;
int mVolumeMax;
int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
@@ -444,27 +441,43 @@
int mDeviceType = DEVICE_TYPE_UNKNOWN;
Bundle mExtras;
+ /**
+ * Constructor for builder to create {@link MediaRoute2Info}.
+ * <p>
+ * In order to ensure ID uniqueness, the {@link MediaRoute2Info#getId() ID} of a route info
+ * obtained from {@link MediaRouter2} can be different from what was set in
+ * {@link MediaRoute2ProviderService}.
+ * </p>
+ * @param id
+ * @param name
+ */
public Builder(@NonNull String id, @NonNull CharSequence name) {
- setId(id);
- setName(name);
- mRouteTypes = new ArrayList<>();
+ if (TextUtils.isEmpty(id)) {
+ throw new IllegalArgumentException("id must not be empty");
+ }
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name must not be empty");
+ }
+ mId = id;
+ mName = name;
+ mFeatures = new ArrayList<>();
}
public Builder(@NonNull MediaRoute2Info routeInfo) {
if (routeInfo == null) {
throw new IllegalArgumentException("route info must not be null");
}
+ mId = routeInfo.mId;
+ mName = routeInfo.mName;
- setId(routeInfo.mId);
if (!TextUtils.isEmpty(routeInfo.mProviderId)) {
setProviderId(routeInfo.mProviderId);
}
- setName(routeInfo.mName);
mDescription = routeInfo.mDescription;
mConnectionState = routeInfo.mConnectionState;
mIconUri = routeInfo.mIconUri;
setClientPackageName(routeInfo.mClientPackageName);
- setRouteTypes(routeInfo.mRouteTypes);
+ mFeatures = new ArrayList<>(routeInfo.mFeatures);
setVolume(routeInfo.mVolume);
setVolumeMax(routeInfo.mVolumeMax);
setVolumeHandling(routeInfo.mVolumeHandling);
@@ -475,26 +488,6 @@
}
/**
- * Sets the unique id of the route. The value given here must be unique for each of your
- * route.
- * <p>
- * In order to ensure uniqueness in {@link MediaRouter2} side, the value of
- * {@link MediaRoute2Info#getId()} can be different from what was set in
- * {@link MediaRoute2ProviderService}.
- * </p>
- *
- * @see MediaRoute2Info#getId()
- */
- @NonNull
- public Builder setId(@NonNull String id) {
- if (TextUtils.isEmpty(id)) {
- throw new IllegalArgumentException("id must not be null or empty");
- }
- mId = id;
- return this;
- }
-
- /**
* Sets the provider id of the route.
* @hide
*/
@@ -508,20 +501,10 @@
}
/**
- * Sets the user-visible name of the route.
- */
- @NonNull
- public Builder setName(@NonNull CharSequence name) {
- Objects.requireNonNull(name, "name must not be null");
- mName = name;
- return this;
- }
-
- /**
* Sets the user-visible description of the route.
*/
@NonNull
- public Builder setDescription(@Nullable String description) {
+ public Builder setDescription(@Nullable CharSequence description) {
mDescription = description;
return this;
}
@@ -569,35 +552,35 @@
}
/**
- * Sets the types of the route.
+ * Clears the features of the route.
*/
@NonNull
- public Builder setRouteTypes(@NonNull Collection<String> routeTypes) {
- mRouteTypes = new ArrayList<>();
- return addRouteTypes(routeTypes);
+ public Builder clearFeatures() {
+ mFeatures = new ArrayList<>();
+ return this;
}
/**
- * Adds types for the route.
+ * Adds features for the route.
*/
@NonNull
- public Builder addRouteTypes(@NonNull Collection<String> routeTypes) {
- Objects.requireNonNull(routeTypes, "routeTypes must not be null");
- for (String routeType: routeTypes) {
- addRouteType(routeType);
+ public Builder addFeatures(@NonNull Collection<String> features) {
+ Objects.requireNonNull(features, "features must not be null");
+ for (String feature : features) {
+ addFeature(feature);
}
return this;
}
/**
- * Add a type for the route.
+ * Adds a feature for the route.
*/
@NonNull
- public Builder addRouteType(@NonNull String routeType) {
- if (TextUtils.isEmpty(routeType)) {
- throw new IllegalArgumentException("routeType must not be null or empty");
+ public Builder addFeature(@NonNull String feature) {
+ if (TextUtils.isEmpty(feature)) {
+ throw new IllegalArgumentException("feature must not be null or empty");
}
- mRouteTypes.add(routeType);
+ mFeatures.add(feature);
return this;
}
@@ -642,7 +625,7 @@
*/
@NonNull
public Builder setExtras(@Nullable Bundle extras) {
- mExtras = extras;
+ mExtras = new Bundle(extras);
return this;
}
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java
index e2f246c..c9a2ec7 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -93,6 +93,9 @@
/**
* Gets the route for the given route id or null if no matching route exists.
+ * Please note that id should be original id.
+ *
+ * @see MediaRoute2Info#getOriginalId()
*/
@Nullable
public MediaRoute2Info getRoute(@NonNull String routeId) {
@@ -168,7 +171,7 @@
MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue())
.setProviderId(mUniqueId)
.build();
- newRoutes.put(routeWithProviderId.getId(), routeWithProviderId);
+ newRoutes.put(routeWithProviderId.getOriginalId(), routeWithProviderId);
}
mRoutes.clear();
@@ -183,14 +186,14 @@
public Builder addRoute(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
- if (mRoutes.containsValue(route)) {
- throw new IllegalArgumentException("route descriptor already added");
+ if (mRoutes.containsKey(route.getOriginalId())) {
+ throw new IllegalArgumentException("A route with the same id is already added");
}
if (mUniqueId != null) {
- mRoutes.put(route.getId(),
+ mRoutes.put(route.getOriginalId(),
new MediaRoute2Info.Builder(route).setProviderId(mUniqueId).build());
} else {
- mRoutes.put(route.getId(), route);
+ mRoutes.put(route.getOriginalId(), route);
}
return this;
}
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 24b65ba..6d9aea5 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -36,18 +36,38 @@
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * @hide
+ * Base class for media route provider services.
+ * <p>
+ * The system media router service will bind to media route provider services when a
+ * {@link RouteDiscoveryPreference discovery preference} is registered via
+ * a {@link MediaRouter2 media router} by an application.
+ * </p><p>
+ * To implement your own media route provider service, extend this class and
+ * override {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} to publish
+ * {@link MediaRoute2Info routes}.
+ * </p>
*/
public abstract class MediaRoute2ProviderService extends Service {
private static final String TAG = "MR2ProviderService";
public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
+ /**
+ * The request ID to pass {@link #notifySessionCreated(RoutingSessionInfo, long)}
+ * when {@link MediaRoute2ProviderService} created a session although there was no creation
+ * request.
+ *
+ * @see #notifySessionCreated(RoutingSessionInfo, long)
+ * @hide
+ */
+ public static final long REQUEST_ID_UNKNOWN = 0;
+
private final Handler mHandler;
private final Object mSessionLock = new Object();
private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false);
@@ -56,13 +76,14 @@
private MediaRoute2ProviderInfo mProviderInfo;
@GuardedBy("mSessionLock")
- private ArrayMap<String, RouteSessionInfo> mSessionInfo = new ArrayMap<>();
+ private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>();
public MediaRoute2ProviderService() {
mHandler = new Handler(Looper.getMainLooper());
}
@Override
+ @NonNull
public IBinder onBind(@NonNull Intent intent) {
//TODO: Allow binding from media router service only?
if (SERVICE_INTERFACE.equals(intent.getAction())) {
@@ -79,6 +100,7 @@
*
* @param routeId the id of the target route
* @param request the media control request intent
+ * @hide
*/
//TODO: Discuss what to use for request (e.g., Intent? Request class?)
public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request);
@@ -88,6 +110,7 @@
*
* @param routeId the id of the route
* @param volume the target volume
+ * @hide
*/
public abstract void onSetVolume(@NonNull String routeId, int volume);
@@ -96,6 +119,7 @@
*
* @param routeId id of the route
* @param delta the delta to add to the current volume
+ * @hide
*/
public abstract void onUpdateVolume(@NonNull String routeId, int delta);
@@ -104,10 +128,11 @@
*
* @param sessionId id of the session
* @return information of the session with the given id.
- * null if the session is destroyed or id is not valid.
+ * null if the session is released or ID is not valid.
+ * @hide
*/
@Nullable
- public final RouteSessionInfo getSessionInfo(@NonNull String sessionId) {
+ public final RoutingSessionInfo getSessionInfo(@NonNull String sessionId) {
if (TextUtils.isEmpty(sessionId)) {
throw new IllegalArgumentException("sessionId must not be empty");
}
@@ -117,102 +142,50 @@
}
/**
- * Gets the list of {@link RouteSessionInfo session info} that the provider service maintains.
+ * Gets the list of {@link RoutingSessionInfo session info} that the provider service maintains.
+ * @hide
*/
@NonNull
- public final List<RouteSessionInfo> getAllSessionInfo() {
+ public final List<RoutingSessionInfo> getAllSessionInfo() {
synchronized (mSessionLock) {
return new ArrayList<>(mSessionInfo.values());
}
}
/**
- * Updates the information of a session.
- * If the session is destroyed or not created before, it will be ignored.
- * A session will be destroyed if it has no selected route.
- * Call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify clients of
- * session info changes.
- *
- * @param sessionInfo new session information
- * @see #notifySessionCreated(RouteSessionInfo, long)
- */
- public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) {
- Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
- String sessionId = sessionInfo.getId();
- if (sessionInfo.getSelectedRoutes().isEmpty()) {
- releaseSession(sessionId);
- return;
- }
-
- synchronized (mSessionLock) {
- if (mSessionInfo.containsKey(sessionId)) {
- mSessionInfo.put(sessionId, sessionInfo);
- schedulePublishState();
- } else {
- Log.w(TAG, "Ignoring unknown session info.");
- return;
- }
- }
- }
-
- /**
- * Notifies the session is changed.
- *
- * TODO: This method is temporary, only created for tests. Remove when the alternative is ready.
- * @hide
- */
- public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) {
- Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-
- String sessionId = sessionInfo.getId();
- synchronized (mSessionLock) {
- if (mSessionInfo.containsKey(sessionId)) {
- mSessionInfo.put(sessionId, sessionInfo);
- } else {
- Log.w(TAG, "Ignoring unknown session info.");
- return;
- }
- }
-
- if (mClient == null) {
- return;
- }
- try {
- mClient.notifySessionInfoChanged(sessionInfo);
- } catch (RemoteException ex) {
- Log.w(TAG, "Failed to notify session info changed.");
- }
- }
-
- /**
- * Notifies clients of that the session is created and ready for use. If the session can be
- * controlled, pass a {@link Bundle} that contains how to control it.
+ * Notifies clients of that the session is created and ready for use.
+ * <p>
+ * If this session is created without any creation request, use {@link #REQUEST_ID_UNKNOWN}
+ * as the request ID.
*
* @param sessionInfo information of the new session.
- * The {@link RouteSessionInfo#getId() id} of the session must be
- * unique. Pass {@code null} to reject the request or inform clients that
- * session creation is failed.
- * @param requestId id of the previous request to create this session
+ * The {@link RoutingSessionInfo#getId() id} of the session must be unique.
+ * @param requestId id of the previous request to create this session provided in
+ * {@link #onCreateSession(String, String, String, long)}
+ * @see #onCreateSession(String, String, String, long)
+ * @hide
*/
- // TODO: fail reason?
- // TODO: Maybe better to create notifySessionCreationFailed?
- public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
- if (sessionInfo != null) {
- String sessionId = sessionInfo.getId();
- synchronized (mSessionLock) {
- if (mSessionInfo.containsKey(sessionId)) {
- Log.w(TAG, "Ignoring duplicate session id.");
- return;
- }
- mSessionInfo.put(sessionInfo.getId(), sessionInfo);
+ public final void notifySessionCreated(@NonNull RoutingSessionInfo sessionInfo,
+ long requestId) {
+ Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+ String sessionId = sessionInfo.getId();
+ synchronized (mSessionLock) {
+ if (mSessionInfo.containsKey(sessionId)) {
+ Log.w(TAG, "Ignoring duplicate session id.");
+ return;
}
- schedulePublishState();
+ mSessionInfo.put(sessionInfo.getId(), sessionInfo);
}
+ schedulePublishState();
if (mClient == null) {
return;
}
try {
+ // TODO: Calling binder calls in multiple thread may cause timing issue.
+ // Consider to change implementations to avoid the problems.
+ // For example, post binder calls, always send all sessions at once, etc.
mClient.notifySessionCreated(sessionInfo, requestId);
} catch (RemoteException ex) {
Log.w(TAG, "Failed to notify session created.");
@@ -220,118 +193,195 @@
}
/**
- * Releases a session with the given id.
- * {@link #onDestroySession} is called if the session is released.
+ * Notifies clients of that the session could not be created.
*
- * @param sessionId id of the session to be released
- * @see #onDestroySession(String, RouteSessionInfo)
+ * @param requestId id of the previous request to create the session provided in
+ * {@link #onCreateSession(String, String, String, long)}.
+ * @see #onCreateSession(String, String, String, long)
+ * @hide
*/
- public final void releaseSession(@NonNull String sessionId) {
- if (TextUtils.isEmpty(sessionId)) {
- throw new IllegalArgumentException("sessionId must not be empty");
+ public final void notifySessionCreationFailed(long requestId) {
+ if (mClient == null) {
+ return;
}
- //TODO: notify media router service of release.
- RouteSessionInfo sessionInfo;
- synchronized (mSessionLock) {
- sessionInfo = mSessionInfo.remove(sessionId);
- }
- if (sessionInfo != null) {
- mHandler.sendMessage(obtainMessage(
- MediaRoute2ProviderService::onDestroySession, this, sessionId, sessionInfo));
- schedulePublishState();
+ try {
+ mClient.notifySessionCreationFailed(requestId);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to notify session creation failed.");
}
}
/**
- * Called when a session should be created.
+ * Notifies the existing session is updated. For example, when
+ * {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed.
+ *
+ * @hide
+ */
+ public final void notifySessionUpdated(@NonNull RoutingSessionInfo sessionInfo) {
+ Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+ String sessionId = sessionInfo.getId();
+ synchronized (mSessionLock) {
+ if (mSessionInfo.containsKey(sessionId)) {
+ mSessionInfo.put(sessionId, sessionInfo);
+ } else {
+ Log.w(TAG, "Ignoring unknown session info.");
+ return;
+ }
+ }
+
+ if (mClient == null) {
+ return;
+ }
+ try {
+ mClient.notifySessionUpdated(sessionInfo);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to notify session info changed.");
+ }
+ }
+
+ /**
+ * Notifies that the session is released.
+ *
+ * @param sessionId id of the released session.
+ * @see #onReleaseSession(String)
+ * @hide
+ */
+ public final void notifySessionReleased(@NonNull String sessionId) {
+ if (TextUtils.isEmpty(sessionId)) {
+ throw new IllegalArgumentException("sessionId must not be empty");
+ }
+ RoutingSessionInfo sessionInfo;
+ synchronized (mSessionLock) {
+ sessionInfo = mSessionInfo.remove(sessionId);
+ }
+
+ if (sessionInfo == null) {
+ Log.w(TAG, "Ignoring unknown session info.");
+ return;
+ }
+
+ if (mClient == null) {
+ return;
+ }
+ try {
+ mClient.notifySessionReleased(sessionInfo);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to notify session info changed.");
+ }
+ }
+
+ /**
+ * Called when the service receives a request to create a session.
+ * <p>
* You should create and maintain your own session and notifies the client of
- * session info. Call {@link #notifySessionCreated(RouteSessionInfo, long)}
+ * session info. Call {@link #notifySessionCreated(RoutingSessionInfo, long)}
* with the given {@code requestId} to notify the information of a new session.
- * If you can't create the session or want to reject the request, pass {@code null}
- * as session info in {@link #notifySessionCreated(RouteSessionInfo, long)}
- * with the given {@code requestId}.
+ * The created session must have the same route feature and must include the given route
+ * specified by {@code routeId}.
+ * <p>
+ * If the session can be controlled, you can optionally pass the control hints to
+ * {@link RoutingSessionInfo.Builder#setControlHints(Bundle)}. Control hints is a
+ * {@link Bundle} which contains how to control the session.
+ * <p>
+ * If you can't create the session or want to reject the request, call
+ * {@link #notifySessionCreationFailed(long)} with the given {@code requestId}.
*
* @param packageName the package name of the application that selected the route
* @param routeId the id of the route initially being connected
- * @param routeType the route type of the new session
+ * @param routeFeature the route feature of the new session
* @param requestId the id of this session creation request
+ *
+ * @see RoutingSessionInfo.Builder#Builder(String, String, String)
+ * @see RoutingSessionInfo.Builder#addSelectedRoute(String)
+ * @see RoutingSessionInfo.Builder#setControlHints(Bundle)
+ * @hide
*/
public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId,
- @NonNull String routeType, long requestId);
+ @NonNull String routeFeature, long requestId);
/**
- * Called when a session is about to be destroyed.
- * You can clean up your session here. This can happen by the
- * client or provider itself.
+ * Called when the session should be released. A client of the session or system can request
+ * a session to be released.
+ * <p>
+ * After releasing the session, call {@link #notifySessionReleased(String)}
+ * with the ID of the released session.
*
- * @param sessionId id of the session being destroyed.
- * @param lastSessionInfo information of the session being destroyed.
- * @see #releaseSession(String)
+ * Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger
+ * this method to be called.
+ *
+ * @param sessionId id of the session being released.
+ * @see #notifySessionReleased(String)
+ * @see #getSessionInfo(String)
+ * @hide
*/
- public abstract void onDestroySession(@NonNull String sessionId,
- @NonNull RouteSessionInfo lastSessionInfo);
+ public abstract void onReleaseSession(@NonNull String sessionId);
//TODO: make a way to reject the request
/**
* Called when a client requests selecting a route for the session.
- * After the route is selected, call {@link #updateSessionInfo(RouteSessionInfo)} to update
- * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
- * clients of updated session info.
+ * After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
+ * to update session info.
*
* @param sessionId id of the session
* @param routeId id of the route
- * @see #updateSessionInfo(RouteSessionInfo)
+ * @hide
*/
public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId);
//TODO: make a way to reject the request
/**
* Called when a client requests deselecting a route from the session.
- * After the route is deselected, call {@link #updateSessionInfo(RouteSessionInfo)} to update
- * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
- * clients of updated session info.
+ * After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
+ * to update session info.
*
* @param sessionId id of the session
* @param routeId id of the route
+ * @hide
*/
public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId);
//TODO: make a way to reject the request
/**
* Called when a client requests transferring a session to a route.
- * After the transfer is finished, call {@link #updateSessionInfo(RouteSessionInfo)} to update
- * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
- * clients of updated session info.
+ * After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)}
+ * to update session info.
*
* @param sessionId id of the session
* @param routeId id of the route
+ * @hide
*/
public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId);
/**
- * Called when the {@link RouteDiscoveryRequest discovery request} has changed.
+ * Called when the {@link RouteDiscoveryPreference discovery preference} has changed.
* <p>
* Whenever an application registers a {@link MediaRouter2.RouteCallback callback},
- * it also provides a discovery request to specify types of routes that it is interested in.
- * The media router combines all of these discovery request into a single discovery request
- * and notifies each provider.
+ * it also provides a discovery preference to specify features of routes that it is interested
+ * in. The media router combines all of these discovery request into a single discovery
+ * preference and notifies each provider.
* </p><p>
- * The provider should examine {@link RouteDiscoveryRequest#getRouteTypes() route types}
- * in the discovery request to determine what kind of routes it should try to discover
- * and whether it should perform active or passive scans. In many cases, the provider may be
- * able to save power by not performing any scans when the request doesn't have any matching
- * route types.
+ * The provider should examine {@link RouteDiscoveryPreference#getPreferredFeatures()
+ * preferred features} in the discovery preference to determine what kind of routes it should
+ * try to discover and whether it should perform active or passive scans. In many cases,
+ * the provider may be able to save power by not performing any scans when the request doesn't
+ * have any matching route features.
* </p>
*
- * @param request the new discovery request
+ * @param preference the new discovery preference
+ *
+ * TODO: This method needs tests.
*/
- public void onDiscoveryRequestChanged(@NonNull RouteDiscoveryRequest request) {}
+ public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {}
/**
- * Updates provider info and publishes routes and session info.
+ * Updates routes of the provider and notifies the system media router service.
*/
- public final void updateProviderInfo(@NonNull MediaRoute2ProviderInfo providerInfo) {
- mProviderInfo = Objects.requireNonNull(providerInfo, "providerInfo must not be null");
+ public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) {
+ Objects.requireNonNull(routes, "routes must not be null");
+ mProviderInfo = new MediaRoute2ProviderInfo.Builder()
+ .addRoutes(routes)
+ .build();
schedulePublishState();
}
@@ -355,12 +405,12 @@
return;
}
- List<RouteSessionInfo> sessionInfos;
+ List<RoutingSessionInfo> sessionInfos;
synchronized (mSessionLock) {
sessionInfos = new ArrayList<>(mSessionInfo.values());
}
try {
- mClient.updateState(mProviderInfo, sessionInfos);
+ mClient.updateState(mProviderInfo);
} catch (RemoteException ex) {
Log.w(TAG, "Failed to send onProviderInfoUpdated");
}
@@ -384,14 +434,15 @@
@Override
public void requestCreateSession(String packageName, String routeId,
- String routeType, long requestId) {
+ String routeFeature, long requestId) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
- MediaRoute2ProviderService.this, packageName, routeId, routeType,
+ MediaRoute2ProviderService.this, packageName, routeId, routeFeature,
requestId));
}
+
@Override
public void releaseSession(@NonNull String sessionId) {
if (!checkCallerisSystem()) {
@@ -401,7 +452,7 @@
Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service.");
return;
}
- mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::releaseSession,
+ mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
MediaRoute2ProviderService.this, sessionId));
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8ebf617..bc4da10 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -18,10 +18,7 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -37,7 +34,6 @@
import com.android.internal.annotations.GuardedBy;
-import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -50,55 +46,18 @@
import java.util.stream.Collectors;
/**
- * A new Media Router
- * @hide
+ * Media Router 2 allows applications to control the routing of media channels
+ * and streams from the current device to remote speakers and devices.
*
* TODO: Add method names at the beginning of log messages. (e.g. changeSessionInfoOnHandler)
* Not only MediaRouter2, but also to service / manager / provider.
*/
public class MediaRouter2 {
-
- /** @hide */
- @Retention(SOURCE)
- @IntDef(value = {
- SELECT_REASON_UNKNOWN,
- SELECT_REASON_USER_SELECTED,
- SELECT_REASON_FALLBACK,
- SELECT_REASON_SYSTEM_SELECTED})
- public @interface SelectReason {}
-
- /**
- * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the reason
- * the route was selected is unknown.
- */
- public static final int SELECT_REASON_UNKNOWN = 0;
-
- /**
- * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route
- * is selected in response to a user's request. For example, when a user has selected
- * a different device to play media to.
- */
- public static final int SELECT_REASON_USER_SELECTED = 1;
-
- /**
- * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route
- * is selected as a fallback route. For example, when Wi-Fi is disconnected, the device speaker
- * may be selected as a fallback route.
- */
- public static final int SELECT_REASON_FALLBACK = 2;
-
- /**
- * This is passed from {@link com.android.server.media.MediaRouterService} when the route
- * is selected in response to a request from other apps (e.g. System UI).
- * @hide
- */
- public static final int SELECT_REASON_SYSTEM_SELECTED = 3;
-
private static final String TAG = "MR2";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final Object sRouterLock = new Object();
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
private static MediaRouter2 sInstance;
private final Context mContext;
@@ -114,29 +73,30 @@
new CopyOnWriteArrayList<>();
private final String mPackageName;
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
- @GuardedBy("sLock")
- private RouteDiscoveryRequest mDiscoveryRequest = RouteDiscoveryRequest.EMPTY;
+ @GuardedBy("sRouterLock")
+ private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
// TODO: Make MediaRouter2 is always connected to the MediaRouterService.
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
Client2 mClient;
- @GuardedBy("sLock")
- private Map<String, RouteSessionController> mSessionControllers = new ArrayMap<>();
+ @GuardedBy("sRouterLock")
+ private Map<String, RoutingController> mRoutingControllers = new ArrayMap<>();
private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1);
final Handler mHandler;
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
private boolean mShouldUpdateRoutes;
private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
/**
* Gets an instance of the media router associated with the context.
*/
+ @NonNull
public static MediaRouter2 getInstance(@NonNull Context context) {
Objects.requireNonNull(context, "context must not be null");
synchronized (sRouterLock) {
@@ -193,12 +153,12 @@
*/
public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull RouteCallback routeCallback,
- @NonNull RouteDiscoveryRequest request) {
+ @NonNull RouteDiscoveryPreference preference) {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(routeCallback, "callback must not be null");
- Objects.requireNonNull(request, "request must not be null");
+ Objects.requireNonNull(preference, "preference must not be null");
- RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, request);
+ RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference);
if (!mRouteCallbackRecords.addIfAbsent(record)) {
Log.w(TAG, "Ignoring the same callback");
return;
@@ -210,15 +170,13 @@
try {
mMediaRouterService.registerClient2(client, mPackageName);
updateDiscoveryRequestLocked();
- mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryRequest);
+ mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryPreference);
mClient = client;
} catch (RemoteException ex) {
Log.e(TAG, "Unable to register media router.", ex);
}
}
}
-
- //TODO: Update discovery request here.
}
/**
@@ -251,8 +209,8 @@
}
private void updateDiscoveryRequestLocked() {
- mDiscoveryRequest = new RouteDiscoveryRequest.Builder(
- mRouteCallbackRecords.stream().map(record -> record.mRequest).collect(
+ mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+ mRouteCallbackRecords.stream().map(record -> record.mPreference).collect(
Collectors.toList())).build();
}
@@ -261,8 +219,8 @@
* known to the media router.
* Please note that the list can be changed before callbacks are invoked.
*
- * @return the list of routes that contains at least one of the route types in discovery
- * requests registered by the application
+ * @return the list of routes that contains at least one of the route features in discovery
+ * preferences registered by the application
*/
@NonNull
public List<MediaRoute2Info> getRoutes() {
@@ -272,7 +230,7 @@
List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
for (MediaRoute2Info route : mRoutes.values()) {
- if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
filteredRoutes.add(route);
}
}
@@ -283,12 +241,13 @@
}
/**
- * Registers a callback to get updates on creations and changes of route sessions.
+ * Registers a callback to get updates on creations and changes of routing sessions.
* If you register the same callback twice or more, it will be ignored.
*
* @param executor the executor to execute the callback on
* @param callback the callback to register
* @see #unregisterSessionCallback
+ * @hide
*/
@NonNull
public void registerSessionCallback(@CallbackExecutor Executor executor,
@@ -309,6 +268,7 @@
*
* @param callback the callback to unregister
* @see #registerSessionCallback
+ * @hide
*/
@NonNull
public void unregisterSessionCallback(@NonNull SessionCallback callback) {
@@ -324,25 +284,26 @@
* Requests the media route provider service to create a session with the given route.
*
* @param route the route you want to create a session with.
- * @param routeType the route type of the session. Should not be empty
+ * @param routeFeature the route feature of the session. Should not be empty.
*
* @see SessionCallback#onSessionCreated
* @see SessionCallback#onSessionCreationFailed
+ * @hide
*/
@NonNull
public void requestCreateSession(@NonNull MediaRoute2Info route,
- @NonNull String routeType) {
+ @NonNull String routeFeature) {
Objects.requireNonNull(route, "route must not be null");
- if (TextUtils.isEmpty(routeType)) {
- throw new IllegalArgumentException("routeType must not be empty");
+ if (TextUtils.isEmpty(routeFeature)) {
+ throw new IllegalArgumentException("routeFeature must not be empty");
}
// TODO: Check the given route exists
- // TODO: Check the route supports the given routeType
+ // TODO: Check the route supports the given routeFeature
final int requestId;
requestId = mSessionCreationRequestCnt.getAndIncrement();
- SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeType);
+ SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeFeature);
mSessionCreationRequests.add(request);
Client2 client;
@@ -351,7 +312,7 @@
}
if (client != null) {
try {
- mMediaRouterService.requestCreateSession(client, route, routeType, requestId);
+ mMediaRouterService.requestCreateSession(client, route, routeFeature, requestId);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to request to create session.", ex);
mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
@@ -365,6 +326,7 @@
*
* @param route the route that will receive the control request
* @param request the media control request
+ * @hide
*/
//TODO: Discuss what to use for request (e.g., Intent? Request class?)
//TODO: Provide a way to obtain the result
@@ -392,6 +354,7 @@
* </p>
*
* @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
+ * @hide
*/
public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) {
Objects.requireNonNull(route, "route must not be null");
@@ -416,6 +379,7 @@
* </p>
*
* @param delta The delta to add to the current volume.
+ * @hide
*/
public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
Objects.requireNonNull(route, "route must not be null");
@@ -442,7 +406,7 @@
synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
- if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
addedRoutes.add(route);
}
}
@@ -458,7 +422,7 @@
synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
mRoutes.remove(route.getId());
- if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
removedRoutes.add(route);
}
}
@@ -474,7 +438,7 @@
synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
- if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
changedRoutes.add(route);
}
}
@@ -491,7 +455,7 @@
* <p>
* Pass {@code null} to sessionInfo for the failure case.
*/
- void createControllerOnHandler(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+ void createControllerOnHandler(@Nullable RoutingSessionInfo sessionInfo, int requestId) {
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
if (request.mRequestId == requestId) {
@@ -504,27 +468,27 @@
mSessionCreationRequests.remove(matchingRequest);
MediaRoute2Info requestedRoute = matchingRequest.mRoute;
- String requestedRouteType = matchingRequest.mRouteType;
+ String requestedRouteFeature = matchingRequest.mRouteFeature;
if (sessionInfo == null) {
// TODO: We may need to distinguish between failure and rejection.
// One way can be introducing 'reason'.
- notifySessionCreationFailed(requestedRoute, requestedRouteType);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
- } else if (!TextUtils.equals(requestedRouteType,
- sessionInfo.getRouteType())) {
- Log.w(TAG, "The session has different route type from what we requested. "
- + "(requested=" + requestedRouteType
- + ", actual=" + sessionInfo.getRouteType()
+ } else if (!TextUtils.equals(requestedRouteFeature,
+ sessionInfo.getRouteFeature())) {
+ Log.w(TAG, "The session has different route feature from what we requested. "
+ + "(requested=" + requestedRouteFeature
+ + ", actual=" + sessionInfo.getRouteFeature()
+ ")");
- notifySessionCreationFailed(requestedRoute, requestedRouteType);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
} else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
Log.w(TAG, "The session does not contain the requested route. "
+ "(requestedRouteId=" + requestedRoute.getId()
+ ", actualRoutes=" + sessionInfo.getSelectedRoutes()
+ ")");
- notifySessionCreationFailed(requestedRoute, requestedRouteType);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
} else if (!TextUtils.equals(requestedRoute.getProviderId(),
sessionInfo.getProviderId())) {
@@ -532,29 +496,29 @@
+ "(requested route's providerId=" + requestedRoute.getProviderId()
+ ", actual providerId=" + sessionInfo.getProviderId()
+ ")");
- notifySessionCreationFailed(requestedRoute, requestedRouteType);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
}
}
if (sessionInfo != null) {
- RouteSessionController controller = new RouteSessionController(sessionInfo);
+ RoutingController controller = new RoutingController(sessionInfo);
synchronized (sRouterLock) {
- mSessionControllers.put(controller.getSessionId(), controller);
+ mRoutingControllers.put(controller.getSessionId(), controller);
}
notifySessionCreated(controller);
}
}
- void changeSessionInfoOnHandler(RouteSessionInfo sessionInfo) {
+ void changeSessionInfoOnHandler(RoutingSessionInfo sessionInfo) {
if (sessionInfo == null) {
Log.w(TAG, "changeSessionInfoOnHandler: Ignoring null sessionInfo.");
return;
}
- RouteSessionController matchingController;
+ RoutingController matchingController;
synchronized (sRouterLock) {
- matchingController = mSessionControllers.get(sessionInfo.getId());
+ matchingController = mRoutingControllers.get(sessionInfo.getId());
}
if (matchingController == null) {
@@ -563,27 +527,27 @@
return;
}
- RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo();
+ RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
Log.w(TAG, "changeSessionInfoOnHandler: Provider IDs are not matched. old="
+ oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId());
return;
}
- matchingController.setRouteSessionInfo(sessionInfo);
+ matchingController.setRoutingSessionInfo(sessionInfo);
notifySessionInfoChanged(matchingController, oldInfo, sessionInfo);
}
- void releaseControllerOnHandler(RouteSessionInfo sessionInfo) {
+ void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) {
if (sessionInfo == null) {
Log.w(TAG, "releaseControllerOnHandler: Ignoring null sessionInfo.");
return;
}
final String uniqueSessionId = sessionInfo.getId();
- RouteSessionController matchingController;
+ RoutingController matchingController;
synchronized (sRouterLock) {
- matchingController = mSessionControllers.get(uniqueSessionId);
+ matchingController = mRoutingControllers.get(uniqueSessionId);
}
if (matchingController == null) {
@@ -594,7 +558,7 @@
return;
}
- RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo();
+ RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
Log.w(TAG, "releaseControllerOnHandler: Provider IDs are not matched. old="
+ oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId());
@@ -602,23 +566,23 @@
}
synchronized (sRouterLock) {
- mSessionControllers.remove(uniqueSessionId, matchingController);
+ mRoutingControllers.remove(uniqueSessionId, matchingController);
}
matchingController.release();
notifyControllerReleased(matchingController);
}
private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
- RouteDiscoveryRequest discoveryRequest) {
+ RouteDiscoveryPreference discoveryRequest) {
return routes.stream()
.filter(
- route -> route.containsRouteTypes(discoveryRequest.getRouteTypes()))
+ route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
.collect(Collectors.toList());
}
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
- List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest);
+ List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
if (!filteredRoutes.isEmpty()) {
record.mExecutor.execute(
() -> record.mRouteCallback.onRoutesAdded(filteredRoutes));
@@ -628,7 +592,7 @@
private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
- List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest);
+ List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
if (!filteredRoutes.isEmpty()) {
record.mExecutor.execute(
() -> record.mRouteCallback.onRoutesRemoved(filteredRoutes));
@@ -638,7 +602,7 @@
private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
- List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest);
+ List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
if (!filteredRoutes.isEmpty()) {
record.mExecutor.execute(
() -> record.mRouteCallback.onRoutesChanged(filteredRoutes));
@@ -646,22 +610,22 @@
}
}
- private void notifySessionCreated(RouteSessionController controller) {
+ private void notifySessionCreated(RoutingController controller) {
for (SessionCallbackRecord record: mSessionCallbackRecords) {
record.mExecutor.execute(
() -> record.mSessionCallback.onSessionCreated(controller));
}
}
- private void notifySessionCreationFailed(MediaRoute2Info route, String routeType) {
+ private void notifySessionCreationFailed(MediaRoute2Info route, String routeFeature) {
for (SessionCallbackRecord record: mSessionCallbackRecords) {
record.mExecutor.execute(
- () -> record.mSessionCallback.onSessionCreationFailed(route, routeType));
+ () -> record.mSessionCallback.onSessionCreationFailed(route, routeFeature));
}
}
- private void notifySessionInfoChanged(RouteSessionController controller,
- RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ private void notifySessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
for (SessionCallbackRecord record: mSessionCallbackRecords) {
record.mExecutor.execute(
() -> record.mSessionCallback.onSessionInfoChanged(
@@ -669,7 +633,7 @@
}
}
- private void notifyControllerReleased(RouteSessionController controller) {
+ private void notifyControllerReleased(RoutingController controller) {
for (SessionCallbackRecord record: mSessionCallbackRecords) {
record.mExecutor.execute(
() -> record.mSessionCallback.onSessionReleased(controller));
@@ -710,23 +674,24 @@
/**
* Callback for receiving a result of session creation and session updates.
+ * @hide
*/
public static class SessionCallback {
/**
- * Called when the route session is created by the route provider.
+ * Called when the routing session is created by the route provider.
*
* @param controller the controller to control the created session
*/
- public void onSessionCreated(@NonNull RouteSessionController controller) {}
+ public void onSessionCreated(@NonNull RoutingController controller) {}
/**
* Called when the session creation request failed.
*
* @param requestedRoute the route info which was used for the request
- * @param requestedRouteType the route type which was used for the request
+ * @param requestedRouteFeature the route feature which was used for the request
*/
public void onSessionCreationFailed(@NonNull MediaRoute2Info requestedRoute,
- @NonNull String requestedRouteType) {}
+ @NonNull String requestedRouteFeature) {}
/**
* Called when the session info has changed.
@@ -737,44 +702,44 @@
* TODO: (Discussion) Do we really need newInfo? The controller has the newInfo.
* However. there can be timing issue if there is no newInfo.
*/
- public void onSessionInfoChanged(@NonNull RouteSessionController controller,
- @NonNull RouteSessionInfo oldInfo,
- @NonNull RouteSessionInfo newInfo) {}
+ public void onSessionInfoChanged(@NonNull RoutingController controller,
+ @NonNull RoutingSessionInfo oldInfo,
+ @NonNull RoutingSessionInfo newInfo) {}
/**
* Called when the session is released by {@link MediaRoute2ProviderService}.
* Before this method is called, the controller would be released by the system,
- * which means the {@link RouteSessionController#isReleased()} will always return true
+ * which means the {@link RoutingController#isReleased()} will always return true
* for the {@code controller} here.
* <p>
- * Note: Calling {@link RouteSessionController#release()} will <em>NOT</em> trigger
+ * Note: Calling {@link RoutingController#release()} will <em>NOT</em> trigger
* this method to be called.
*
* TODO: Add tests for checking whether this method is called.
* TODO: When service process dies, this should be called.
*
- * @see RouteSessionController#isReleased()
+ * @see RoutingController#isReleased()
*/
- public void onSessionReleased(@NonNull RouteSessionController controller) {}
+ public void onSessionReleased(@NonNull RoutingController controller) {}
}
/**
- * A class to control media route session in media route provider.
+ * A class to control media routing session in media route provider.
* For example, selecting/deselcting/transferring routes to session can be done through this
* class. Instances are created by {@link MediaRouter2}.
*
- * TODO: Need to add toString()
+ * @hide
*/
- public final class RouteSessionController {
+ public final class RoutingController {
private final Object mControllerLock = new Object();
- @GuardedBy("mLock")
- private RouteSessionInfo mSessionInfo;
+ @GuardedBy("mControllerLock")
+ private RoutingSessionInfo mSessionInfo;
- @GuardedBy("mLock")
+ @GuardedBy("mControllerLock")
private volatile boolean mIsReleased;
- RouteSessionController(@NonNull RouteSessionInfo sessionInfo) {
+ RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
mSessionInfo = sessionInfo;
}
@@ -788,17 +753,17 @@
}
/**
- * @return the type of routes that the session includes.
+ * @return the feature which is used by the session mainly.
*/
@NonNull
- public String getRouteType() {
+ public String getRouteFeature() {
synchronized (mControllerLock) {
- return mSessionInfo.getRouteType();
+ return mSessionInfo.getRouteFeature();
}
}
/**
- * @return the control hints used to control route session if available.
+ * @return the control hints used to control routing session if available.
*/
@Nullable
public Bundle getControlHints() {
@@ -1020,7 +985,7 @@
Client2 client;
synchronized (sRouterLock) {
- mSessionControllers.remove(getSessionId(), this);
+ mRoutingControllers.remove(getSessionId(), this);
client = mClient;
}
if (client != null) {
@@ -1032,18 +997,50 @@
}
}
+ @Override
+ public String toString() {
+ // To prevent logging spam, we only print the ID of each route.
+ List<String> selectedRoutes = getSelectedRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+ List<String> selectableRoutes = getSelectableRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+ List<String> deselectableRoutes = getDeselectableRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+ List<String> transferrableRoutes = getTransferrableRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+
+ StringBuilder result = new StringBuilder()
+ .append("RoutingController{ ")
+ .append("sessionId=").append(getSessionId())
+ .append(", routeFeature=").append(getRouteFeature())
+ .append(", selectedRoutes={")
+ .append(selectedRoutes)
+ .append("}")
+ .append(", selectableRoutes={")
+ .append(selectableRoutes)
+ .append("}")
+ .append(", deselectableRoutes={")
+ .append(deselectableRoutes)
+ .append("}")
+ .append(", transferrableRoutes={")
+ .append(transferrableRoutes)
+ .append("}")
+ .append(" }");
+ return result.toString();
+ }
+
/**
* TODO: Change this to package private. (Hidden for debugging purposes)
* @hide
*/
@NonNull
- public RouteSessionInfo getRouteSessionInfo() {
+ public RoutingSessionInfo getRoutingSessionInfo() {
synchronized (mControllerLock) {
return mSessionInfo;
}
}
- void setRouteSessionInfo(@NonNull RouteSessionInfo info) {
+ void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) {
synchronized (mControllerLock) {
mSessionInfo = info;
}
@@ -1070,13 +1067,13 @@
final class RouteCallbackRecord {
public final Executor mExecutor;
public final RouteCallback mRouteCallback;
- public final RouteDiscoveryRequest mRequest;
+ public final RouteDiscoveryPreference mPreference;
RouteCallbackRecord(@Nullable Executor executor, @NonNull RouteCallback routeCallback,
- @Nullable RouteDiscoveryRequest request) {
+ @Nullable RouteDiscoveryPreference preference) {
mRouteCallback = routeCallback;
mExecutor = executor;
- mRequest = request;
+ mPreference = preference;
}
@Override
@@ -1125,13 +1122,13 @@
final class SessionCreationRequest {
public final MediaRoute2Info mRoute;
- public final String mRouteType;
+ public final String mRouteFeature;
public final int mRequestId;
SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route,
- @NonNull String routeType) {
+ @NonNull String routeFeature) {
mRoute = route;
- mRouteType = routeType;
+ mRouteFeature = routeFeature;
mRequestId = requestId;
}
}
@@ -1159,19 +1156,19 @@
}
@Override
- public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+ public void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, int requestId) {
mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
MediaRouter2.this, sessionInfo, requestId));
}
@Override
- public void notifySessionInfoChanged(@Nullable RouteSessionInfo sessionInfo) {
+ public void notifySessionInfoChanged(@Nullable RoutingSessionInfo sessionInfo) {
mHandler.sendMessage(obtainMessage(MediaRouter2::changeSessionInfoOnHandler,
MediaRouter2.this, sessionInfo));
}
@Override
- public void notifySessionReleased(RouteSessionInfo sessionInfo) {
+ public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
mHandler.sendMessage(obtainMessage(MediaRouter2::releaseControllerOnHandler,
MediaRouter2.this, sessionInfo));
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 1e6ec51..7022933 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -65,7 +65,7 @@
@GuardedBy("mRoutesLock")
private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
@NonNull
- final ConcurrentMap<String, List<String>> mRouteTypeMap = new ConcurrentHashMap<>();
+ final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
private AtomicInteger mNextRequestId = new AtomicInteger(1);
@@ -144,7 +144,7 @@
}
//TODO: clear mRoutes?
mClient = null;
- mRouteTypeMap.clear();
+ mPreferredFeaturesMap.clear();
}
}
}
@@ -160,14 +160,14 @@
public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) {
Objects.requireNonNull(packageName, "packageName must not be null");
- List<String> routeTypes = mRouteTypeMap.get(packageName);
- if (routeTypes == null) {
+ List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
+ if (preferredFeatures == null) {
return Collections.emptyList();
}
List<MediaRoute2Info> routes = new ArrayList<>();
synchronized (mRoutesLock) {
for (MediaRoute2Info route : mRoutes.values()) {
- if (route.containsRouteTypes(routeTypes)) {
+ if (route.hasAnyFeatures(preferredFeatures)) {
routes.add(route);
}
}
@@ -176,7 +176,7 @@
}
@NonNull
- public List<RouteSessionInfo> getActiveSessions() {
+ public List<RoutingSessionInfo> getActiveSessions() {
Client client;
synchronized (sLock) {
client = mClient;
@@ -352,15 +352,15 @@
}
}
- void updateRouteTypes(String packageName, List<String> routeTypes) {
- List<String> prevTypes = mRouteTypeMap.put(packageName, routeTypes);
- if ((prevTypes == null && routeTypes.size() == 0)
- || Objects.equals(routeTypes, prevTypes)) {
+ void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
+ List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
+ if ((prevFeatures == null && preferredFeatures.size() == 0)
+ || Objects.equals(preferredFeatures, prevFeatures)) {
return;
}
for (CallbackRecord record : mCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mCallback.onControlCategoriesChanged(packageName, routeTypes));
+ record.mExecutor.execute(() -> record.mCallback
+ .onControlCategoriesChanged(packageName, preferredFeatures));
}
}
@@ -398,13 +398,13 @@
/**
- * Called when the route types of an app is changed.
+ * Called when the preferred route features of an app is changed.
*
* @param packageName the package name of the application
- * @param routeTypes the list of route types set by an application.
+ * @param preferredFeatures the list of preferred route features set by an application.
*/
public void onControlCategoriesChanged(@NonNull String packageName,
- @NonNull List<String> routeTypes) {}
+ @NonNull List<String> preferredFeatures) {}
}
final class CallbackRecord {
@@ -440,9 +440,9 @@
MediaRouter2Manager.this, packageName, route));
}
- public void notifyRouteTypesChanged(String packageName, List<String> routeTypes) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateRouteTypes,
- MediaRouter2Manager.this, packageName, routeTypes));
+ public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
+ MediaRouter2Manager.this, packageName, features));
}
@Override
diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java
new file mode 100644
index 0000000..2c431b9
--- /dev/null
+++ b/media/java/android/media/MediaTranscodeManager.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * MediaTranscodeManager provides an interface to the system's media transcode service.
+ * Transcode requests are put in a queue and processed in order. When a transcode operation is
+ * completed the caller is notified via its OnTranscodingFinishedListener. In the meantime the
+ * caller may use the returned TranscodingJob object to cancel or check the status of a specific
+ * transcode operation.
+ * The currently supported media types are video and still images.
+ *
+ * TODO(lnilsson): Add sample code when API is settled.
+ *
+ * @hide
+ */
+public final class MediaTranscodeManager {
+ private static final String TAG = "MediaTranscodeManager";
+
+ // Invalid ID passed from native means the request was never enqueued.
+ private static final long ID_INVALID = -1;
+
+ // Events passed from native.
+ private static final int EVENT_JOB_STARTED = 1;
+ private static final int EVENT_JOB_PROGRESSED = 2;
+ private static final int EVENT_JOB_FINISHED = 3;
+
+ @IntDef(prefix = { "EVENT_" }, value = {
+ EVENT_JOB_STARTED,
+ EVENT_JOB_PROGRESSED,
+ EVENT_JOB_FINISHED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Event {}
+
+ private static MediaTranscodeManager sMediaTranscodeManager;
+ private final ConcurrentMap<Long, TranscodingJob> mPendingTranscodingJobs =
+ new ConcurrentHashMap<>();
+ private final Context mContext;
+
+ /**
+ * Listener that gets notified when a transcoding operation has finished.
+ * This listener gets notified regardless of how the operation finished. It is up to the
+ * listener implementation to check the result and take appropriate action.
+ */
+ @FunctionalInterface
+ public interface OnTranscodingFinishedListener {
+ /**
+ * Called when the transcoding operation has finished. The receiver may use the
+ * TranscodingJob to check the result, i.e. whether the operation succeeded, was canceled or
+ * if an error occurred.
+ * @param transcodingJob The TranscodingJob instance for the finished transcoding operation.
+ */
+ void onTranscodingFinished(@NonNull TranscodingJob transcodingJob);
+ }
+
+ /**
+ * Class describing a transcode operation to be performed. The caller uses this class to
+ * configure a transcoding operation that can then be enqueued using MediaTranscodeManager.
+ */
+ public static final class TranscodingRequest {
+ private Uri mSrcUri;
+ private Uri mDstUri;
+ private MediaFormat mDstFormat;
+
+ private TranscodingRequest(Builder b) {
+ mSrcUri = b.mSrcUri;
+ mDstUri = b.mDstUri;
+ mDstFormat = b.mDstFormat;
+ }
+
+ /** TranscodingRequest builder class. */
+ public static class Builder {
+ private Uri mSrcUri;
+ private Uri mDstUri;
+ private MediaFormat mDstFormat;
+
+ /**
+ * Specifies the source media file.
+ * @param uri Content uri for the source media file.
+ * @return The builder instance.
+ */
+ public Builder setSourceUri(Uri uri) {
+ mSrcUri = uri;
+ return this;
+ }
+
+ /**
+ * Specifies the destination media file.
+ * @param uri Content uri for the destination media file.
+ * @return The builder instance.
+ */
+ public Builder setDestinationUri(Uri uri) {
+ mDstUri = uri;
+ return this;
+ }
+
+ /**
+ * Specifies the media format of the transcoded media file.
+ * @param dstFormat MediaFormat containing the desired destination format.
+ * @return The builder instance.
+ */
+ public Builder setDestinationFormat(MediaFormat dstFormat) {
+ mDstFormat = dstFormat;
+ return this;
+ }
+
+ /**
+ * Builds a new TranscodingRequest with the configuration set on this builder.
+ * @return A new TranscodingRequest.
+ */
+ public TranscodingRequest build() {
+ return new TranscodingRequest(this);
+ }
+ }
+ }
+
+ /**
+ * Handle to an enqueued transcoding operation. An instance of this class represents a single
+ * enqueued transcoding operation. The caller can use that instance to query the status or
+ * progress, and to get the result once the operation has completed.
+ */
+ public static final class TranscodingJob {
+ /** The job is enqueued but not yet running. */
+ public static final int STATUS_PENDING = 1;
+ /** The job is currently running. */
+ public static final int STATUS_RUNNING = 2;
+ /** The job is finished. */
+ public static final int STATUS_FINISHED = 3;
+
+ @IntDef(prefix = { "STATUS_" }, value = {
+ STATUS_PENDING,
+ STATUS_RUNNING,
+ STATUS_FINISHED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {}
+
+ /** The job does not have a result yet. */
+ public static final int RESULT_NONE = 1;
+ /** The job completed successfully. */
+ public static final int RESULT_SUCCESS = 2;
+ /** The job encountered an error while running. */
+ public static final int RESULT_ERROR = 3;
+ /** The job was canceled by the caller. */
+ public static final int RESULT_CANCELED = 4;
+
+ @IntDef(prefix = { "RESULT_" }, value = {
+ RESULT_NONE,
+ RESULT_SUCCESS,
+ RESULT_ERROR,
+ RESULT_CANCELED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Result {}
+
+ /** Listener that gets notified when the progress changes. */
+ @FunctionalInterface
+ public interface OnProgressChangedListener {
+
+ /**
+ * Called when the progress changes. The progress is between 0 and 1, where 0 means
+ * that the job has not yet started and 1 means that it has finished.
+ * @param progress The new progress.
+ */
+ void onProgressChanged(float progress);
+ }
+
+ private final Executor mExecutor;
+ private final OnTranscodingFinishedListener mListener;
+ private final ReentrantLock mStatusChangeLock = new ReentrantLock();
+ private Executor mProgressChangedExecutor;
+ private OnProgressChangedListener mProgressChangedListener;
+ private long mID;
+ private float mProgress = 0.0f;
+ private @Status int mStatus = STATUS_PENDING;
+ private @Result int mResult = RESULT_NONE;
+
+ private TranscodingJob(long id, @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnTranscodingFinishedListener listener) {
+ mID = id;
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ /**
+ * Set a progress listener.
+ * @param listener The progress listener.
+ */
+ public void setOnProgressChangedListener(@NonNull @CallbackExecutor Executor executor,
+ @Nullable OnProgressChangedListener listener) {
+ mProgressChangedExecutor = executor;
+ mProgressChangedListener = listener;
+ }
+
+ /**
+ * Cancels the transcoding job and notify the listener. If the job happened to finish before
+ * being canceled this call is effectively a no-op and will not update the result in that
+ * case.
+ */
+ public void cancel() {
+ setJobFinished(RESULT_CANCELED);
+ sMediaTranscodeManager.native_cancelTranscodingRequest(mID);
+ }
+
+ /**
+ * Gets the progress of the transcoding job. The progress is between 0 and 1, where 0 means
+ * that the job has not yet started and 1 means that it is finished.
+ * @return The progress.
+ */
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Gets the status of the transcoding job.
+ * @return The status.
+ */
+ public @Status int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Gets the result of the transcoding job.
+ * @return The result.
+ */
+ public @Result int getResult() {
+ return mResult;
+ }
+
+ private void setJobStarted() {
+ mStatus = STATUS_RUNNING;
+ }
+
+ private void setJobProgress(float newProgress) {
+ mProgress = newProgress;
+
+ // Notify listener.
+ OnProgressChangedListener onProgressChangedListener = mProgressChangedListener;
+ if (onProgressChangedListener != null) {
+ mProgressChangedExecutor.execute(
+ () -> onProgressChangedListener.onProgressChanged(mProgress));
+ }
+ }
+
+ private void setJobFinished(int result) {
+ boolean doNotifyListener = false;
+
+ // Prevent conflicting simultaneous status updates from native (finished) and from the
+ // caller (cancel).
+ try {
+ mStatusChangeLock.lock();
+ if (mStatus != STATUS_FINISHED) {
+ mStatus = STATUS_FINISHED;
+ mResult = result;
+ doNotifyListener = true;
+ }
+ } finally {
+ mStatusChangeLock.unlock();
+ }
+
+ if (doNotifyListener) {
+ mExecutor.execute(() -> mListener.onTranscodingFinished(this));
+ }
+ }
+
+ private void processJobEvent(@Event int event, int arg) {
+ switch (event) {
+ case EVENT_JOB_STARTED:
+ setJobStarted();
+ break;
+ case EVENT_JOB_PROGRESSED:
+ setJobProgress((float) arg / 100);
+ break;
+ case EVENT_JOB_FINISHED:
+ setJobFinished(arg);
+ break;
+ default:
+ Log.e(TAG, "Unsupported event: " + event);
+ break;
+ }
+ }
+ }
+
+ // Initializes the native library.
+ private static native void native_init();
+ // Requests a new job ID from the native service.
+ private native long native_requestUniqueJobID();
+ // Enqueues a transcoding request to the native service.
+ private native boolean native_enqueueTranscodingRequest(
+ long id, @NonNull TranscodingRequest transcodingRequest, @NonNull Context context);
+ // Cancels an enqueued transcoding request.
+ private native void native_cancelTranscodingRequest(long id);
+
+ // Private constructor.
+ private MediaTranscodeManager(@NonNull Context context) {
+ mContext = context;
+ }
+
+ // Events posted from the native service.
+ @SuppressWarnings("unused")
+ private void postEventFromNative(@Event int event, long id, int arg) {
+ Log.d(TAG, String.format("postEventFromNative. Event %d, ID %d, arg %d", event, id, arg));
+
+ TranscodingJob transcodingJob = mPendingTranscodingJobs.get(id);
+
+ // Job IDs are added to the tracking set before the job is enqueued so it should never
+ // be null unless the service misbehaves.
+ if (transcodingJob == null) {
+ Log.e(TAG, "No matching transcode job found for id " + id);
+ return;
+ }
+
+ transcodingJob.processJobEvent(event, arg);
+ }
+
+ /**
+ * Gets the MediaTranscodeManager singleton instance.
+ * @param context The application context.
+ * @return the {@link MediaTranscodeManager} singleton instance.
+ */
+ public static MediaTranscodeManager getInstance(@NonNull Context context) {
+ Preconditions.checkNotNull(context);
+ synchronized (MediaTranscodeManager.class) {
+ if (sMediaTranscodeManager == null) {
+ sMediaTranscodeManager = new MediaTranscodeManager(context.getApplicationContext());
+ }
+ return sMediaTranscodeManager;
+ }
+ }
+
+ /**
+ * Enqueues a TranscodingRequest for execution.
+ * @param transcodingRequest The TranscodingRequest to enqueue.
+ * @param listenerExecutor Executor on which the listener is notified.
+ * @param listener Listener to get notified when the transcoding job is finished.
+ * @return A TranscodingJob for this operation.
+ */
+ public @Nullable TranscodingJob enqueueTranscodingRequest(
+ @NonNull TranscodingRequest transcodingRequest,
+ @NonNull @CallbackExecutor Executor listenerExecutor,
+ @NonNull OnTranscodingFinishedListener listener) {
+ Log.i(TAG, "enqueueTranscodingRequest called.");
+ Preconditions.checkNotNull(transcodingRequest);
+ Preconditions.checkNotNull(listenerExecutor);
+ Preconditions.checkNotNull(listener);
+
+ // Reserve a job ID.
+ long jobID = native_requestUniqueJobID();
+ if (jobID == ID_INVALID) {
+ return null;
+ }
+
+ // Add the job to the tracking set.
+ TranscodingJob transcodingJob = new TranscodingJob(jobID, listenerExecutor, listener);
+ mPendingTranscodingJobs.put(jobID, transcodingJob);
+
+ // Enqueue the request with the native service.
+ boolean enqueued = native_enqueueTranscodingRequest(jobID, transcodingRequest, mContext);
+ if (!enqueued) {
+ mPendingTranscodingJobs.remove(jobID);
+ return null;
+ }
+
+ return transcodingJob;
+ }
+
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
+ }
+}
diff --git a/media/java/android/media/RouteDiscoveryRequest.aidl b/media/java/android/media/RouteDiscoveryPreference.aidl
similarity index 94%
rename from media/java/android/media/RouteDiscoveryRequest.aidl
rename to media/java/android/media/RouteDiscoveryPreference.aidl
index 744f656..898eb39 100644
--- a/media/java/android/media/RouteDiscoveryRequest.aidl
+++ b/media/java/android/media/RouteDiscoveryPreference.aidl
@@ -16,4 +16,4 @@
package android.media;
-parcelable RouteDiscoveryRequest;
+parcelable RouteDiscoveryPreference;
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
new file mode 100644
index 0000000..7ec1123
--- /dev/null
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A media route discovery preference describing the kinds of routes that media router
+ * would like to discover and whether to perform active scanning.
+ *
+ * @see MediaRouter2#registerRouteCallback
+ */
+public final class RouteDiscoveryPreference implements Parcelable {
+ @NonNull
+ public static final Creator<RouteDiscoveryPreference> CREATOR =
+ new Creator<RouteDiscoveryPreference>() {
+ @Override
+ public RouteDiscoveryPreference createFromParcel(Parcel in) {
+ return new RouteDiscoveryPreference(in);
+ }
+
+ @Override
+ public RouteDiscoveryPreference[] newArray(int size) {
+ return new RouteDiscoveryPreference[size];
+ }
+ };
+
+ @NonNull
+ private final List<String> mPreferredFeatures;
+ private final boolean mActiveScan;
+ @Nullable
+ private final Bundle mExtras;
+
+ /**
+ * @hide
+ */
+ public static final RouteDiscoveryPreference EMPTY =
+ new Builder(Collections.emptyList(), false).build();
+
+ RouteDiscoveryPreference(@NonNull Builder builder) {
+ mPreferredFeatures = builder.mPreferredFeatures;
+ mActiveScan = builder.mActiveScan;
+ mExtras = builder.mExtras;
+ }
+
+ RouteDiscoveryPreference(@NonNull Parcel in) {
+ mPreferredFeatures = in.createStringArrayList();
+ mActiveScan = in.readBoolean();
+ mExtras = in.readBundle();
+ }
+
+ @NonNull
+ public List<String> getPreferredFeatures() {
+ return mPreferredFeatures;
+ }
+
+ public boolean isActiveScan() {
+ return mActiveScan;
+ }
+
+ /**
+ * @hide
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStringList(mPreferredFeatures);
+ dest.writeBoolean(mActiveScan);
+ dest.writeBundle(mExtras);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder()
+ .append("RouteDiscoveryRequest{ ")
+ .append("preferredFeatures={")
+ .append(String.join(", ", mPreferredFeatures))
+ .append("}")
+ .append(", activeScan=")
+ .append(mActiveScan)
+ .append(" }");
+
+ return result.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof RouteDiscoveryPreference)) {
+ return false;
+ }
+ RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
+ return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
+ && mActiveScan == other.mActiveScan;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPreferredFeatures, mActiveScan);
+ }
+
+ /**
+ * Builder for {@link RouteDiscoveryPreference}.
+ */
+ public static final class Builder {
+ List<String> mPreferredFeatures;
+ boolean mActiveScan;
+ Bundle mExtras;
+
+ public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
+ mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
+ "preferredFeatures must not be null"));
+ mActiveScan = activeScan;
+ }
+
+ public Builder(@NonNull RouteDiscoveryPreference preference) {
+ Objects.requireNonNull(preference, "preference must not be null");
+
+ mPreferredFeatures = preference.getPreferredFeatures();
+ mActiveScan = preference.isActiveScan();
+ mExtras = preference.getExtras();
+ }
+
+ /**
+ * A constructor to combine all of the preferences into a single preference .
+ * It ignores extras of preferences.
+ *
+ * @hide
+ */
+ public Builder(@NonNull Collection<RouteDiscoveryPreference> preferences) {
+ Objects.requireNonNull(preferences, "preferences must not be null");
+
+ Set<String> routeFeatureSet = new HashSet<>();
+ mActiveScan = false;
+ for (RouteDiscoveryPreference preference : preferences) {
+ routeFeatureSet.addAll(preference.mPreferredFeatures);
+ mActiveScan |= preference.mActiveScan;
+ }
+ mPreferredFeatures = new ArrayList<>(routeFeatureSet);
+ }
+
+ /**
+ * Sets preferred route features to discover.
+ */
+ @NonNull
+ public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) {
+ mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
+ "preferredFeatures must not be null"));
+ return this;
+ }
+
+ /**
+ * Sets if active scanning should be performed.
+ */
+ @NonNull
+ public Builder setActiveScan(boolean activeScan) {
+ mActiveScan = activeScan;
+ return this;
+ }
+
+ /**
+ * Sets the extras of the route.
+ * @hide
+ */
+ @NonNull
+ public Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link RouteDiscoveryPreference}.
+ */
+ @NonNull
+ public RouteDiscoveryPreference build() {
+ return new RouteDiscoveryPreference(this);
+ }
+ }
+}
diff --git a/media/java/android/media/RouteDiscoveryRequest.java b/media/java/android/media/RouteDiscoveryRequest.java
deleted file mode 100644
index 88b31fb..0000000
--- a/media/java/android/media/RouteDiscoveryRequest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-
-/**
- * @hide
- */
-public final class RouteDiscoveryRequest implements Parcelable {
- @NonNull
- public static final Creator<RouteDiscoveryRequest> CREATOR =
- new Creator<RouteDiscoveryRequest>() {
- @Override
- public RouteDiscoveryRequest createFromParcel(Parcel in) {
- return new RouteDiscoveryRequest(in);
- }
-
- @Override
- public RouteDiscoveryRequest[] newArray(int size) {
- return new RouteDiscoveryRequest[size];
- }
- };
-
- @NonNull
- private final List<String> mRouteTypes;
- private final boolean mActiveScan;
- @Nullable
- private final Bundle mExtras;
-
- /**
- * @hide
- */
- public static final RouteDiscoveryRequest EMPTY =
- new Builder(Collections.emptyList(), false).build();
-
- RouteDiscoveryRequest(@NonNull Builder builder) {
- mRouteTypes = builder.mRouteTypes;
- mActiveScan = builder.mActiveScan;
- mExtras = builder.mExtras;
- }
-
- RouteDiscoveryRequest(@NonNull Parcel in) {
- mRouteTypes = in.createStringArrayList();
- mActiveScan = in.readBoolean();
- mExtras = in.readBundle();
- }
-
- @NonNull
- public List<String> getRouteTypes() {
- return mRouteTypes;
- }
-
- public boolean isActiveScan() {
- return mActiveScan;
- }
-
- /**
- * @hide
- */
- public Bundle getExtras() {
- return mExtras;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeStringList(mRouteTypes);
- dest.writeBoolean(mActiveScan);
- dest.writeBundle(mExtras);
- }
-
- @Override
- public String toString() {
- StringBuilder result = new StringBuilder()
- .append("RouteDiscoveryRequest{ ")
- .append("routeTypes={")
- .append(String.join(", ", mRouteTypes))
- .append("}")
- .append(", activeScan=")
- .append(mActiveScan)
- .append(" }");
-
- return result.toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof RouteDiscoveryRequest)) {
- return false;
- }
- RouteDiscoveryRequest other = (RouteDiscoveryRequest) o;
- return Objects.equals(mRouteTypes, other.mRouteTypes)
- && mActiveScan == other.mActiveScan;
- }
-
- /**
- * Builder for {@link RouteDiscoveryRequest}.
- */
- public static final class Builder {
- List<String> mRouteTypes;
- boolean mActiveScan;
- Bundle mExtras;
-
- public Builder(@NonNull List<String> routeTypes, boolean activeScan) {
- mRouteTypes = new ArrayList<>(
- Objects.requireNonNull(routeTypes, "routeTypes must not be null"));
- mActiveScan = activeScan;
- }
-
- public Builder(@NonNull RouteDiscoveryRequest request) {
- Objects.requireNonNull(request, "request must not be null");
-
- mRouteTypes = request.getRouteTypes();
- mActiveScan = request.isActiveScan();
- mExtras = request.getExtras();
- }
-
- /**
- * A constructor to combine all of the requests into a single request.
- * It ignores extras of requests.
- */
- Builder(@NonNull Collection<RouteDiscoveryRequest> requests) {
- Set<String> routeTypeSet = new HashSet<>();
- mActiveScan = false;
- for (RouteDiscoveryRequest request : requests) {
- routeTypeSet.addAll(request.mRouteTypes);
- mActiveScan |= request.mActiveScan;
- }
- mRouteTypes = new ArrayList<>(routeTypeSet);
- }
-
- /**
- * Sets route types to discover.
- */
- public Builder setRouteTypes(@NonNull List<String> routeTypes) {
- mRouteTypes = new ArrayList<>(
- Objects.requireNonNull(routeTypes, "routeTypes must not be null"));
- return this;
- }
-
- /**
- * Sets if active scanning should be performed.
- */
- public Builder setActiveScan(boolean activeScan) {
- mActiveScan = activeScan;
- return this;
- }
-
- /**
- * Sets the extras of the route.
- * @hide
- */
- public Builder setExtras(@Nullable Bundle extras) {
- mExtras = extras;
- return this;
- }
-
- /**
- * Builds the {@link RouteDiscoveryRequest}.
- */
- public RouteDiscoveryRequest build() {
- return new RouteDiscoveryRequest(this);
- }
- }
-}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/RoutingSessionInfo.aidl
similarity index 95%
rename from media/java/android/media/RouteSessionInfo.aidl
rename to media/java/android/media/RoutingSessionInfo.aidl
index fb5d836..7b8e3d9 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/RoutingSessionInfo.aidl
@@ -16,4 +16,4 @@
package android.media;
-parcelable RouteSessionInfo;
+parcelable RoutingSessionInfo;
diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
similarity index 65%
rename from media/java/android/media/RouteSessionInfo.java
rename to media/java/android/media/RoutingSessionInfo.java
index 5330630..96acf6c 100644
--- a/media/java/android/media/RouteSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -30,29 +30,28 @@
import java.util.Objects;
/**
- * Describes a route session that is made when a media route is selected.
+ * Describes a routing session which is created when a media route is selected.
* @hide
*/
-public class RouteSessionInfo implements Parcelable {
-
+public final class RoutingSessionInfo implements Parcelable {
@NonNull
- public static final Creator<RouteSessionInfo> CREATOR =
- new Creator<RouteSessionInfo>() {
+ public static final Creator<RoutingSessionInfo> CREATOR =
+ new Creator<RoutingSessionInfo>() {
@Override
- public RouteSessionInfo createFromParcel(Parcel in) {
- return new RouteSessionInfo(in);
+ public RoutingSessionInfo createFromParcel(Parcel in) {
+ return new RoutingSessionInfo(in);
}
@Override
- public RouteSessionInfo[] newArray(int size) {
- return new RouteSessionInfo[size];
+ public RoutingSessionInfo[] newArray(int size) {
+ return new RoutingSessionInfo[size];
}
};
- public static final String TAG = "RouteSessionInfo";
+ private static final String TAG = "RoutingSessionInfo";
final String mId;
final String mClientPackageName;
- final String mRouteType;
+ final String mRouteFeature;
@Nullable
final String mProviderId;
final List<String> mSelectedRoutes;
@@ -62,14 +61,15 @@
@Nullable
final Bundle mControlHints;
- RouteSessionInfo(@NonNull Builder builder) {
+ RoutingSessionInfo(@NonNull Builder builder) {
Objects.requireNonNull(builder, "builder must not be null.");
mId = builder.mId;
mClientPackageName = builder.mClientPackageName;
- mRouteType = builder.mRouteType;
+ mRouteFeature = builder.mRouteFeature;
mProviderId = builder.mProviderId;
+ // TODO: Needs to check that the routes already have unique IDs.
mSelectedRoutes = Collections.unmodifiableList(
convertToUniqueRouteIds(builder.mSelectedRoutes));
mSelectableRoutes = Collections.unmodifiableList(
@@ -82,12 +82,12 @@
mControlHints = builder.mControlHints;
}
- RouteSessionInfo(@NonNull Parcel src) {
+ RoutingSessionInfo(@NonNull Parcel src) {
Objects.requireNonNull(src, "src must not be null.");
mId = ensureString(src.readString());
mClientPackageName = ensureString(src.readString());
- mRouteType = ensureString(src.readString());
+ mRouteFeature = ensureString(src.readString());
mProviderId = src.readString();
mSelectedRoutes = ensureList(src.createStringArrayList());
@@ -113,18 +113,6 @@
}
/**
- * Returns whether the session info is valid or not
- *
- * TODO in this CL: Remove this method.
- */
- public boolean isValid() {
- return !TextUtils.isEmpty(mId)
- && !TextUtils.isEmpty(mClientPackageName)
- && !TextUtils.isEmpty(mRouteType)
- && mSelectedRoutes.size() > 0;
- }
-
- /**
* Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have
* unique IDs.
* <p>
@@ -160,12 +148,12 @@
}
/**
- * Gets the route type of the session.
- * Routes that don't have the type can't be added to the session.
+ * Gets the route feature of the session.
+ * Routes that don't have the feature can't be selected into the session.
*/
@NonNull
- public String getRouteType() {
- return mRouteType;
+ public String getRouteFeature() {
+ return mRouteFeature;
}
/**
@@ -226,7 +214,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mId);
dest.writeString(mClientPackageName);
- dest.writeString(mRouteType);
+ dest.writeString(mRouteFeature);
dest.writeString(mProviderId);
dest.writeStringList(mSelectedRoutes);
dest.writeStringList(mSelectableRoutes);
@@ -236,11 +224,37 @@
}
@Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof RoutingSessionInfo)) {
+ return false;
+ }
+
+ RoutingSessionInfo other = (RoutingSessionInfo) obj;
+ return Objects.equals(mId, other.mId)
+ && Objects.equals(mClientPackageName, other.mClientPackageName)
+ && Objects.equals(mRouteFeature, other.mRouteFeature)
+ && Objects.equals(mProviderId, other.mProviderId)
+ && Objects.equals(mSelectedRoutes, other.mSelectedRoutes)
+ && Objects.equals(mSelectableRoutes, other.mSelectableRoutes)
+ && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes)
+ && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mClientPackageName, mRouteFeature, mProviderId,
+ mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes);
+ }
+
+ @Override
public String toString() {
StringBuilder result = new StringBuilder()
- .append("RouteSessionInfo{ ")
+ .append("RoutingSessionInfo{ ")
.append("sessionId=").append(mId)
- .append(", routeType=").append(mRouteType)
+ .append(", routeFeature=").append(mRouteFeature)
.append(", selectedRoutes={")
.append(String.join(",", mSelectedRoutes))
.append("}")
@@ -276,12 +290,12 @@
}
/**
- * Builder class for {@link RouteSessionInfo}.
+ * Builder class for {@link RoutingSessionInfo}.
*/
public static final class Builder {
final String mId;
final String mClientPackageName;
- final String mRouteType;
+ final String mRouteFeature;
String mProviderId;
final List<String> mSelectedRoutes;
final List<String> mSelectableRoutes;
@@ -290,24 +304,32 @@
Bundle mControlHints;
/**
- * Constructor for builder to create {@link RouteSessionInfo}.
+ * Constructor for builder to create {@link RoutingSessionInfo}.
* <p>
* In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of
- * {@link RouteSessionInfo#getId()} can be different from what was set in
+ * {@link RoutingSessionInfo#getId()} can be different from what was set in
* {@link MediaRoute2ProviderService}.
* </p>
*
+ * @param id ID of the session. Must not be empty.
+ * @param clientPackageName package name of the client app which uses this session.
+ * If is is unknown, then just use an empty string.
+ * @param routeFeature the route feature of session. Must not be empty.
* @see MediaRoute2Info#getId()
*/
public Builder(@NonNull String id, @NonNull String clientPackageName,
- @NonNull String routeType) {
+ @NonNull String routeFeature) {
if (TextUtils.isEmpty(id)) {
throw new IllegalArgumentException("id must not be empty");
}
+ Objects.requireNonNull(clientPackageName, "clientPackageName must not be null");
+ if (TextUtils.isEmpty(routeFeature)) {
+ throw new IllegalArgumentException("routeFeature must not be empty");
+ }
+
mId = id;
- mClientPackageName = Objects.requireNonNull(
- clientPackageName, "clientPackageName must not be null");
- mRouteType = Objects.requireNonNull(routeType, "routeType must not be null");
+ mClientPackageName = clientPackageName;
+ mRouteFeature = routeFeature;
mSelectedRoutes = new ArrayList<>();
mSelectableRoutes = new ArrayList<>();
mDeselectableRoutes = new ArrayList<>();
@@ -315,17 +337,17 @@
}
/**
- * Constructor for builder to create {@link RouteSessionInfo} with
- * existing {@link RouteSessionInfo} instance.
+ * Constructor for builder to create {@link RoutingSessionInfo} with
+ * existing {@link RoutingSessionInfo} instance.
*
* @param sessionInfo the existing instance to copy data from.
*/
- public Builder(@NonNull RouteSessionInfo sessionInfo) {
+ public Builder(@NonNull RoutingSessionInfo sessionInfo) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
mId = sessionInfo.mId;
mClientPackageName = sessionInfo.mClientPackageName;
- mRouteType = sessionInfo.mRouteType;
+ mRouteFeature = sessionInfo.mRouteFeature;
mProviderId = sessionInfo.mProviderId;
mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
@@ -338,8 +360,6 @@
/**
* Sets the provider ID of the session.
- * Also, calling this method will make all type of route IDs be unique by adding
- * {@code providerId:} as a prefix. So do NOT call this method twice on same instance.
*
* @hide
*/
@@ -362,20 +382,26 @@
}
/**
- * Adds a route to the selected routes.
+ * Adds a route to the selected routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder addSelectedRoute(@NonNull String routeId) {
- mSelectedRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectedRoutes.add(routeId);
return this;
}
/**
- * Removes a route from the selected routes.
+ * Removes a route from the selected routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder removeSelectedRoute(@NonNull String routeId) {
- mSelectedRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectedRoutes.remove(routeId);
return this;
}
@@ -389,20 +415,26 @@
}
/**
- * Adds a route to the selectable routes.
+ * Adds a route to the selectable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder addSelectableRoute(@NonNull String routeId) {
- mSelectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectableRoutes.add(routeId);
return this;
}
/**
- * Removes a route from the selectable routes.
+ * Removes a route from the selectable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder removeSelectableRoute(@NonNull String routeId) {
- mSelectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectableRoutes.remove(routeId);
return this;
}
@@ -416,20 +448,26 @@
}
/**
- * Adds a route to the deselectable routes.
+ * Adds a route to the deselectable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder addDeselectableRoute(@NonNull String routeId) {
- mDeselectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mDeselectableRoutes.add(routeId);
return this;
}
/**
- * Removes a route from the deselectable routes.
+ * Removes a route from the deselectable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder removeDeselectableRoute(@NonNull String routeId) {
- mDeselectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mDeselectableRoutes.remove(routeId);
return this;
}
@@ -443,21 +481,26 @@
}
/**
- * Adds a route to the transferrable routes.
+ * Adds a route to the transferrable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder addTransferrableRoute(@NonNull String routeId) {
- mTransferrableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mTransferrableRoutes.add(routeId);
return this;
}
/**
- * Removes a route from the transferrable routes.
+ * Removes a route from the transferrable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder removeTransferrableRoute(@NonNull String routeId) {
- mTransferrableRoutes.remove(
- Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mTransferrableRoutes.remove(routeId);
return this;
}
@@ -471,11 +514,16 @@
}
/**
- * Builds a route session info.
+ * Builds a routing session info.
+ *
+ * @throws IllegalArgumentException if no selected routes are added.
*/
@NonNull
- public RouteSessionInfo build() {
- return new RouteSessionInfo(this);
+ public RoutingSessionInfo build() {
+ if (mSelectedRoutes.isEmpty()) {
+ throw new IllegalArgumentException("selectedRoutes must not be empty");
+ }
+ return new RoutingSessionInfo(this);
}
}
}
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index 8f68cbd..ed272d5 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -16,6 +16,7 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.media.session.MediaSession;
import java.lang.annotation.Retention;
@@ -60,6 +61,7 @@
private final int mControlType;
private final int mMaxVolume;
+ private final String mControlId;
private int mCurrentVolume;
private Callback mCallback;
@@ -73,10 +75,28 @@
* @param maxVolume The maximum allowed volume.
* @param currentVolume The current volume on the output.
*/
+
public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume) {
+ this(volumeControl, maxVolume, currentVolume, null);
+ }
+
+ /**
+ * Create a new volume provider for handling volume events. You must specify
+ * the type of volume control, the maximum volume that can be used, and the
+ * current volume on the output.
+ *
+ * @param volumeControl The method for controlling volume that is used by
+ * this provider.
+ * @param maxVolume The maximum allowed volume.
+ * @param currentVolume The current volume on the output.
+ * @param volumeControlId The volume control id of this provider.
+ */
+ public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume,
+ @Nullable String volumeControlId) {
mControlType = volumeControl;
mMaxVolume = maxVolume;
mCurrentVolume = currentVolume;
+ mControlId = volumeControlId;
}
/**
@@ -122,6 +142,17 @@
}
/**
+ * Gets the volume control id. It can be used to identify which volume provider is
+ * used by the session.
+ *
+ * @return the volume control id or {@code null} if it isn't set.
+ */
+ @Nullable
+ public final String getVolumeControlId() {
+ return mControlId;
+ }
+
+ /**
* Override to handle requests to set the volume of the current output.
* After the volume has been modified {@link #setCurrentVolume} must be
* called to notify the system.
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 01f1250..27f02fe 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -719,9 +719,14 @@
if (track == null) {
break;
}
- // TODO: add synchronous versions
- track.stop();
- track.flush();
+ try {
+ // TODO: add synchronous versions
+ track.stop();
+ track.flush();
+ } catch (IllegalStateException e) {
+ // ignore exception, AudioTrack could have already been stopped or
+ // released by the user of the AudioPolicy
+ }
}
}
if (mCaptors != null) {
@@ -730,8 +735,13 @@
if (record == null) {
break;
}
- // TODO: if needed: implement an invalidate method
- record.stop();
+ try {
+ // TODO: if needed: implement an invalidate method
+ record.stop();
+ } catch (IllegalStateException e) {
+ // ignore exception, AudioRecord could have already been stopped or
+ // released by the user of the AudioPolicy
+ }
}
}
}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 4d68a6a..21378c8 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -48,6 +48,6 @@
// These commands relate to volume handling
void setPlaybackToLocal(in AudioAttributes attributes);
- void setPlaybackToRemote(int control, int max);
+ void setPlaybackToRemote(int control, int max, @nullable String controlId);
void setCurrentVolume(int currentVolume);
}
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 1812d9c..c2f6206 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -964,16 +964,26 @@
private final int mMaxVolume;
private final int mCurrentVolume;
private final AudioAttributes mAudioAttrs;
+ private final String mVolumeControlId;
/**
* @hide
*/
public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs) {
+ this(type, control, max, current, attrs, null);
+ }
+
+ /**
+ * @hide
+ */
+ public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs,
+ String volumeControlId) {
mVolumeType = type;
mVolumeControl = control;
mMaxVolume = max;
mCurrentVolume = current;
mAudioAttrs = attrs;
+ mVolumeControlId = volumeControlId;
}
PlaybackInfo(Parcel in) {
@@ -982,6 +992,7 @@
mMaxVolume = in.readInt();
mCurrentVolume = in.readInt();
mAudioAttrs = in.readParcelable(null);
+ mVolumeControlId = in.readString();
}
/**
@@ -1042,11 +1053,24 @@
return mAudioAttrs;
}
+ /**
+ * Gets the volume control ID for this session. It can be used to identify which
+ * volume provider is used by the session.
+ *
+ * @return the volume control ID for this session or {@code null} if it's local playback
+ * or not set.
+ * @see VolumeProvider#getVolumeControlId()
+ */
+ @Nullable
+ public String getVolumeControlId() {
+ return mVolumeControlId;
+ }
+
@Override
public String toString() {
return "volumeType=" + mVolumeType + ", volumeControl=" + mVolumeControl
+ ", maxVolume=" + mMaxVolume + ", currentVolume=" + mCurrentVolume
- + ", audioAttrs=" + mAudioAttrs;
+ + ", audioAttrs=" + mAudioAttrs + ", volumeControlId=" + mVolumeControlId;
}
@Override
@@ -1061,6 +1085,7 @@
dest.writeInt(mMaxVolume);
dest.writeInt(mCurrentVolume);
dest.writeParcelable(mAudioAttrs, flags);
+ dest.writeString(mVolumeControlId);
}
public static final @android.annotation.NonNull Parcelable.Creator<PlaybackInfo> CREATOR =
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 3adee59..9953626 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -335,7 +335,7 @@
try {
mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(),
- volumeProvider.getMaxVolume());
+ volumeProvider.getMaxVolume(), volumeProvider.getVolumeControlId());
mBinder.setCurrentVolume(volumeProvider.getCurrentVolume());
} catch (RemoteException e) {
Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 77596a5..118f65c 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -25,6 +25,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.AudioFormat;
import android.os.Handler;
@@ -75,7 +76,9 @@
value = {
RECOGNITION_FLAG_NONE,
RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
- RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+ RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
+ RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
+ RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
})
public @interface RecognitionFlags {}
@@ -100,11 +103,34 @@
* triggers after a call to {@link #startRecognition(int)}, if the model
* triggers multiple times.
* When this isn't specified, the default behavior is to stop recognition once the
- * trigger happenss, till the caller starts recognition again.
+ * trigger happens, till the caller starts recognition again.
*/
public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
/**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use AEC.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
+ * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
+ * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_ECHO_CANCELLATION}.
+ * If this flag is passed without the audio capability supported, there will be no audio effect
+ * applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
+
+ /**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use noise suppression.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
+ * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
+ * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_NOISE_SUPPRESSION}. If this flag
+ * is passed without the audio capability supported, there will be no audio effect applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
+
+ /**
* Additional payload for {@link Callback#onDetected}.
*/
public static class EventPayload {
@@ -267,11 +293,20 @@
boolean allowMultipleTriggers =
(recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
- int status = STATUS_OK;
+
+ int audioCapabilities = 0;
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
+ audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+ }
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
+ audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+ }
+
+ int status;
try {
status = mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
- allowMultipleTriggers, null, null));
+ allowMultipleTriggers, null, null, audioCapabilities));
} catch (RemoteException e) {
return false;
}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 1c38301..dd4dac2 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -44,6 +44,7 @@
import com.android.internal.util.Preconditions;
import java.util.HashMap;
+import java.util.Objects;
import java.util.UUID;
/**
@@ -175,19 +176,40 @@
* Factory constructor to create a SoundModel instance for use with methods in this
* class.
*/
- public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) {
- return new Model(new SoundTrigger.GenericSoundModel(modelUuid,
- vendorUuid, data));
+ @NonNull
+ public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data, int version) {
+ Objects.requireNonNull(modelUuid);
+ Objects.requireNonNull(vendorUuid);
+ return new Model(new SoundTrigger.GenericSoundModel(modelUuid, vendorUuid, data,
+ version));
}
+ /**
+ * Factory constructor to create a SoundModel instance for use with methods in this
+ * class.
+ */
+ @NonNull
+ public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data) {
+ return create(modelUuid, vendorUuid, data, -1);
+ }
+
+ @NonNull
public UUID getModelUuid() {
return mGenericSoundModel.uuid;
}
+ @NonNull
public UUID getVendorUuid() {
return mGenericSoundModel.vendorUuid;
}
+ public int getVersion() {
+ return mGenericSoundModel.version;
+ }
+
+ @Nullable
public byte[] getModelData() {
return mGenericSoundModel.data;
}
@@ -428,8 +450,7 @@
*/
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
public int setParameter(@Nullable UUID soundModelId,
- @ModelParams int modelParam, int value)
- throws UnsupportedOperationException, IllegalArgumentException {
+ @ModelParams int modelParam, int value) {
try {
return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam,
value);
@@ -449,15 +470,10 @@
* @param soundModelId UUID of model to get parameter
* @param modelParam {@link ModelParams}
* @return value of parameter
- * @throws UnsupportedOperationException if hal or model do not support this API.
- * {@link SoundTriggerManager#queryParameter} should be checked first.
- * @throws IllegalArgumentException if invalid model handle or parameter is passed.
- * {@link SoundTriggerManager#queryParameter} should be checked first.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
public int getParameter(@NonNull UUID soundModelId,
- @ModelParams int modelParam)
- throws UnsupportedOperationException, IllegalArgumentException {
+ @ModelParams int modelParam) {
try {
return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam);
} catch (RemoteException e) {
@@ -479,8 +495,7 @@
public ModelParamRange queryParameter(@Nullable UUID soundModelId,
@ModelParams int modelParam) {
try {
- return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId),
- modelParam);
+ return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), modelParam);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
new file mode 100644
index 0000000..97a8849
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * AudioCapabilities supported by the implemented HAL driver.
+ * @hide
+ */
+@Backing(type="int")
+enum AudioCapabilities {
+ /**
+ * If set the underlying module supports AEC.
+ */
+ ECHO_CANCELLATION = 1 << 0,
+ /**
+ * If set, the underlying module supports noise suppression.
+ */
+ NOISE_SUPPRESSION = 1 << 1,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
index 149c1cd..726af76 100644
--- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -44,4 +44,11 @@
* and this event will be sent in addition to the abort event.
*/
void onRecognitionAvailabilityChange(boolean available);
+ /**
+ * Notifies the client that the associated module has crashed and restarted. The module instance
+ * is no longer usable and will throw a ServiceSpecificException with a Status.DEAD_OBJECT code
+ * for every call. The client should detach, then re-attach to the module in order to get a new,
+ * usable instance. All state for this module has been lost.
+ */
+ void onModuleDied();
}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
index c7642e8..5c0eeb1 100644
--- a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
@@ -28,6 +28,12 @@
/* Configuration for each key phrase. */
PhraseRecognitionExtra[] phraseRecognitionExtras;
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ */
+ int audioCapabilities;
+
/** Opaque capture configuration data. */
byte[] data;
}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
index 1a3b402..9c56e7b 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -30,6 +30,14 @@
* Unique implementation ID. The UUID must change with each version of
the engine implementation */
String uuid;
+ /**
+ * String naming the architecture used for running the supported models.
+ * (eg. a platform running models on a DSP could implement this string to convey the DSP
+ * architecture used)
+ * This property is supported for soundtrigger HAL v2.3 and above.
+ * If running a previous version, the string will be empty.
+ */
+ String supportedModelArch;
/** Maximum number of concurrent sound models loaded */
int maxSoundModels;
/** Maximum number of key phrases */
@@ -50,4 +58,11 @@
* Rated power consumption when detection is active with TDB
* silence/sound/speech ratio */
int powerConsumptionMw;
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ * This property is supported for soundtrigger HAL v2.3 and above.
+ * If running a previous version, this value will be 0.
+ */
+ int audioCapabilities;
}
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
index d8f9d8f..85ccacf 100644
--- a/media/java/android/media/soundtrigger_middleware/Status.aidl
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -26,4 +26,8 @@
RESOURCE_CONTENTION = 1,
/** Operation is not supported in this implementation. This is a permanent condition. */
OPERATION_NOT_SUPPORTED = 2,
+ /** Temporary lack of permission. */
+ TEMPORARY_PERMISSION_DENIED = 3,
+ /** The object on which this method is called is dead and all of its state is lost. */
+ DEAD_OBJECT = 4,
}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index b076bb6..b5e9d1b 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -60,6 +60,7 @@
void createSession(in ITvInputClient client, in String inputId, boolean isRecordingSession,
int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
+ int getClientPid(in String sessionId);
void setMainSession(in IBinder sessionToken, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
index f90c504..8ccf13a 100755
--- a/media/java/android/media/tv/ITvInputService.aidl
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -30,8 +30,9 @@
void registerCallback(in ITvInputServiceCallback callback);
void unregisterCallback(in ITvInputServiceCallback callback);
void createSession(in InputChannel channel, in ITvInputSessionCallback callback,
- in String inputId);
- void createRecordingSession(in ITvInputSessionCallback callback, in String inputId);
+ in String inputId, in String sessionId);
+ void createRecordingSession(in ITvInputSessionCallback callback, in String inputId,
+ in String sessionId);
// For hardware TvInputService
void notifyHardwareAdded(in TvInputHardwareInfo hardwareInfo);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 854ea43..630d819 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -266,6 +266,15 @@
public static final int INPUT_STATE_DISCONNECTED = 2;
/**
+ * An unknown state of the client pid gets from the TvInputManager. Client gets this value when
+ * query through {@link getClientPid(String sessionId)} fails.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int UNKNOWN_CLIENT_PID = -1;
+
+ /**
* Broadcast intent action when the user blocked content ratings change. For use with the
* {@link #isRatingBlocked}.
*/
@@ -1484,6 +1493,21 @@
}
/**
+ * Get a the client pid when creating the session with the session id provided.
+ *
+ * @param sessionId a String of session id that is used to query the client pid.
+ * @return the client pid when created the session. Returns {@link #UNKNOWN_CLIENT_PID}
+ * if the call fails.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+ public int getClientPid(@NonNull String sessionId) {
+ return getClientPidInternal(sessionId);
+ };
+
+ /**
* Creates a recording {@link Session} for a given TV input.
*
* <p>The number of sessions that can be created at the same time is limited by the capability
@@ -1516,6 +1540,17 @@
}
}
+ private int getClientPidInternal(String sessionId) {
+ Preconditions.checkNotNull(sessionId);
+ int clientPid = UNKNOWN_CLIENT_PID;
+ try {
+ clientPid = mService.getClientPid(sessionId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return clientPid;
+ }
+
/**
* Returns the TvStreamConfig list of the given TV input.
*
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 7fbb337..629dc7c 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -124,7 +124,7 @@
@Override
public void createSession(InputChannel channel, ITvInputSessionCallback cb,
- String inputId) {
+ String inputId, String sessionId) {
if (channel == null) {
Log.w(TAG, "Creating session without input channel");
}
@@ -135,17 +135,20 @@
args.arg1 = channel;
args.arg2 = cb;
args.arg3 = inputId;
+ args.arg4 = sessionId;
mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
}
@Override
- public void createRecordingSession(ITvInputSessionCallback cb, String inputId) {
+ public void createRecordingSession(ITvInputSessionCallback cb, String inputId,
+ String sessionId) {
if (cb == null) {
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = cb;
args.arg2 = inputId;
+ args.arg3 = sessionId;
mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_RECORDING_SESSION, args)
.sendToTarget();
}
@@ -208,6 +211,37 @@
}
/**
+ * Returns a concrete implementation of {@link Session}.
+ *
+ * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager,
+ * it needs to override this method to get the sessionId passed. When no overriding, this method
+ * calls {@link #onCreateSession(String)} defaultly.
+ *
+ * @param inputId The ID of the TV input associated with the session.
+ * @param sessionId the unique sessionId created by TIF when session is created.
+ */
+ @Nullable
+ public Session onCreateSession(@NonNull String inputId, @NonNull String sessionId) {
+ return onCreateSession(inputId);
+ }
+
+ /**
+ * Returns a concrete implementation of {@link RecordingSession}.
+ *
+ * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager,
+ * it needs to override this method to get the sessionId passed. When no overriding, this method
+ * calls {@link #onCreateRecordingSession(String)} defaultly.
+ *
+ * @param inputId The ID of the TV input associated with the recording session.
+ * @param sessionId the unique sessionId created by TIF when session is created.
+ */
+ @Nullable
+ public RecordingSession onCreateRecordingSession(
+ @NonNull String inputId, @NonNull String sessionId) {
+ return onCreateRecordingSession(inputId);
+ }
+
+ /**
* Returns a new {@link TvInputInfo} object if this service is responsible for
* {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of
* ignoring all hardware input.
@@ -2032,8 +2066,9 @@
InputChannel channel = (InputChannel) args.arg1;
ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
String inputId = (String) args.arg3;
+ String sessionId = (String) args.arg4;
args.recycle();
- Session sessionImpl = onCreateSession(inputId);
+ Session sessionImpl = onCreateSession(inputId, sessionId);
if (sessionImpl == null) {
try {
// Failed to create a session.
@@ -2103,8 +2138,10 @@
SomeArgs args = (SomeArgs) msg.obj;
ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg1;
String inputId = (String) args.arg2;
+ String sessionId = (String) args.arg3;
args.recycle();
- RecordingSession recordingSessionImpl = onCreateRecordingSession(inputId);
+ RecordingSession recordingSessionImpl =
+ onCreateRecordingSession(inputId, sessionId);
if (recordingSessionImpl == null) {
try {
// Failed to create a recording session.
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index 4318a0a..5e0b1ea 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -318,7 +318,8 @@
* @param flags The flags used for parceling.
*/
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Preconditions.checkNotNull(dest);
dest.writeInt(mType);
dest.writeString(mId);
dest.writeString(mLanguage);
@@ -387,11 +388,13 @@
public static final @android.annotation.NonNull Parcelable.Creator<TvTrackInfo> CREATOR =
new Parcelable.Creator<TvTrackInfo>() {
@Override
+ @NonNull
public TvTrackInfo createFromParcel(Parcel in) {
return new TvTrackInfo(in);
}
@Override
+ @NonNull
public TvTrackInfo[] newArray(int size) {
return new TvTrackInfo[size];
}
@@ -444,7 +447,9 @@
*
* @param language The language string encoded by either ISO 639-1 or ISO 639-2/T.
*/
- public final Builder setLanguage(String language) {
+ @NonNull
+ public Builder setLanguage(@NonNull String language) {
+ Preconditions.checkNotNull(language);
mLanguage = language;
return this;
}
@@ -454,7 +459,9 @@
*
* @param description The user readable description.
*/
- public final Builder setDescription(CharSequence description) {
+ @NonNull
+ public Builder setDescription(@NonNull CharSequence description) {
+ Preconditions.checkNotNull(description);
mDescription = description;
return this;
}
@@ -479,7 +486,8 @@
* @param audioChannelCount The audio channel count.
* @throws IllegalStateException if not called on an audio track
*/
- public final Builder setAudioChannelCount(int audioChannelCount) {
+ @NonNull
+ public Builder setAudioChannelCount(int audioChannelCount) {
if (mType != TYPE_AUDIO) {
throw new IllegalStateException("Not an audio track");
}
@@ -494,7 +502,8 @@
* @param audioSampleRate The audio sample rate.
* @throws IllegalStateException if not called on an audio track
*/
- public final Builder setAudioSampleRate(int audioSampleRate) {
+ @NonNull
+ public Builder setAudioSampleRate(int audioSampleRate) {
if (mType != TYPE_AUDIO) {
throw new IllegalStateException("Not an audio track");
}
@@ -570,7 +579,8 @@
* @param videoWidth The width of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoWidth(int videoWidth) {
+ @NonNull
+ public Builder setVideoWidth(int videoWidth) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -585,7 +595,8 @@
* @param videoHeight The height of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoHeight(int videoHeight) {
+ @NonNull
+ public Builder setVideoHeight(int videoHeight) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -600,7 +611,8 @@
* @param videoFrameRate The frame rate of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoFrameRate(float videoFrameRate) {
+ @NonNull
+ public Builder setVideoFrameRate(float videoFrameRate) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -620,7 +632,8 @@
* @param videoPixelAspectRatio The pixel aspect ratio of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
+ @NonNull
+ public Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -640,7 +653,8 @@
* @param videoActiveFormatDescription The AFD code of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) {
+ @NonNull
+ public Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -653,7 +667,9 @@
*
* @param extra The extra information.
*/
- public final Builder setExtra(Bundle extra) {
+ @NonNull
+ public Builder setExtra(@NonNull Bundle extra) {
+ Preconditions.checkNotNull(extra);
mExtra = new Bundle(extra);
return this;
}
@@ -663,6 +679,7 @@
*
* @return The new {@link TvTrackInfo} instance
*/
+ @NonNull
public TvTrackInfo build() {
return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted,
mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing,
diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java
index bda166e..83abf86 100644
--- a/media/java/android/media/tv/tuner/DemuxCapabilities.java
+++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java
@@ -16,11 +16,32 @@
package android.media.tv.tuner;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.media.tv.tuner.filter.FilterConfiguration;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Capabilities info for Demux.
+ *
* @hide
*/
public class DemuxCapabilities {
+
+ /** @hide */
+ @IntDef(flag = true, value = {
+ FilterConfiguration.FILTER_TYPE_TS,
+ FilterConfiguration.FILTER_TYPE_MMTP,
+ FilterConfiguration.FILTER_TYPE_IP,
+ FilterConfiguration.FILTER_TYPE_TLV,
+ FilterConfiguration.FILTER_TYPE_ALP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FilterCapabilities {}
+
private final int mNumDemux;
private final int mNumRecord;
private final int mNumPlayback;
@@ -34,7 +55,8 @@
private final int mFilterCaps;
private final int[] mLinkCaps;
- DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter,
+ // Used by JNI
+ private DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter,
int numSectionFilter, int numAudioFilter, int numVideoFilter, int numPesFilter,
int numPcrFilter, int numBytesInSectionFilter, int filterCaps, int[] linkCaps) {
mNumDemux = numDemux;
@@ -51,52 +73,73 @@
mLinkCaps = linkCaps;
}
- /** Gets total number of demuxes. */
+ /**
+ * Gets total number of demuxes.
+ */
public int getNumDemux() {
return mNumDemux;
}
- /** Gets max number of recordings at a time. */
+ /**
+ * Gets max number of recordings at a time.
+ */
public int getNumRecord() {
return mNumRecord;
}
- /** Gets max number of playbacks at a time. */
+ /**
+ * Gets max number of playbacks at a time.
+ */
public int getNumPlayback() {
return mNumPlayback;
}
- /** Gets number of TS filters. */
+ /**
+ * Gets number of TS filters.
+ */
public int getNumTsFilter() {
return mNumTsFilter;
}
- /** Gets number of section filters. */
+ /**
+ * Gets number of section filters.
+ */
public int getNumSectionFilter() {
return mNumSectionFilter;
}
- /** Gets number of audio filters. */
+ /**
+ * Gets number of audio filters.
+ */
public int getNumAudioFilter() {
return mNumAudioFilter;
}
- /** Gets number of video filters. */
+ /**
+ * Gets number of video filters.
+ */
public int getNumVideoFilter() {
return mNumVideoFilter;
}
- /** Gets number of PES filters. */
+ /**
+ * Gets number of PES filters.
+ */
public int getNumPesFilter() {
return mNumPesFilter;
}
- /** Gets number of PCR filters. */
+ /**
+ * Gets number of PCR filters.
+ */
public int getNumPcrFilter() {
return mNumPcrFilter;
}
- /** Gets number of bytes in the mask of a section filter. */
+ /**
+ * Gets number of bytes in the mask of a section filter.
+ */
public int getNumBytesInSectionFilter() {
return mNumBytesInSectionFilter;
}
/**
* Gets filter capabilities in bit field.
*
- * The bits of the returned value is corresponding to the types in
- * {@link TunerConstants.FilterType}.
+ * <p>The bits of the returned value is corresponding to the types in
+ * {@link FilterConfiguration}.
*/
+ @FilterCapabilities
public int getFilterCapabilities() {
return mFilterCaps;
}
@@ -104,10 +147,12 @@
/**
* Gets link capabilities.
*
- * The returned array contains the same elements as the number of types in
- * {@link TunerConstants.FilterType}.
- * The ith element represents the filter's capability as the source for the ith type
+ * <p>The returned array contains the same elements as the number of types in
+ * {@link FilterConfiguration}.
+ * <p>The ith element represents the filter's capability as the source for the ith type.
*/
+ @Nullable
+ @Size(5)
public int[] getLinkCapabilities() {
return mLinkCaps;
}
diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java
index f9f7a22..0143582 100644
--- a/media/java/android/media/tv/tuner/Descrambler.java
+++ b/media/java/android/media/tv/tuner/Descrambler.java
@@ -16,9 +16,12 @@
package android.media.tv.tuner;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.media.tv.tuner.Tuner.Filter;
-import android.media.tv.tuner.TunerConstants.DemuxPidType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* This class is used to interact with descramblers.
@@ -26,9 +29,25 @@
* <p> Descrambler is a hardware component used to descramble data.
*
* <p> This class controls the TIS interaction with Tuner HAL.
+ *
* @hide
*/
public class Descrambler implements AutoCloseable {
+ /** @hide */
+ @IntDef(prefix = "PID_TYPE_", value = {PID_TYPE_T, PID_TYPE_MMPT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PidType {}
+
+ /**
+ * Packet ID is used to specify packets in transport stream.
+ */
+ public static final int PID_TYPE_T = 1;
+ /**
+ * Packet ID is used to specify packets in MMTP.
+ */
+ public static final int PID_TYPE_MMPT = 2;
+
+
private long mNativeContext;
private native int nativeAddPid(int pidType, int pid, Filter filter);
@@ -52,10 +71,8 @@
* @param pid the PID of packets to start to be descrambled.
* @param filter an optional filter instance to identify upper stream.
* @return result status of the operation.
- *
- * @hide
*/
- public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
+ public int addPid(@PidType int pidType, int pid, @Nullable Filter filter) {
return nativeAddPid(pidType, pid, filter);
}
@@ -68,10 +85,8 @@
* @param pid the PID of packets to stop to be descrambled.
* @param filter an optional filter instance to identify upper stream.
* @return result status of the operation.
- *
- * @hide
*/
- public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
+ public int removePid(@PidType int pidType, int pid, @Nullable Filter filter) {
return nativeRemovePid(pidType, pid, filter);
}
@@ -83,17 +98,13 @@
*
* @param keyToken the token to be used to link the key slot.
* @return result status of the operation.
- *
- * @hide
*/
- public int setKeyToken(byte[] keyToken) {
+ public int setKeyToken(@Nullable byte[] keyToken) {
return nativeSetKeyToken(keyToken);
}
/**
* Release the descrambler instance.
- *
- * @hide
*/
@Override
public void close() {
diff --git a/media/java/android/media/tv/tuner/Dvr.java b/media/java/android/media/tv/tuner/Dvr.java
deleted file mode 100644
index 0bfba8f..0000000
--- a/media/java/android/media/tv/tuner/Dvr.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.tuner;
-
-import android.annotation.NonNull;
-import android.media.tv.tuner.Tuner.DvrCallback;
-import android.media.tv.tuner.Tuner.Filter;
-import android.os.ParcelFileDescriptor;
-
-/** @hide */
-public class Dvr {
- private long mNativeContext;
- private DvrCallback mCallback;
-
- private native int nativeAttachFilter(Filter filter);
- private native int nativeDetachFilter(Filter filter);
- private native int nativeConfigureDvr(DvrSettings settings);
- private native int nativeStartDvr();
- private native int nativeStopDvr();
- private native int nativeFlushDvr();
- private native int nativeClose();
- private native void nativeSetFileDescriptor(int fd);
- private native int nativeRead(int size);
- private native int nativeRead(byte[] bytes, int offset, int size);
- private native int nativeWrite(int size);
- private native int nativeWrite(byte[] bytes, int offset, int size);
-
- private Dvr() {}
-
- /**
- * Attaches a filter to DVR interface for recording.
- *
- * @param filter the filter to be attached.
- * @return result status of the operation.
- */
- public int attachFilter(Filter filter) {
- return nativeAttachFilter(filter);
- }
-
- /**
- * Detaches a filter from DVR interface.
- *
- * @param filter the filter to be detached.
- * @return result status of the operation.
- */
- public int detachFilter(Filter filter) {
- return nativeDetachFilter(filter);
- }
-
- /**
- * Configures the DVR.
- *
- * @param settings the settings of the DVR interface.
- * @return result status of the operation.
- */
- public int configure(DvrSettings settings) {
- return nativeConfigureDvr(settings);
- }
-
- /**
- * Starts DVR.
- *
- * Starts consuming playback data or producing data for recording.
- *
- * @return result status of the operation.
- */
- public int start() {
- return nativeStartDvr();
- }
-
- /**
- * Stops DVR.
- *
- * Stops consuming playback data or producing data for recording.
- *
- * @return result status of the operation.
- */
- public int stop() {
- return nativeStopDvr();
- }
-
- /**
- * Flushed DVR data.
- *
- * @return result status of the operation.
- */
- public int flush() {
- return nativeFlushDvr();
- }
-
- /**
- * closes the DVR instance to release resources.
- *
- * @return result status of the operation.
- */
- public int close() {
- return nativeClose();
- }
-
- /**
- * Sets file descriptor to read/write data.
- */
- public void setFileDescriptor(ParcelFileDescriptor fd) {
- nativeSetFileDescriptor(fd.getFd());
- }
-
- /**
- * Reads data from the file for DVR playback.
- */
- public int read(int size) {
- return nativeRead(size);
- }
-
- /**
- * Reads data from the buffer for DVR playback.
- */
- public int read(@NonNull byte[] bytes, int offset, int size) {
- if (size + offset > bytes.length) {
- throw new ArrayIndexOutOfBoundsException(
- "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
- }
- return nativeRead(bytes, offset, size);
- }
-
- /**
- * Writes recording data to file.
- */
- public int write(int size) {
- return nativeWrite(size);
- }
-
- /**
- * Writes recording data to buffer.
- */
- public int write(@NonNull byte[] bytes, int offset, int size) {
- return nativeWrite(bytes, offset, size);
- }
-}
diff --git a/media/java/android/media/tv/tuner/DvrSettings.java b/media/java/android/media/tv/tuner/DvrSettings.java
deleted file mode 100644
index 76160dc..0000000
--- a/media/java/android/media/tv/tuner/DvrSettings.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.tuner;
-
-import android.media.tv.tuner.TunerConstants.DataFormat;
-import android.media.tv.tuner.TunerConstants.DvrSettingsType;
-
-/**
- * DVR settings.
- *
- * @hide
- */
-public class DvrSettings {
- private int mStatusMask;
- private int mLowThreshold;
- private int mHighThreshold;
- private int mPacketSize;
-
- @DataFormat
- private int mDataFormat;
- @DvrSettingsType
- private int mType;
-
- private DvrSettings(int statusMask, int lowThreshold, int highThreshold, int packetSize,
- @DataFormat int dataFormat, @DvrSettingsType int type) {
- mStatusMask = statusMask;
- mLowThreshold = lowThreshold;
- mHighThreshold = highThreshold;
- mPacketSize = packetSize;
- mDataFormat = dataFormat;
- mType = type;
- }
-
- /**
- * Creates a new builder.
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- /**
- * Builder for DvrSettings.
- */
- public static final class Builder {
- private int mStatusMask;
- private int mLowThreshold;
- private int mHighThreshold;
- private int mPacketSize;
- @DataFormat
- private int mDataFormat;
- @DvrSettingsType
- private int mType;
-
- /**
- * Sets status mask.
- */
- public Builder setStatusMask(int statusMask) {
- this.mStatusMask = statusMask;
- return this;
- }
-
- /**
- * Sets low threshold.
- */
- public Builder setLowThreshold(int lowThreshold) {
- this.mLowThreshold = lowThreshold;
- return this;
- }
-
- /**
- * Sets high threshold.
- */
- public Builder setHighThreshold(int highThreshold) {
- this.mHighThreshold = highThreshold;
- return this;
- }
-
- /**
- * Sets packet size.
- */
- public Builder setPacketSize(int packetSize) {
- this.mPacketSize = packetSize;
- return this;
- }
-
- /**
- * Sets data format.
- */
- public Builder setDataFormat(@DataFormat int dataFormat) {
- this.mDataFormat = dataFormat;
- return this;
- }
-
- /**
- * Sets settings type.
- */
- public Builder setType(@DvrSettingsType int type) {
- this.mType = type;
- return this;
- }
-
- /**
- * Builds a DvrSettings instance.
- */
- public DvrSettings build() {
- return new DvrSettings(
- mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType);
- }
- }
-}
diff --git a/media/java/android/media/tv/tuner/FrontendSettings.java b/media/java/android/media/tv/tuner/FrontendSettings.java
index e2e9910..ad8422c 100644
--- a/media/java/android/media/tv/tuner/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/FrontendSettings.java
@@ -17,7 +17,6 @@
package android.media.tv.tuner;
import android.annotation.SystemApi;
-import android.media.tv.tuner.TunerConstants.FrontendSettingsType;
/**
* Frontend settings for tune and scan operations.
@@ -35,7 +34,6 @@
/**
* Returns the frontend type.
*/
- @FrontendSettingsType
public abstract int getType();
/**
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
new file mode 100644
index 0000000..8e579bf
--- /dev/null
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.Result;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * LNB (low-noise block downconverter) for satellite tuner.
+ *
+ * A Tuner LNB (low-noise block downconverter) is used by satellite frontend to receive the
+ * microwave signal from the satellite, amplify it, and downconvert the frequency to a lower
+ * frequency.
+ *
+ * @hide
+ */
+@SystemApi
+public class Lnb implements AutoCloseable {
+ /** @hide */
+ @IntDef({VOLTAGE_NONE, VOLTAGE_5V, VOLTAGE_11V, VOLTAGE_12V, VOLTAGE_13V, VOLTAGE_14V,
+ VOLTAGE_15V, VOLTAGE_18V, VOLTAGE_19V})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Voltage {}
+
+ /**
+ * LNB power voltage not set.
+ */
+ public static final int VOLTAGE_NONE = Constants.LnbVoltage.NONE;
+ /**
+ * LNB power voltage 5V.
+ */
+ public static final int VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V;
+ /**
+ * LNB power voltage 11V.
+ */
+ public static final int VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V;
+ /**
+ * LNB power voltage 12V.
+ */
+ public static final int VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V;
+ /**
+ * LNB power voltage 13V.
+ */
+ public static final int VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V;
+ /**
+ * LNB power voltage 14V.
+ */
+ public static final int VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V;
+ /**
+ * LNB power voltage 15V.
+ */
+ public static final int VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V;
+ /**
+ * LNB power voltage 18V.
+ */
+ public static final int VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V;
+ /**
+ * LNB power voltage 19V.
+ */
+ public static final int VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V;
+
+ /** @hide */
+ @IntDef({TONE_NONE, TONE_CONTINUOUS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Tone {}
+
+ /**
+ * LNB tone mode not set.
+ */
+ public static final int TONE_NONE = Constants.LnbTone.NONE;
+ /**
+ * LNB continuous tone mode.
+ */
+ public static final int TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS;
+
+ /** @hide */
+ @IntDef({POSITION_UNDEFINED, POSITION_A, POSITION_B})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Position {}
+
+ /**
+ * LNB position is not defined.
+ */
+ public static final int POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED;
+ /**
+ * Position A of two-band LNBs
+ */
+ public static final int POSITION_A = Constants.LnbPosition.POSITION_A;
+ /**
+ * Position B of two-band LNBs
+ */
+ public static final int POSITION_B = Constants.LnbPosition.POSITION_B;
+
+ int mId;
+ LnbCallback mCallback;
+ Context mContext;
+
+ private native int nativeSetVoltage(int voltage);
+ private native int nativeSetTone(int tone);
+ private native int nativeSetSatellitePosition(int position);
+ private native int nativeSendDiseqcMessage(byte[] message);
+ private native int nativeClose();
+
+ Lnb(int id) {
+ mId = id;
+ }
+
+ /** @hide */
+ public void setCallback(@Nullable LnbCallback callback) {
+ mCallback = callback;
+ if (mCallback == null) {
+ return;
+ }
+ }
+
+ /**
+ * Sets the LNB's power voltage.
+ *
+ * @param voltage the power voltage constant the Lnb to use.
+ * @return result status of the operation.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
+ public int setVoltage(@Voltage int voltage) {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeSetVoltage(voltage);
+ }
+
+ /**
+ * Sets the LNB's tone mode.
+ *
+ * @param tone the tone mode the Lnb to use.
+ * @return result status of the operation.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
+ public int setTone(@Tone int tone) {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeSetTone(tone);
+ }
+
+ /**
+ * Selects the LNB's position.
+ *
+ * @param position the position the Lnb to use.
+ * @return result status of the operation.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
+ public int setSatellitePosition(@Position int position) {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeSetSatellitePosition(position);
+ }
+
+ /**
+ * Sends DiSEqC (Digital Satellite Equipment Control) message.
+ *
+ * The response message from the device comes back through callback onDiseqcMessage.
+ *
+ * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus
+ * Functional Specification Version 4.2.
+ *
+ * @return result status of the operation.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
+ public int sendDiseqcMessage(@NonNull byte[] message) {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeSendDiseqcMessage(message);
+ }
+
+ /**
+ * Releases the LNB instance.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ public void close() {
+ TunerUtils.checkTunerPermission(mContext);
+ nativeClose();
+ }
+}
diff --git a/media/java/android/media/tv/tuner/LnbCallback.java b/media/java/android/media/tv/tuner/LnbCallback.java
new file mode 100644
index 0000000..99bbf86
--- /dev/null
+++ b/media/java/android/media/tv/tuner/LnbCallback.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner;
+
+
+/**
+ * Callback interface for receiving information from LNBs.
+ *
+ * @hide
+ */
+public interface LnbCallback {
+ /**
+ * Invoked when there is a LNB event.
+ */
+ void onEvent(int lnbEventType);
+
+ /**
+ * Invoked when there is a new DiSEqC message.
+ *
+ * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite
+ * Equipment Control) message which is specified by EUTELSAT Bus Functional
+ * Specification Version 4.2.
+ */
+ void onDiseqcMessage(byte[] diseqcMessage);
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 962a7f6..6f5ba84 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -16,6 +16,7 @@
package android.media.tv.tuner;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -24,20 +25,21 @@
import android.media.tv.tuner.TunerConstants.FilterStatus;
import android.media.tv.tuner.TunerConstants.FilterSubtype;
import android.media.tv.tuner.TunerConstants.FrontendScanType;
-import android.media.tv.tuner.TunerConstants.LnbPosition;
-import android.media.tv.tuner.TunerConstants.LnbTone;
-import android.media.tv.tuner.TunerConstants.LnbVoltage;
import android.media.tv.tuner.TunerConstants.Result;
+import android.media.tv.tuner.dvr.Dvr;
import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
import android.media.tv.tuner.filter.FilterEvent;
+import android.media.tv.tuner.filter.TimeFilter;
import android.media.tv.tuner.frontend.FrontendCallback;
import android.media.tv.tuner.frontend.FrontendInfo;
import android.media.tv.tuner.frontend.FrontendStatus;
+import android.media.tv.tuner.frontend.ScanCallback;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* This class is used to interact with hardware tuners devices.
@@ -71,6 +73,10 @@
private List<Integer> mLnbIds;
private Lnb mLnb;
+ @Nullable
+ private ScanCallback mScanCallback;
+ @Nullable
+ private Executor mScanCallbackExecutor;
/**
* Constructs a Tuner instance.
@@ -131,29 +137,10 @@
private static native DemuxCapabilities nativeGetDemuxCapabilities();
- /**
- * LNB Callback.
- *
- * @hide
- */
- public interface LnbCallback {
- /**
- * Invoked when there is a LNB event.
- */
- void onEvent(int lnbEventType);
-
- /**
- * Invoked when there is a new DiSEqC message.
- *
- * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite
- * Equipment Control) message which is specified by EUTELSAT Bus Functional
- * Specification Version 4.2.
- */
- void onDiseqcMessage(byte[] diseqcMessage);
- }
/**
* Callback interface for receiving information from the corresponding filters.
+ * TODO: remove
*/
public interface FilterCallback {
/**
@@ -172,22 +159,6 @@
void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
}
- /**
- * DVR Callback.
- *
- * @hide
- */
- public interface DvrCallback {
- /**
- * Invoked when record status changed.
- */
- void onRecordStatus(int status);
- /**
- * Invoked when playback status changed.
- */
- void onPlaybackStatus(int status);
- }
-
@Nullable
private EventHandler createEventHandler() {
Looper looper;
@@ -219,11 +190,6 @@
}
break;
}
- case MSG_ON_LNB_EVENT: {
- if (mLnb != null && mLnb.mCallback != null) {
- mLnb.mCallback.onEvent(msg.arg1);
- }
- }
default:
// fall through
}
@@ -237,29 +203,6 @@
private Frontend(int id) {
mId = id;
}
-
- public void setCallback(@Nullable FrontendCallback callback, @Nullable Handler handler) {
- mCallback = callback;
-
- if (mCallback == null) {
- return;
- }
-
- if (handler == null) {
- // use default looper if handler is null
- if (mHandler == null) {
- mHandler = createEventHandler();
- }
- return;
- }
-
- Looper looper = handler.getLooper();
- if (mHandler != null && mHandler.getLooper() == looper) {
- // the same looper. reuse mHandler
- return;
- }
- mHandler = new EventHandler(looper);
- }
}
/**
@@ -292,18 +235,32 @@
* Scan channels.
* @hide
*/
- public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType) {
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType,
+ @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
+ mScanCallback = scanCallback;
+ mScanCallbackExecutor = executor;
return nativeScan(settings.getType(), settings, scanType);
}
/**
* Stops a previous scanning.
*
- * If the method completes successfully, the frontend stop previous scanning.
+ * <p>
+ * The {@link ScanCallback} and it's {@link Executor} will be removed.
+ *
+ * <p>
+ * If the method completes successfully, the frontend stopped previous scanning.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
public int stopScan() {
- return nativeStopScan();
+ TunerUtils.checkTunerPermission(mContext);
+ int retVal = nativeStopScan();
+ mScanCallback = null;
+ mScanCallbackExecutor = null;
+ return retVal;
}
/**
@@ -423,8 +380,14 @@
return mFrontend.mId;
}
- /** @hide */
- private static DemuxCapabilities getDemuxCapabilities() {
+ /**
+ * Gets Demux capabilities.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Nullable
+ public static DemuxCapabilities getDemuxCapabilities(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
return nativeGetDemuxCapabilities();
}
@@ -474,102 +437,6 @@
return filter;
}
- /**
- * Open a time filter instance.
- *
- * It is used to open time filter of demux.
- *
- * @return a time filter instance.
- * @hide
- */
- public TimeFilter openTimeFilter() {
- return nativeOpenTimeFilter();
- }
-
- /** @hide */
- public class Lnb {
- private int mId;
- private LnbCallback mCallback;
-
- private native int nativeSetVoltage(int voltage);
- private native int nativeSetTone(int tone);
- private native int nativeSetSatellitePosition(int position);
- private native int nativeSendDiseqcMessage(byte[] message);
- private native int nativeClose();
-
- private Lnb(int id) {
- mId = id;
- }
-
- public void setCallback(@Nullable LnbCallback callback) {
- mCallback = callback;
- if (mCallback == null) {
- return;
- }
- if (mHandler == null) {
- mHandler = createEventHandler();
- }
- }
-
- /**
- * Sets the LNB's power voltage.
- *
- * @param voltage the power voltage the Lnb to use.
- * @return result status of the operation.
- */
- @Result
- public int setVoltage(@LnbVoltage int voltage) {
- return nativeSetVoltage(voltage);
- }
-
- /**
- * Sets the LNB's tone mode.
- *
- * @param tone the tone mode the Lnb to use.
- * @return result status of the operation.
- */
- @Result
- public int setTone(@LnbTone int tone) {
- return nativeSetTone(tone);
- }
-
- /**
- * Selects the LNB's position.
- *
- * @param position the position the Lnb to use.
- * @return result status of the operation.
- */
- @Result
- public int setSatellitePosition(@LnbPosition int position) {
- return nativeSetSatellitePosition(position);
- }
-
- /**
- * Sends DiSEqC (Digital Satellite Equipment Control) message.
- *
- * The response message from the device comes back through callback onDiseqcMessage.
- *
- * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus
- * Functional Specification Version 4.2.
- *
- * @return result status of the operation.
- */
- @Result
- public int sendDiseqcMessage(byte[] message) {
- return nativeSendDiseqcMessage(message);
- }
-
- /**
- * Releases the LNB instance
- *
- * @return result status of the operation.
- */
- @Result
- public int close() {
- return nativeClose();
- }
- }
-
private List<Integer> getLnbIds() {
mLnbIds = nativeGetLnbIds();
return mLnbIds;
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index bbaa518..202b979 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -20,6 +20,11 @@
import android.annotation.LongDef;
import android.annotation.SystemApi;
import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.frontend.DvbcFrontendSettings;
+import android.media.tv.tuner.frontend.DvbsFrontendSettings;
+import android.media.tv.tuner.frontend.Isdbs3FrontendSettings;
+import android.media.tv.tuner.frontend.IsdbsFrontendSettings;
+import android.media.tv.tuner.frontend.IsdbtFrontendSettings;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -38,35 +43,6 @@
/** @hide */
- @IntDef({FRONTEND_TYPE_UNDEFINED, FRONTEND_TYPE_ANALOG, FRONTEND_TYPE_ATSC, FRONTEND_TYPE_ATSC3,
- FRONTEND_TYPE_DVBC, FRONTEND_TYPE_DVBS, FRONTEND_TYPE_DVBT, FRONTEND_TYPE_ISDBS,
- FRONTEND_TYPE_ISDBS3, FRONTEND_TYPE_ISDBT})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendType {}
-
- /** @hide */
- public static final int FRONTEND_TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_TYPE_ANALOG = Constants.FrontendType.ANALOG;
- /** @hide */
- public static final int FRONTEND_TYPE_ATSC = Constants.FrontendType.ATSC;
- /** @hide */
- public static final int FRONTEND_TYPE_ATSC3 = Constants.FrontendType.ATSC3;
- /** @hide */
- public static final int FRONTEND_TYPE_DVBC = Constants.FrontendType.DVBC;
- /** @hide */
- public static final int FRONTEND_TYPE_DVBS = Constants.FrontendType.DVBS;
- /** @hide */
- public static final int FRONTEND_TYPE_DVBT = Constants.FrontendType.DVBT;
- /** @hide */
- public static final int FRONTEND_TYPE_ISDBS = Constants.FrontendType.ISDBS;
- /** @hide */
- public static final int FRONTEND_TYPE_ISDBS3 = Constants.FrontendType.ISDBS3;
- /** @hide */
- public static final int FRONTEND_TYPE_ISDBT = Constants.FrontendType.ISDBT;
-
-
- /** @hide */
@IntDef({FRONTEND_EVENT_TYPE_LOCKED, FRONTEND_EVENT_TYPE_NO_SIGNAL,
FRONTEND_EVENT_TYPE_LOST_LOCK})
@Retention(RetentionPolicy.SOURCE)
@@ -80,54 +56,6 @@
/** @hide */
- @IntDef({DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV})
- @Retention(RetentionPolicy.SOURCE)
- public @interface DataFormat {}
- /** @hide */
- public static final int DATA_FORMAT_TS = Constants.DataFormat.TS;
- /** @hide */
- public static final int DATA_FORMAT_PES = Constants.DataFormat.PES;
- /** @hide */
- public static final int DATA_FORMAT_ES = Constants.DataFormat.ES;
- /** @hide */
- public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV;
-
-
- /** @hide */
- @IntDef({DEMUX_T_PID, DEMUX_MMPT_PID})
- @Retention(RetentionPolicy.SOURCE)
- public @interface DemuxPidType {}
- /** @hide */
- public static final int DEMUX_T_PID = 1;
- /** @hide */
- public static final int DEMUX_MMPT_PID = 2;
-
- /** @hide */
- @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})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendSettingsType {}
- /** @hide */
- public static final int FRONTEND_SETTINGS_ANALOG = 1;
- /** @hide */
- public static final int FRONTEND_SETTINGS_ATSC = 2;
- /** @hide */
- public static final int FRONTEND_SETTINGS_ATSC3 = 3;
- /** @hide */
- public static final int FRONTEND_SETTINGS_DVBS = 4;
- /** @hide */
- public static final int FRONTEND_SETTINGS_DVBC = 5;
- /** @hide */
- public static final int FRONTEND_SETTINGS_DVBT = 6;
- /** @hide */
- public static final int FRONTEND_SETTINGS_ISDBS = 7;
- /** @hide */
- public static final int FRONTEND_SETTINGS_ISDBS3 = 8;
- /** @hide */
- public static final int FRONTEND_SETTINGS_ISDBT = 9;
-
- /** @hide */
@IntDef({FILTER_SUBTYPE_UNDEFINED, FILTER_SUBTYPE_SECTION, FILTER_SUBTYPE_PES,
FILTER_SUBTYPE_AUDIO, FILTER_SUBTYPE_VIDEO, FILTER_SUBTYPE_DOWNLOAD,
FILTER_SUBTYPE_RECORD, FILTER_SUBTYPE_TS, FILTER_SUBTYPE_PCR, FILTER_SUBTYPE_TEMI,
@@ -171,8 +99,8 @@
public static final int FILTER_SUBTYPE_PTP = 16;
/** @hide */
- @IntDef({FILTER_STATUS_DATA_READY, FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER,
- FILTER_STATUS_OVERFLOW})
+ @IntDef(flag = true, prefix = "FILTER_STATUS_", value = {FILTER_STATUS_DATA_READY,
+ FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER, FILTER_STATUS_OVERFLOW})
@Retention(RetentionPolicy.SOURCE)
public @interface FilterStatus {}
@@ -201,6 +129,187 @@
*/
public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
+ /**
+ * Indexes can be tagged through TS (Transport Stream) header.
+ *
+ * @hide
+ */
+ @IntDef(flag = true, value = {TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR,
+ TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED,
+ TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR,
+ TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR, TS_INDEX_PCR_FLAG,
+ TS_INDEX_OPCR_FLAG, TS_INDEX_SPLICING_POINT_FLAG, TS_INDEX_PRIVATE_DATA,
+ TS_INDEX_ADAPTATION_EXTENSION_FLAG})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TsIndex {}
+
+ /**
+ * TS index FIRST_PACKET.
+ * @hide
+ */
+ public static final int TS_INDEX_FIRST_PACKET = Constants.DemuxTsIndex.FIRST_PACKET;
+ /**
+ * TS index PAYLOAD_UNIT_START_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR =
+ Constants.DemuxTsIndex.PAYLOAD_UNIT_START_INDICATOR;
+ /**
+ * TS index CHANGE_TO_NOT_SCRAMBLED.
+ * @hide
+ */
+ public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED =
+ Constants.DemuxTsIndex.CHANGE_TO_NOT_SCRAMBLED;
+ /**
+ * TS index CHANGE_TO_EVEN_SCRAMBLED.
+ * @hide
+ */
+ public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED =
+ Constants.DemuxTsIndex.CHANGE_TO_EVEN_SCRAMBLED;
+ /**
+ * TS index CHANGE_TO_ODD_SCRAMBLED.
+ * @hide
+ */
+ public static final int TS_INDEX_CHANGE_TO_ODD_SCRAMBLED =
+ Constants.DemuxTsIndex.CHANGE_TO_ODD_SCRAMBLED;
+ /**
+ * TS index DISCONTINUITY_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_DISCONTINUITY_INDICATOR =
+ Constants.DemuxTsIndex.DISCONTINUITY_INDICATOR;
+ /**
+ * TS index RANDOM_ACCESS_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_RANDOM_ACCESS_INDICATOR =
+ Constants.DemuxTsIndex.RANDOM_ACCESS_INDICATOR;
+ /**
+ * TS index PRIORITY_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_PRIORITY_INDICATOR = Constants.DemuxTsIndex.PRIORITY_INDICATOR;
+ /**
+ * TS index PCR_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_PCR_FLAG = Constants.DemuxTsIndex.PCR_FLAG;
+ /**
+ * TS index OPCR_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_OPCR_FLAG = Constants.DemuxTsIndex.OPCR_FLAG;
+ /**
+ * TS index SPLICING_POINT_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_SPLICING_POINT_FLAG =
+ Constants.DemuxTsIndex.SPLICING_POINT_FLAG;
+ /**
+ * TS index PRIVATE_DATA.
+ * @hide
+ */
+ public static final int TS_INDEX_PRIVATE_DATA = Constants.DemuxTsIndex.PRIVATE_DATA;
+ /**
+ * TS index ADAPTATION_EXTENSION_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG =
+ Constants.DemuxTsIndex.ADAPTATION_EXTENSION_FLAG;
+
+ /**
+ * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream)
+ * according to ISO/IEC 13818-1.
+ * @hide
+ */
+ @IntDef(flag = true, value = {SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, SC_INDEX_B_FRAME,
+ SC_INDEX_SEQUENCE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScIndex {}
+
+ /**
+ * SC index for a new I-frame.
+ * @hide
+ */
+ public static final int SC_INDEX_I_FRAME = Constants.DemuxScIndex.I_FRAME;
+ /**
+ * SC index for a new P-frame.
+ * @hide
+ */
+ public static final int SC_INDEX_P_FRAME = Constants.DemuxScIndex.P_FRAME;
+ /**
+ * SC index for a new B-frame.
+ * @hide
+ */
+ public static final int SC_INDEX_B_FRAME = Constants.DemuxScIndex.B_FRAME;
+ /**
+ * SC index for a new sequence.
+ * @hide
+ */
+ public static final int SC_INDEX_SEQUENCE = Constants.DemuxScIndex.SEQUENCE;
+
+
+ /**
+ * Indexes can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
+ *
+ * @hide
+ */
+ @IntDef(flag = true,
+ value = {SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+ SC_HEVC_INDEX_SLICE_BLA_W_RADL, SC_HEVC_INDEX_SLICE_BLA_N_LP,
+ SC_HEVC_INDEX_SLICE_IDR_W_RADL, SC_HEVC_INDEX_SLICE_IDR_N_LP,
+ SC_HEVC_INDEX_SLICE_TRAIL_CRA})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScHevcIndex {}
+
+ /**
+ * SC HEVC index SPS.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SPS = Constants.DemuxScHevcIndex.SPS;
+ /**
+ * SC HEVC index AUD.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_AUD = Constants.DemuxScHevcIndex.AUD;
+ /**
+ * SC HEVC index SLICE_CE_BLA_W_LP.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_CE_BLA_W_LP =
+ Constants.DemuxScHevcIndex.SLICE_CE_BLA_W_LP;
+ /**
+ * SC HEVC index SLICE_BLA_W_RADL.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_BLA_W_RADL =
+ Constants.DemuxScHevcIndex.SLICE_BLA_W_RADL;
+ /**
+ * SC HEVC index SLICE_BLA_N_LP.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_BLA_N_LP =
+ Constants.DemuxScHevcIndex.SLICE_BLA_N_LP;
+ /**
+ * SC HEVC index SLICE_IDR_W_RADL.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_IDR_W_RADL =
+ Constants.DemuxScHevcIndex.SLICE_IDR_W_RADL;
+ /**
+ * SC HEVC index SLICE_IDR_N_LP.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_IDR_N_LP =
+ Constants.DemuxScHevcIndex.SLICE_IDR_N_LP;
+ /**
+ * SC HEVC index SLICE_TRAIL_CRA.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA =
+ Constants.DemuxScHevcIndex.SLICE_TRAIL_CRA;
+
+
/** @hide */
@IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND})
@Retention(RetentionPolicy.SOURCE)
@@ -561,682 +670,52 @@
/** @hide */
- @IntDef({DVBC_MODULATION_UNDEFINED, DVBC_MODULATION_AUTO, DVBC_MODULATION_MOD_16QAM,
- DVBC_MODULATION_MOD_32QAM, DVBC_MODULATION_MOD_64QAM, DVBC_MODULATION_MOD_128QAM,
- DVBC_MODULATION_MOD_256QAM, DVBS_MODULATION_UNDEFINED, DVBS_MODULATION_AUTO,
- DVBS_MODULATION_MOD_QPSK, DVBS_MODULATION_MOD_8PSK, DVBS_MODULATION_MOD_16QAM,
- DVBS_MODULATION_MOD_16PSK, DVBS_MODULATION_MOD_32PSK, DVBS_MODULATION_MOD_ACM,
- DVBS_MODULATION_MOD_8APSK, DVBS_MODULATION_MOD_16APSK, DVBS_MODULATION_MOD_32APSK,
- DVBS_MODULATION_MOD_64APSK, DVBS_MODULATION_MOD_128APSK, DVBS_MODULATION_MOD_256APSK,
- DVBS_MODULATION_MOD_RESERVED, ISDBS_MODULATION_UNDEFINED, ISDBS_MODULATION_AUTO,
- ISDBS_MODULATION_MOD_BPSK, ISDBS_MODULATION_MOD_QPSK, ISDBS_MODULATION_MOD_TC8PSK,
- ISDBS3_MODULATION_UNDEFINED, ISDBS3_MODULATION_AUTO, ISDBS3_MODULATION_MOD_BPSK,
- ISDBS3_MODULATION_MOD_QPSK, ISDBS3_MODULATION_MOD_8PSK, ISDBS3_MODULATION_MOD_16APSK,
- ISDBS3_MODULATION_MOD_32APSK, ISDBT_MODULATION_UNDEFINED, ISDBT_MODULATION_AUTO,
- ISDBT_MODULATION_MOD_DQPSK, ISDBT_MODULATION_MOD_QPSK, ISDBT_MODULATION_MOD_16QAM,
- ISDBT_MODULATION_MOD_64QAM})
+ @IntDef(value = {
+ DvbcFrontendSettings.MODULATION_UNDEFINED,
+ DvbcFrontendSettings.MODULATION_AUTO,
+ DvbcFrontendSettings.MODULATION_MOD_16QAM,
+ DvbcFrontendSettings.MODULATION_MOD_32QAM,
+ DvbcFrontendSettings.MODULATION_MOD_64QAM,
+ DvbcFrontendSettings.MODULATION_MOD_128QAM,
+ DvbcFrontendSettings.MODULATION_MOD_256QAM,
+ DvbsFrontendSettings.MODULATION_UNDEFINED,
+ DvbsFrontendSettings.MODULATION_AUTO,
+ DvbsFrontendSettings.MODULATION_MOD_QPSK,
+ DvbsFrontendSettings.MODULATION_MOD_8PSK,
+ DvbsFrontendSettings.MODULATION_MOD_16QAM,
+ DvbsFrontendSettings.MODULATION_MOD_16PSK,
+ DvbsFrontendSettings.MODULATION_MOD_32PSK,
+ DvbsFrontendSettings.MODULATION_MOD_ACM,
+ DvbsFrontendSettings.MODULATION_MOD_8APSK,
+ DvbsFrontendSettings.MODULATION_MOD_16APSK,
+ DvbsFrontendSettings.MODULATION_MOD_32APSK,
+ DvbsFrontendSettings.MODULATION_MOD_64APSK,
+ DvbsFrontendSettings.MODULATION_MOD_128APSK,
+ DvbsFrontendSettings.MODULATION_MOD_256APSK,
+ DvbsFrontendSettings.MODULATION_MOD_RESERVED,
+ IsdbsFrontendSettings.MODULATION_UNDEFINED,
+ IsdbsFrontendSettings.MODULATION_AUTO,
+ IsdbsFrontendSettings.MODULATION_MOD_BPSK,
+ IsdbsFrontendSettings.MODULATION_MOD_QPSK,
+ IsdbsFrontendSettings.MODULATION_MOD_TC8PSK,
+ Isdbs3FrontendSettings.MODULATION_UNDEFINED,
+ Isdbs3FrontendSettings.MODULATION_AUTO,
+ Isdbs3FrontendSettings.MODULATION_MOD_BPSK,
+ Isdbs3FrontendSettings.MODULATION_MOD_QPSK,
+ Isdbs3FrontendSettings.MODULATION_MOD_8PSK,
+ Isdbs3FrontendSettings.MODULATION_MOD_16APSK,
+ Isdbs3FrontendSettings.MODULATION_MOD_32APSK,
+ IsdbtFrontendSettings.MODULATION_UNDEFINED,
+ IsdbtFrontendSettings.MODULATION_AUTO,
+ IsdbtFrontendSettings.MODULATION_MOD_DQPSK,
+ IsdbtFrontendSettings.MODULATION_MOD_QPSK,
+ IsdbtFrontendSettings.MODULATION_MOD_16QAM,
+ IsdbtFrontendSettings.MODULATION_MOD_64QAM})
@Retention(RetentionPolicy.SOURCE)
public @interface FrontendModulation {}
- /** @hide */
- public static final int DVBC_MODULATION_UNDEFINED = Constants.FrontendDvbcModulation.UNDEFINED;
- /** @hide */
- public static final int DVBC_MODULATION_AUTO = Constants.FrontendDvbcModulation.AUTO;
- /** @hide */
- public static final int DVBC_MODULATION_MOD_16QAM = Constants.FrontendDvbcModulation.MOD_16QAM;
- /** @hide */
- public static final int DVBC_MODULATION_MOD_32QAM = Constants.FrontendDvbcModulation.MOD_32QAM;
- /** @hide */
- public static final int DVBC_MODULATION_MOD_64QAM = Constants.FrontendDvbcModulation.MOD_64QAM;
- /** @hide */
- public static final int DVBC_MODULATION_MOD_128QAM =
- Constants.FrontendDvbcModulation.MOD_128QAM;
- /** @hide */
- public static final int DVBC_MODULATION_MOD_256QAM =
- Constants.FrontendDvbcModulation.MOD_256QAM;
- /** @hide */
- public static final int DVBS_MODULATION_UNDEFINED = Constants.FrontendDvbsModulation.UNDEFINED;
- /** @hide */
- public static final int DVBS_MODULATION_AUTO = Constants.FrontendDvbsModulation.AUTO;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_QPSK = Constants.FrontendDvbsModulation.MOD_QPSK;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_8PSK = Constants.FrontendDvbsModulation.MOD_8PSK;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_16QAM = Constants.FrontendDvbsModulation.MOD_16QAM;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_16PSK = Constants.FrontendDvbsModulation.MOD_16PSK;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_32PSK = Constants.FrontendDvbsModulation.MOD_32PSK;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_ACM = Constants.FrontendDvbsModulation.MOD_ACM;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_8APSK = Constants.FrontendDvbsModulation.MOD_8APSK;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_16APSK =
- Constants.FrontendDvbsModulation.MOD_16APSK;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_32APSK =
- Constants.FrontendDvbsModulation.MOD_32APSK;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_64APSK =
- Constants.FrontendDvbsModulation.MOD_64APSK;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_128APSK =
- Constants.FrontendDvbsModulation.MOD_128APSK;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_256APSK =
- Constants.FrontendDvbsModulation.MOD_256APSK;
- /** @hide */
- public static final int DVBS_MODULATION_MOD_RESERVED =
- Constants.FrontendDvbsModulation.MOD_RESERVED;
- /** @hide */
- public static final int ISDBS_MODULATION_UNDEFINED =
- Constants.FrontendIsdbsModulation.UNDEFINED;
- /** @hide */
- public static final int ISDBS_MODULATION_AUTO = Constants.FrontendIsdbsModulation.AUTO;
- /** @hide */
- public static final int ISDBS_MODULATION_MOD_BPSK = Constants.FrontendIsdbsModulation.MOD_BPSK;
- /** @hide */
- public static final int ISDBS_MODULATION_MOD_QPSK = Constants.FrontendIsdbsModulation.MOD_QPSK;
- /** @hide */
- public static final int ISDBS_MODULATION_MOD_TC8PSK =
- Constants.FrontendIsdbsModulation.MOD_TC8PSK;
- /** @hide */
- public static final int ISDBS3_MODULATION_UNDEFINED =
- Constants.FrontendIsdbs3Modulation.UNDEFINED;
- /** @hide */
- public static final int ISDBS3_MODULATION_AUTO = Constants.FrontendIsdbs3Modulation.AUTO;
- /** @hide */
- public static final int ISDBS3_MODULATION_MOD_BPSK =
- Constants.FrontendIsdbs3Modulation.MOD_BPSK;
- /** @hide */
- public static final int ISDBS3_MODULATION_MOD_QPSK =
- Constants.FrontendIsdbs3Modulation.MOD_QPSK;
- /** @hide */
- public static final int ISDBS3_MODULATION_MOD_8PSK =
- Constants.FrontendIsdbs3Modulation.MOD_8PSK;
- /** @hide */
- public static final int ISDBS3_MODULATION_MOD_16APSK =
- Constants.FrontendIsdbs3Modulation.MOD_16APSK;
- /** @hide */
- public static final int ISDBS3_MODULATION_MOD_32APSK =
- Constants.FrontendIsdbs3Modulation.MOD_32APSK;
- /** @hide */
- public static final int ISDBT_MODULATION_UNDEFINED =
- Constants.FrontendIsdbtModulation.UNDEFINED;
- /** @hide */
- public static final int ISDBT_MODULATION_AUTO = Constants.FrontendIsdbtModulation.AUTO;
- /** @hide */
- public static final int ISDBT_MODULATION_MOD_DQPSK =
- Constants.FrontendIsdbtModulation.MOD_DQPSK;
- /** @hide */
- public static final int ISDBT_MODULATION_MOD_QPSK = Constants.FrontendIsdbtModulation.MOD_QPSK;
- /** @hide */
- public static final int ISDBT_MODULATION_MOD_16QAM =
- Constants.FrontendIsdbtModulation.MOD_16QAM;
- /** @hide */
- public static final int ISDBT_MODULATION_MOD_64QAM =
- Constants.FrontendIsdbtModulation.MOD_64QAM;
/** @hide */
- @IntDef({SPECTRAL_INVERSION_UNDEFINED, SPECTRAL_INVERSION_NORMAL, SPECTRAL_INVERSION_INVERTED})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendDvbcSpectralInversion {}
- /** @hide */
- public static final int SPECTRAL_INVERSION_UNDEFINED =
- Constants.FrontendDvbcSpectralInversion.UNDEFINED;
- /** @hide */
- public static final int SPECTRAL_INVERSION_NORMAL =
- Constants.FrontendDvbcSpectralInversion.NORMAL;
- /** @hide */
- public static final int SPECTRAL_INVERSION_INVERTED =
- Constants.FrontendDvbcSpectralInversion.INVERTED;
-
-
- /** @hide */
- @IntDef({HIERARCHY_UNDEFINED, HIERARCHY_AUTO, HIERARCHY_NON_NATIVE, HIERARCHY_1_NATIVE,
- HIERARCHY_2_NATIVE, HIERARCHY_4_NATIVE, HIERARCHY_NON_INDEPTH, HIERARCHY_1_INDEPTH,
- HIERARCHY_2_INDEPTH, HIERARCHY_4_INDEPTH})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendDvbtHierarchy {}
- /** @hide */
- public static final int HIERARCHY_UNDEFINED = Constants.FrontendDvbtHierarchy.UNDEFINED;
- /** @hide */
- public static final int HIERARCHY_AUTO = Constants.FrontendDvbtHierarchy.AUTO;
- /** @hide */
- public static final int HIERARCHY_NON_NATIVE =
- Constants.FrontendDvbtHierarchy.HIERARCHY_NON_NATIVE;
- /** @hide */
- public static final int HIERARCHY_1_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_1_NATIVE;
- /** @hide */
- public static final int HIERARCHY_2_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_2_NATIVE;
- /** @hide */
- public static final int HIERARCHY_4_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_4_NATIVE;
- /** @hide */
- public static final int HIERARCHY_NON_INDEPTH =
- Constants.FrontendDvbtHierarchy.HIERARCHY_NON_INDEPTH;
- /** @hide */
- public static final int HIERARCHY_1_INDEPTH =
- Constants.FrontendDvbtHierarchy.HIERARCHY_1_INDEPTH;
- /** @hide */
- public static final int HIERARCHY_2_INDEPTH =
- Constants.FrontendDvbtHierarchy.HIERARCHY_2_INDEPTH;
- /** @hide */
- public static final int HIERARCHY_4_INDEPTH =
- Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH;
-
- /** @hide */
- @IntDef({FRONTEND_ANALOG_TYPE_UNDEFINED, FRONTEND_ANALOG_TYPE_PAL, FRONTEND_ANALOG_TYPE_SECAM,
- FRONTEND_ANALOG_TYPE_NTSC})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendAnalogType {}
- /** @hide */
- public static final int FRONTEND_ANALOG_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ANALOG_TYPE_PAL = Constants.FrontendAnalogType.PAL;
- /** @hide */
- public static final int FRONTEND_ANALOG_TYPE_SECAM = Constants.FrontendAnalogType.SECAM;
- /** @hide */
- public static final int FRONTEND_ANALOG_TYPE_NTSC = Constants.FrontendAnalogType.NTSC;
-
- /** @hide */
- @IntDef({FRONTEND_ANALOG_SIF_UNDEFINED, FRONTEND_ANALOG_SIF_BG, FRONTEND_ANALOG_SIF_BG_A2,
- FRONTEND_ANALOG_SIF_BG_NICAM, FRONTEND_ANALOG_SIF_I, FRONTEND_ANALOG_SIF_DK,
- FRONTEND_ANALOG_SIF_DK1, FRONTEND_ANALOG_SIF_DK2, FRONTEND_ANALOG_SIF_DK3,
- FRONTEND_ANALOG_SIF_DK_NICAM, FRONTEND_ANALOG_SIF_L, FRONTEND_ANALOG_SIF_M,
- FRONTEND_ANALOG_SIF_M_BTSC, FRONTEND_ANALOG_SIF_M_A2, FRONTEND_ANALOG_SIF_M_EIA_J,
- FRONTEND_ANALOG_SIF_I_NICAM, FRONTEND_ANALOG_SIF_L_NICAM, FRONTEND_ANALOG_SIF_L_PRIME})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendAnalogSifStandard {}
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_UNDEFINED =
- Constants.FrontendAnalogSifStandard.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_BG = Constants.FrontendAnalogSifStandard.BG;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_BG_NICAM =
- Constants.FrontendAnalogSifStandard.BG_NICAM;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_I = Constants.FrontendAnalogSifStandard.I;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_DK = Constants.FrontendAnalogSifStandard.DK;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_DK_NICAM =
- Constants.FrontendAnalogSifStandard.DK_NICAM;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_L = Constants.FrontendAnalogSifStandard.L;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_M = Constants.FrontendAnalogSifStandard.M;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_M_EIA_J =
- Constants.FrontendAnalogSifStandard.M_EIA_J;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_I_NICAM =
- Constants.FrontendAnalogSifStandard.I_NICAM;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_L_NICAM =
- Constants.FrontendAnalogSifStandard.L_NICAM;
- /** @hide */
- public static final int FRONTEND_ANALOG_SIF_L_PRIME =
- Constants.FrontendAnalogSifStandard.L_PRIME;
-
- /** @hide */
- @IntDef({FRONTEND_ATSC_MODULATION_UNDEFINED, FRONTEND_ATSC_MODULATION_AUTO,
- FRONTEND_ATSC_MODULATION_MOD_8VSB, FRONTEND_ATSC_MODULATION_MOD_16VSB})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendAtscModulation {}
- /** @hide */
- public static final int FRONTEND_ATSC_MODULATION_UNDEFINED =
- Constants.FrontendAtscModulation.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ATSC_MODULATION_AUTO = Constants.FrontendAtscModulation.AUTO;
- /** @hide */
- public static final int FRONTEND_ATSC_MODULATION_MOD_8VSB =
- Constants.FrontendAtscModulation.MOD_8VSB;
- /** @hide */
- public static final int FRONTEND_ATSC_MODULATION_MOD_16VSB =
- Constants.FrontendAtscModulation.MOD_16VSB;
-
- /** @hide */
- @IntDef({FRONTEND_ATSC3_BANDWIDTH_UNDEFINED, FRONTEND_ATSC3_BANDWIDTH_AUTO,
- FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ, FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ,
- FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendAtsc3Bandwidth {}
- /** @hide */
- public static final int FRONTEND_ATSC3_BANDWIDTH_UNDEFINED =
- Constants.FrontendAtsc3Bandwidth.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ATSC3_BANDWIDTH_AUTO = Constants.FrontendAtsc3Bandwidth.AUTO;
- /** @hide */
- public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ =
- Constants.FrontendAtsc3Bandwidth.BANDWIDTH_6MHZ;
- /** @hide */
- public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ =
- Constants.FrontendAtsc3Bandwidth.BANDWIDTH_7MHZ;
- /** @hide */
- public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ =
- Constants.FrontendAtsc3Bandwidth.BANDWIDTH_8MHZ;
-
- /** @hide */
- @IntDef({FRONTEND_ATSC3_MODULATION_UNDEFINED, FRONTEND_ATSC3_MODULATION_AUTO,
- FRONTEND_ATSC3_MODULATION_MOD_QPSK, FRONTEND_ATSC3_MODULATION_MOD_16QAM,
- FRONTEND_ATSC3_MODULATION_MOD_64QAM, FRONTEND_ATSC3_MODULATION_MOD_256QAM,
- FRONTEND_ATSC3_MODULATION_MOD_1024QAM, FRONTEND_ATSC3_MODULATION_MOD_4096QAM})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendAtsc3Modulation {}
- /** @hide */
- public static final int FRONTEND_ATSC3_MODULATION_UNDEFINED =
- Constants.FrontendAtsc3Modulation.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ATSC3_MODULATION_AUTO = Constants.FrontendAtsc3Modulation.AUTO;
- /** @hide */
- public static final int FRONTEND_ATSC3_MODULATION_MOD_QPSK =
- Constants.FrontendAtsc3Modulation.MOD_QPSK;
- /** @hide */
- public static final int FRONTEND_ATSC3_MODULATION_MOD_16QAM =
- Constants.FrontendAtsc3Modulation.MOD_16QAM;
- /** @hide */
- public static final int FRONTEND_ATSC3_MODULATION_MOD_64QAM =
- Constants.FrontendAtsc3Modulation.MOD_64QAM;
- /** @hide */
- public static final int FRONTEND_ATSC3_MODULATION_MOD_256QAM =
- Constants.FrontendAtsc3Modulation.MOD_256QAM;
- /** @hide */
- public static final int FRONTEND_ATSC3_MODULATION_MOD_1024QAM =
- Constants.FrontendAtsc3Modulation.MOD_1024QAM;
- /** @hide */
- public static final int FRONTEND_ATSC3_MODULATION_MOD_4096QAM =
- Constants.FrontendAtsc3Modulation.MOD_4096QAM;
-
- /** @hide */
- @IntDef({FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED,
- FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO, FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI,
- FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendAtsc3TimeInterleaveMode {}
- /** @hide */
- public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED =
- Constants.FrontendAtsc3TimeInterleaveMode.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO =
- Constants.FrontendAtsc3TimeInterleaveMode.AUTO;
- /** @hide */
- public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI =
- Constants.FrontendAtsc3TimeInterleaveMode.CTI;
- /** @hide */
- public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI =
- Constants.FrontendAtsc3TimeInterleaveMode.HTI;
-
- /** @hide */
- @IntDef({FRONTEND_ATSC3_CODERATE_UNDEFINED, FRONTEND_ATSC3_CODERATE_AUTO,
- FRONTEND_ATSC3_CODERATE_2_15, FRONTEND_ATSC3_CODERATE_3_15,
- FRONTEND_ATSC3_CODERATE_4_15, FRONTEND_ATSC3_CODERATE_5_15,
- FRONTEND_ATSC3_CODERATE_6_15, FRONTEND_ATSC3_CODERATE_7_15,
- FRONTEND_ATSC3_CODERATE_8_15, FRONTEND_ATSC3_CODERATE_9_15,
- FRONTEND_ATSC3_CODERATE_10_15, FRONTEND_ATSC3_CODERATE_11_15,
- FRONTEND_ATSC3_CODERATE_12_15, FRONTEND_ATSC3_CODERATE_13_15})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendAtsc3CodeRate {}
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_UNDEFINED =
- Constants.FrontendAtsc3CodeRate.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_AUTO = Constants.FrontendAtsc3CodeRate.AUTO;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_2_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_2_15;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_3_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_3_15;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_4_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_4_15;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_5_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_5_15;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_6_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_6_15;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_7_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_7_15;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_8_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_8_15;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_9_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_9_15;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_10_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_10_15;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_11_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_11_15;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_12_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_12_15;
- /** @hide */
- public static final int FRONTEND_ATSC3_CODERATE_13_15 =
- Constants.FrontendAtsc3CodeRate.CODERATE_13_15;
-
- /** @hide */
- @IntDef({FRONTEND_ATSC3_FEC_UNDEFINED, FRONTEND_ATSC3_FEC_AUTO, FRONTEND_ATSC3_FEC_BCH_LDPC_16K,
- FRONTEND_ATSC3_FEC_BCH_LDPC_64K, FRONTEND_ATSC3_FEC_CRC_LDPC_16K,
- FRONTEND_ATSC3_FEC_CRC_LDPC_64K, FRONTEND_ATSC3_FEC_LDPC_16K,
- FRONTEND_ATSC3_FEC_LDPC_64K})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendAtsc3Fec {}
- /** @hide */
- public static final int FRONTEND_ATSC3_FEC_UNDEFINED = Constants.FrontendAtsc3Fec.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ATSC3_FEC_AUTO = Constants.FrontendAtsc3Fec.AUTO;
- /** @hide */
- public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_16K =
- Constants.FrontendAtsc3Fec.BCH_LDPC_16K;
- /** @hide */
- public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_64K =
- Constants.FrontendAtsc3Fec.BCH_LDPC_64K;
- /** @hide */
- public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_16K =
- Constants.FrontendAtsc3Fec.CRC_LDPC_16K;
- /** @hide */
- public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_64K =
- Constants.FrontendAtsc3Fec.CRC_LDPC_64K;
- /** @hide */
- public static final int FRONTEND_ATSC3_FEC_LDPC_16K = Constants.FrontendAtsc3Fec.LDPC_16K;
- /** @hide */
- public static final int FRONTEND_ATSC3_FEC_LDPC_64K = Constants.FrontendAtsc3Fec.LDPC_64K;
-
- /** @hide */
- @IntDef({FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED,
- FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET,
- FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendAtsc3DemodOutputFormat {}
- /** @hide */
- public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED =
- Constants.FrontendAtsc3DemodOutputFormat.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET =
- Constants.FrontendAtsc3DemodOutputFormat.ATSC3_LINKLAYER_PACKET;
- /** @hide */
- public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET =
- Constants.FrontendAtsc3DemodOutputFormat.BASEBAND_PACKET;
-
- /** @hide */
- @IntDef({FRONTEND_DVBS_STANDARD_AUTO, FRONTEND_DVBS_STANDARD_S, FRONTEND_DVBS_STANDARD_S2,
- FRONTEND_DVBS_STANDARD_S2X})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendDvbsStandard {}
- /** @hide */
- public static final int FRONTEND_DVBS_STANDARD_AUTO = Constants.FrontendDvbsStandard.AUTO;
- /** @hide */
- public static final int FRONTEND_DVBS_STANDARD_S = Constants.FrontendDvbsStandard.S;
- /** @hide */
- public static final int FRONTEND_DVBS_STANDARD_S2 = Constants.FrontendDvbsStandard.S2;
- /** @hide */
- public static final int FRONTEND_DVBS_STANDARD_S2X = Constants.FrontendDvbsStandard.S2X;
-
- /** @hide */
- @IntDef({FRONTEND_DVBC_ANNEX_UNDEFINED, FRONTEND_DVBC_ANNEX_A, FRONTEND_DVBC_ANNEX_B,
- FRONTEND_DVBC_ANNEX_C})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendDvbcAnnex {}
- /** @hide */
- public static final int FRONTEND_DVBC_ANNEX_UNDEFINED = Constants.FrontendDvbcAnnex.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_DVBC_ANNEX_A = Constants.FrontendDvbcAnnex.A;
- /** @hide */
- public static final int FRONTEND_DVBC_ANNEX_B = Constants.FrontendDvbcAnnex.B;
- /** @hide */
- public static final int FRONTEND_DVBC_ANNEX_C = Constants.FrontendDvbcAnnex.C;
-
- /** @hide */
- @IntDef({FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED, FRONTEND_DVBT_TRANSMISSION_MODE_AUTO,
- FRONTEND_DVBT_TRANSMISSION_MODE_2K, FRONTEND_DVBT_TRANSMISSION_MODE_8K,
- FRONTEND_DVBT_TRANSMISSION_MODE_4K, FRONTEND_DVBT_TRANSMISSION_MODE_1K,
- FRONTEND_DVBT_TRANSMISSION_MODE_16K, FRONTEND_DVBT_TRANSMISSION_MODE_32K})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendDvbtTransmissionMode {}
- /** @hide */
- public static final int FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED =
- Constants.FrontendDvbtTransmissionMode.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_DVBT_TRANSMISSION_MODE_AUTO =
- Constants.FrontendDvbtTransmissionMode.AUTO;
- /** @hide */
- public static final int FRONTEND_DVBT_TRANSMISSION_MODE_2K =
- Constants.FrontendDvbtTransmissionMode.MODE_2K;
- /** @hide */
- public static final int FRONTEND_DVBT_TRANSMISSION_MODE_8K =
- Constants.FrontendDvbtTransmissionMode.MODE_8K;
- /** @hide */
- public static final int FRONTEND_DVBT_TRANSMISSION_MODE_4K =
- Constants.FrontendDvbtTransmissionMode.MODE_4K;
- /** @hide */
- public static final int FRONTEND_DVBT_TRANSMISSION_MODE_1K =
- Constants.FrontendDvbtTransmissionMode.MODE_1K;
- /** @hide */
- public static final int FRONTEND_DVBT_TRANSMISSION_MODE_16K =
- Constants.FrontendDvbtTransmissionMode.MODE_16K;
- /** @hide */
- public static final int FRONTEND_DVBT_TRANSMISSION_MODE_32K =
- Constants.FrontendDvbtTransmissionMode.MODE_32K;
-
- /** @hide */
- @IntDef({FRONTEND_DVBT_BANDWIDTH_UNDEFINED, FRONTEND_DVBT_BANDWIDTH_AUTO,
- FRONTEND_DVBT_BANDWIDTH_8MHZ, FRONTEND_DVBT_BANDWIDTH_7MHZ,
- FRONTEND_DVBT_BANDWIDTH_6MHZ, FRONTEND_DVBT_BANDWIDTH_5MHZ,
- FRONTEND_DVBT_BANDWIDTH_1_7MHZ, FRONTEND_DVBT_BANDWIDTH_10MHZ})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendDvbtBandwidth {}
- /** @hide */
- public static final int FRONTEND_DVBT_BANDWIDTH_UNDEFINED =
- Constants.FrontendDvbtBandwidth.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_DVBT_BANDWIDTH_AUTO = Constants.FrontendDvbtBandwidth.AUTO;
- /** @hide */
- public static final int FRONTEND_DVBT_BANDWIDTH_8MHZ =
- Constants.FrontendDvbtBandwidth.BANDWIDTH_8MHZ;
- /** @hide */
- public static final int FRONTEND_DVBT_BANDWIDTH_7MHZ =
- Constants.FrontendDvbtBandwidth.BANDWIDTH_7MHZ;
- /** @hide */
- public static final int FRONTEND_DVBT_BANDWIDTH_6MHZ =
- Constants.FrontendDvbtBandwidth.BANDWIDTH_6MHZ;
- /** @hide */
- public static final int FRONTEND_DVBT_BANDWIDTH_5MHZ =
- Constants.FrontendDvbtBandwidth.BANDWIDTH_5MHZ;
- /** @hide */
- public static final int FRONTEND_DVBT_BANDWIDTH_1_7MHZ =
- Constants.FrontendDvbtBandwidth.BANDWIDTH_1_7MHZ;
- /** @hide */
- public static final int FRONTEND_DVBT_BANDWIDTH_10MHZ =
- Constants.FrontendDvbtBandwidth.BANDWIDTH_10MHZ;
-
- /** @hide */
- @IntDef({FRONTEND_DVBT_CONSTELLATION_UNDEFINED, FRONTEND_DVBT_CONSTELLATION_AUTO,
- FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK,
- FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM,
- FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM,
- FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendDvbtConstellation {}
- /** @hide */
- public static final int FRONTEND_DVBT_CONSTELLATION_UNDEFINED =
- Constants.FrontendDvbtConstellation.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_DVBT_CONSTELLATION_AUTO =
- Constants.FrontendDvbtConstellation.AUTO;
- /** @hide */
- public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK =
- Constants.FrontendDvbtConstellation.CONSTELLATION_QPSK;
- /** @hide */
- public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM =
- Constants.FrontendDvbtConstellation.CONSTELLATION_16QAM;
- /** @hide */
- public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM =
- Constants.FrontendDvbtConstellation.CONSTELLATION_64QAM;
- /** @hide */
- public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM =
- Constants.FrontendDvbtConstellation.CONSTELLATION_256QAM;
-
- /** @hide */
- @IntDef({FRONTEND_DVBT_CODERATE_UNDEFINED, FRONTEND_DVBT_CODERATE_AUTO,
- FRONTEND_DVBT_CODERATE_1_2, FRONTEND_DVBT_CODERATE_2_3, FRONTEND_DVBT_CODERATE_3_4,
- FRONTEND_DVBT_CODERATE_5_6, FRONTEND_DVBT_CODERATE_7_8, FRONTEND_DVBT_CODERATE_3_5,
- FRONTEND_DVBT_CODERATE_4_5, FRONTEND_DVBT_CODERATE_6_7, FRONTEND_DVBT_CODERATE_8_9})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendDvbtCoderate {}
- /** @hide */
- public static final int FRONTEND_DVBT_CODERATE_UNDEFINED =
- Constants.FrontendDvbtCoderate.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_DVBT_CODERATE_AUTO = Constants.FrontendDvbtCoderate.AUTO;
- /** @hide */
- public static final int FRONTEND_DVBT_CODERATE_1_2 =
- Constants.FrontendDvbtCoderate.CODERATE_1_2;
- /** @hide */
- public static final int FRONTEND_DVBT_CODERATE_2_3 =
- Constants.FrontendDvbtCoderate.CODERATE_2_3;
- /** @hide */
- public static final int FRONTEND_DVBT_CODERATE_3_4 =
- Constants.FrontendDvbtCoderate.CODERATE_3_4;
- /** @hide */
- public static final int FRONTEND_DVBT_CODERATE_5_6 =
- Constants.FrontendDvbtCoderate.CODERATE_5_6;
- /** @hide */
- public static final int FRONTEND_DVBT_CODERATE_7_8 =
- Constants.FrontendDvbtCoderate.CODERATE_7_8;
- /** @hide */
- public static final int FRONTEND_DVBT_CODERATE_3_5 =
- Constants.FrontendDvbtCoderate.CODERATE_3_5;
- /** @hide */
- public static final int FRONTEND_DVBT_CODERATE_4_5 =
- Constants.FrontendDvbtCoderate.CODERATE_4_5;
- /** @hide */
- public static final int FRONTEND_DVBT_CODERATE_6_7 =
- Constants.FrontendDvbtCoderate.CODERATE_6_7;
- /** @hide */
- public static final int FRONTEND_DVBT_CODERATE_8_9 =
- Constants.FrontendDvbtCoderate.CODERATE_8_9;
-
- /** @hide */
- @IntDef({FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED, FRONTEND_DVBT_GUARD_INTERVAL_AUTO,
- FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16,
- FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4,
- FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128,
- FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128,
- FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendDvbtGuardInterval {}
- /** @hide */
- public static final int FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED =
- Constants.FrontendDvbtGuardInterval.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_DVBT_GUARD_INTERVAL_AUTO =
- Constants.FrontendDvbtGuardInterval.AUTO;
- /** @hide */
- public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32 =
- Constants.FrontendDvbtGuardInterval.INTERVAL_1_32;
- /** @hide */
- public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16 =
- Constants.FrontendDvbtGuardInterval.INTERVAL_1_16;
- /** @hide */
- public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8 =
- Constants.FrontendDvbtGuardInterval.INTERVAL_1_8;
- /** @hide */
- public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4 =
- Constants.FrontendDvbtGuardInterval.INTERVAL_1_4;
- /** @hide */
- public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128 =
- Constants.FrontendDvbtGuardInterval.INTERVAL_1_128;
- /** @hide */
- public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128 =
- Constants.FrontendDvbtGuardInterval.INTERVAL_19_128;
- /** @hide */
- public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256 =
- Constants.FrontendDvbtGuardInterval.INTERVAL_19_256;
-
- /** @hide */
- @IntDef({FRONTEND_ISDBS_CODERATE_UNDEFINED, FRONTEND_ISDBS_CODERATE_AUTO,
- FRONTEND_ISDBS_CODERATE_1_2, FRONTEND_ISDBS_CODERATE_2_3, FRONTEND_ISDBS_CODERATE_3_4,
- FRONTEND_ISDBS_CODERATE_5_6, FRONTEND_ISDBS_CODERATE_7_8})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendIsdbsCoderate {}
- /** @hide */
- public static final int FRONTEND_ISDBS_CODERATE_UNDEFINED =
- Constants.FrontendIsdbsCoderate.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ISDBS_CODERATE_AUTO = Constants.FrontendIsdbsCoderate.AUTO;
- /** @hide */
- public static final int FRONTEND_ISDBS_CODERATE_1_2 =
- Constants.FrontendIsdbsCoderate.CODERATE_1_2;
- /** @hide */
- public static final int FRONTEND_ISDBS_CODERATE_2_3 =
- Constants.FrontendIsdbsCoderate.CODERATE_2_3;
- /** @hide */
- public static final int FRONTEND_ISDBS_CODERATE_3_4 =
- Constants.FrontendIsdbsCoderate.CODERATE_3_4;
- /** @hide */
- public static final int FRONTEND_ISDBS_CODERATE_5_6 =
- Constants.FrontendIsdbsCoderate.CODERATE_5_6;
- /** @hide */
- public static final int FRONTEND_ISDBS_CODERATE_7_8 =
- Constants.FrontendIsdbsCoderate.CODERATE_7_8;
-
- /** @hide */
- @IntDef({FRONTEND_ISDBT_MODE_UNDEFINED, FRONTEND_ISDBT_MODE_AUTO, FRONTEND_ISDBT_MODE_1,
- FRONTEND_ISDBT_MODE_2, FRONTEND_ISDBT_MODE_3})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendIsdbtMode {}
- /** @hide */
- public static final int FRONTEND_ISDBT_MODE_UNDEFINED = Constants.FrontendIsdbtMode.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ISDBT_MODE_AUTO = Constants.FrontendIsdbtMode.AUTO;
- /** @hide */
- public static final int FRONTEND_ISDBT_MODE_1 = Constants.FrontendIsdbtMode.MODE_1;
- /** @hide */
- public static final int FRONTEND_ISDBT_MODE_2 = Constants.FrontendIsdbtMode.MODE_2;
- /** @hide */
- public static final int FRONTEND_ISDBT_MODE_3 = Constants.FrontendIsdbtMode.MODE_3;
-
- /** @hide */
- @IntDef({FRONTEND_ISDBT_BANDWIDTH_UNDEFINED, FRONTEND_ISDBT_BANDWIDTH_AUTO,
- FRONTEND_ISDBT_BANDWIDTH_8MHZ, FRONTEND_ISDBT_BANDWIDTH_7MHZ,
- FRONTEND_ISDBT_BANDWIDTH_6MHZ})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendIsdbtBandwidth {}
- /** @hide */
- public static final int FRONTEND_ISDBT_BANDWIDTH_UNDEFINED =
- Constants.FrontendIsdbtBandwidth.UNDEFINED;
- /** @hide */
- public static final int FRONTEND_ISDBT_BANDWIDTH_AUTO = Constants.FrontendIsdbtBandwidth.AUTO;
- /** @hide */
- public static final int FRONTEND_ISDBT_BANDWIDTH_8MHZ =
- Constants.FrontendIsdbtBandwidth.BANDWIDTH_8MHZ;
- /** @hide */
- public static final int FRONTEND_ISDBT_BANDWIDTH_7MHZ =
- Constants.FrontendIsdbtBandwidth.BANDWIDTH_7MHZ;
- /** @hide */
- public static final int FRONTEND_ISDBT_BANDWIDTH_6MHZ =
- Constants.FrontendIsdbtBandwidth.BANDWIDTH_6MHZ;
-
- /** @hide */
@IntDef({FILTER_SETTINGS_TS, FILTER_SETTINGS_MMTP, FILTER_SETTINGS_IP, FILTER_SETTINGS_TLV,
FILTER_SETTINGS_ALP})
@Retention(RetentionPolicy.SOURCE)
@@ -1252,79 +731,40 @@
/** @hide */
public static final int FILTER_SETTINGS_ALP = Constants.DemuxFilterMainType.ALP;
- /** @hide */
- @IntDef({DVR_SETTINGS_RECORD, DVR_SETTINGS_PLAYBACK})
- @Retention(RetentionPolicy.SOURCE)
- public @interface DvrSettingsType {}
- /** @hide */
- public static final int DVR_SETTINGS_RECORD = Constants.DvrType.RECORD;
- /** @hide */
- public static final int DVR_SETTINGS_PLAYBACK = Constants.DvrType.PLAYBACK;
-
-
- /** @hide */
- @IntDef({LNB_VOLTAGE_NONE, LNB_VOLTAGE_5V, LNB_VOLTAGE_11V, LNB_VOLTAGE_12V, LNB_VOLTAGE_13V,
- LNB_VOLTAGE_14V, LNB_VOLTAGE_15V, LNB_VOLTAGE_18V, LNB_VOLTAGE_19V})
- @Retention(RetentionPolicy.SOURCE)
- public @interface LnbVoltage {}
- /** @hide */
- public static final int LNB_VOLTAGE_NONE = Constants.LnbVoltage.NONE;
- /** @hide */
- public static final int LNB_VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V;
- /** @hide */
- public static final int LNB_VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V;
- /** @hide */
- public static final int LNB_VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V;
- /** @hide */
- public static final int LNB_VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V;
- /** @hide */
- public static final int LNB_VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V;
- /** @hide */
- public static final int LNB_VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V;
- /** @hide */
- public static final int LNB_VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V;
- /** @hide */
- public static final int LNB_VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V;
-
- /** @hide */
- @IntDef({LNB_TONE_NONE, LNB_TONE_CONTINUOUS})
- @Retention(RetentionPolicy.SOURCE)
- public @interface LnbTone {}
- /** @hide */
- public static final int LNB_TONE_NONE = Constants.LnbTone.NONE;
- /** @hide */
- public static final int LNB_TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS;
-
- /** @hide */
- @IntDef({LNB_POSITION_UNDEFINED, LNB_POSITION_A, LNB_POSITION_B})
- @Retention(RetentionPolicy.SOURCE)
- public @interface LnbPosition {}
- /** @hide */
- public static final int LNB_POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED;
- /** @hide */
- public static final int LNB_POSITION_A = Constants.LnbPosition.POSITION_A;
- /** @hide */
- public static final int LNB_POSITION_B = Constants.LnbPosition.POSITION_B;
-
/** @hide */
@IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE,
RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR})
@Retention(RetentionPolicy.SOURCE)
public @interface Result {}
- /** @hide */
+
+ /**
+ * Operation succeeded.
+ */
public static final int RESULT_SUCCESS = Constants.Result.SUCCESS;
- /** @hide */
+ /**
+ * Operation failed because the corresponding resources are not available.
+ */
public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE;
- /** @hide */
+ /**
+ * Operation failed because the corresponding resources are not initialized.
+ */
public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED;
- /** @hide */
+ /**
+ * Operation failed because it's not in a valid state.
+ */
public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE;
- /** @hide */
+ /**
+ * Operation failed because there are invalid arguments.
+ */
public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT;
- /** @hide */
+ /**
+ * Memory allocation failed.
+ */
public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY;
- /** @hide */
+ /**
+ * Operation failed due to unknown errors.
+ */
public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR;
private TunerConstants() {
diff --git a/media/java/android/media/tv/tuner/dvr/Dvr.java b/media/java/android/media/tv/tuner/dvr/Dvr.java
new file mode 100644
index 0000000..95508d3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/Dvr.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.dvr;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.media.tv.tuner.Tuner.Filter;
+import android.media.tv.tuner.TunerConstants.Result;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Digital Video Record (DVR) interface provides record control on Demux's output buffer and
+ * playback control on Demux's input buffer.
+ *
+ * @hide
+ */
+public class Dvr {
+ private long mNativeContext;
+ private DvrCallback mCallback;
+
+ private native int nativeAttachFilter(Filter filter);
+ private native int nativeDetachFilter(Filter filter);
+ private native int nativeConfigureDvr(DvrSettings settings);
+ private native int nativeStartDvr();
+ private native int nativeStopDvr();
+ private native int nativeFlushDvr();
+ private native int nativeClose();
+ private native void nativeSetFileDescriptor(int fd);
+ private native int nativeRead(long size);
+ private native int nativeRead(byte[] bytes, long offset, long size);
+ private native int nativeWrite(long size);
+ private native int nativeWrite(byte[] bytes, long offset, long size);
+
+ private Dvr() {}
+
+ /**
+ * Attaches a filter to DVR interface for recording.
+ *
+ * @param filter the filter to be attached.
+ * @return result status of the operation.
+ */
+ @Result
+ public int attachFilter(@NonNull Filter filter) {
+ return nativeAttachFilter(filter);
+ }
+
+ /**
+ * Detaches a filter from DVR interface.
+ *
+ * @param filter the filter to be detached.
+ * @return result status of the operation.
+ */
+ @Result
+ public int detachFilter(@NonNull Filter filter) {
+ return nativeDetachFilter(filter);
+ }
+
+ /**
+ * Configures the DVR.
+ *
+ * @param settings the settings of the DVR interface.
+ * @return result status of the operation.
+ */
+ @Result
+ public int configure(@NonNull DvrSettings settings) {
+ return nativeConfigureDvr(settings);
+ }
+
+ /**
+ * Starts DVR.
+ *
+ * <p>Starts consuming playback data or producing data for recording.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int start() {
+ return nativeStartDvr();
+ }
+
+ /**
+ * Stops DVR.
+ *
+ * <p>Stops consuming playback data or producing data for recording.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int stop() {
+ return nativeStopDvr();
+ }
+
+ /**
+ * Flushed DVR data.
+ *
+ * <p>The data in DVR buffer is cleared.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int flush() {
+ return nativeFlushDvr();
+ }
+
+ /**
+ * Closes the DVR instance to release resources.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int close() {
+ return nativeClose();
+ }
+
+ /**
+ * Sets file descriptor to read/write data.
+ *
+ * @param fd the file descriptor to read/write data.
+ */
+ public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) {
+ nativeSetFileDescriptor(fd.getFd());
+ }
+
+ /**
+ * Reads data from the file for DVR playback.
+ *
+ * @param size the maximum number of bytes to read.
+ * @return the number of bytes read.
+ */
+ public int read(@BytesLong long size) {
+ return nativeRead(size);
+ }
+
+ /**
+ * Reads data from the buffer for DVR playback and copies to the given byte array.
+ *
+ * @param bytes the byte array to store the data.
+ * @param offset the index of the first byte in {@code bytes} to copy to.
+ * @param size the maximum number of bytes to read.
+ * @return the number of bytes read.
+ */
+ public int read(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
+ if (size + offset > bytes.length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+ }
+ return nativeRead(bytes, offset, size);
+ }
+
+ /**
+ * Writes recording data to file.
+ *
+ * @param size the maximum number of bytes to write.
+ * @return the number of bytes written.
+ */
+ public int write(@BytesLong long size) {
+ return nativeWrite(size);
+ }
+
+ /**
+ * Writes recording data to buffer.
+ *
+ * @param bytes the byte array stores the data to be written to DVR.
+ * @param offset the index of the first byte in {@code bytes} to be written to DVR.
+ * @param size the maximum number of bytes to write.
+ * @return the number of bytes written.
+ */
+ public int write(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
+ return nativeWrite(bytes, offset, size);
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/dvr/DvrCallback.java
similarity index 64%
rename from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
rename to media/java/android/media/tv/tuner/dvr/DvrCallback.java
index 306de84..3d140f0 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrCallback.java
@@ -14,18 +14,20 @@
* limitations under the License.
*/
-package android.media.tv.tuner.filter;
+package android.media.tv.tuner.dvr;
/**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * Callback interface for receiving information from DVR interfaces.
+ *
* @hide
*/
-public class AudioExtraMetaData {
- private byte mAdFade;
- private byte mAdPan;
- private byte mVersionTextTag;
- private byte mAdGainCenter;
- private byte mAdGainFront;
- private byte mAdGainSurround;
+public interface DvrCallback {
+ /**
+ * Invoked when record status changed.
+ */
+ void onRecordStatusChanged(int status);
+ /**
+ * Invoked when playback status changed.
+ */
+ void onPlaybackStatusChanged(int status);
}
diff --git a/media/java/android/media/tv/tuner/dvr/DvrSettings.java b/media/java/android/media/tv/tuner/dvr/DvrSettings.java
new file mode 100644
index 0000000..46efd7a
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/DvrSettings.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.dvr;
+
+import android.annotation.BytesLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.FilterStatus;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * DVR settings used to configure {@link Dvr}.
+ *
+ * @hide
+ */
+public class DvrSettings {
+
+ /** @hide */
+ @IntDef(prefix = "DATA_FORMAT_",
+ value = {DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DataFormat {}
+
+ /**
+ * Transport Stream.
+ */
+ public static final int DATA_FORMAT_TS = Constants.DataFormat.TS;
+ /**
+ * Packetized Elementary Stream.
+ */
+ public static final int DATA_FORMAT_PES = Constants.DataFormat.PES;
+ /**
+ * Elementary Stream.
+ */
+ public static final int DATA_FORMAT_ES = Constants.DataFormat.ES;
+ /**
+ * TLV (type-length-value) Stream for SHV
+ */
+ public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV;
+
+
+ /** @hide */
+ @IntDef(prefix = "TYPE_", value = {TYPE_RECORD, TYPE_PLAYBACK})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /**
+ * DVR for recording.
+ */
+ public static final int TYPE_RECORD = Constants.DvrType.RECORD;
+ /**
+ * DVR for playback of recorded programs.
+ */
+ public static final int TYPE_PLAYBACK = Constants.DvrType.PLAYBACK;
+
+
+
+ private final int mStatusMask;
+ private final long mLowThreshold;
+ private final long mHighThreshold;
+ private final long mPacketSize;
+
+ @DataFormat
+ private final int mDataFormat;
+ @Type
+ private final int mType;
+
+ private DvrSettings(int statusMask, long lowThreshold, long highThreshold, long packetSize,
+ @DataFormat int dataFormat, @Type int type) {
+ mStatusMask = statusMask;
+ mLowThreshold = lowThreshold;
+ mHighThreshold = highThreshold;
+ mPacketSize = packetSize;
+ mDataFormat = dataFormat;
+ mType = type;
+ }
+
+ /**
+ * Creates a builder for {@link DvrSettings}.
+ */
+ @NonNull
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link DvrSettings}.
+ */
+ public static final class Builder {
+ private int mStatusMask;
+ private long mLowThreshold;
+ private long mHighThreshold;
+ private long mPacketSize;
+ @DataFormat
+ private int mDataFormat;
+ @Type
+ private int mType;
+
+ /**
+ * Sets status mask.
+ */
+ @NonNull
+ public Builder setStatusMask(@FilterStatus int statusMask) {
+ this.mStatusMask = statusMask;
+ return this;
+ }
+
+ /**
+ * Sets low threshold in bytes.
+ */
+ @NonNull
+ public Builder setLowThreshold(@BytesLong long lowThreshold) {
+ this.mLowThreshold = lowThreshold;
+ return this;
+ }
+
+ /**
+ * Sets high threshold in bytes.
+ */
+ @NonNull
+ public Builder setHighThreshold(@BytesLong long highThreshold) {
+ this.mHighThreshold = highThreshold;
+ return this;
+ }
+
+ /**
+ * Sets packet size in bytes.
+ */
+ @NonNull
+ public Builder setPacketSize(@BytesLong long packetSize) {
+ this.mPacketSize = packetSize;
+ return this;
+ }
+
+ /**
+ * Sets data format.
+ */
+ @NonNull
+ public Builder setDataFormat(@DataFormat int dataFormat) {
+ this.mDataFormat = dataFormat;
+ return this;
+ }
+
+ /**
+ * Sets settings type.
+ */
+ @NonNull
+ public Builder setType(@Type int type) {
+ this.mType = type;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DvrSettings} object.
+ */
+ @NonNull
+ public DvrSettings build() {
+ return new DvrSettings(
+ mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType);
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AudioDescriptor.java b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java
new file mode 100644
index 0000000..c88c07f
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+/**
+ * Meta data from AD (Audio Descriptor) according to ETSI TS 101 154 V2.1.1.
+ *
+ * @hide
+ */
+public class AudioDescriptor {
+ private final byte mAdFade;
+ private final byte mAdPan;
+ private final char mVersionTextTag;
+ private final byte mAdGainCenter;
+ private final byte mAdGainFront;
+ private final byte mAdGainSurround;
+
+ // This constructor is used by JNI code only
+ private AudioDescriptor(byte adFade, byte adPan, char versionTextTag, byte adGainCenter,
+ byte adGainFront, byte adGainSurround) {
+ mAdFade = adFade;
+ mAdPan = adPan;
+ mVersionTextTag = versionTextTag;
+ mAdGainCenter = adGainCenter;
+ mAdGainFront = adGainFront;
+ mAdGainSurround = adGainSurround;
+ }
+
+ /**
+ * Gets AD fade byte.
+ *
+ * <p>Takes values between 0x00 (representing no fade of the main programme sound) and 0xFF
+ * (representing a full fade). Over the range 0x00 to 0xFE one lsb represents a step in
+ * attenuation of the programme sound of 0.3 dB giving a range of 76.2 dB. The fade value of
+ * 0xFF represents no programme sound at all (i.e. mute).
+ */
+ public byte getAdFade() {
+ return mAdFade;
+ }
+
+ /**
+ * Gets AD pan byte.
+ *
+ * <p>Takes values between 0x00 representing a central forward presentation of the audio
+ * description and 0xFF, each increment representing a 360/256 degree step clockwise looking
+ * down on the listener (i.e. just over 1.4 degrees).
+ */
+ public byte getAdPan() {
+ return mAdPan;
+ }
+
+ /**
+ * Gets AD version tag. A single ASCII character version indicates the version.
+ *
+ * <p>A single ASCII character version designator (here "1" indicates revision 1).
+ */
+ public char getVersionTextTag() {
+ return mVersionTextTag;
+ }
+
+ /**
+ * Gets AD gain byte center in dB.
+ *
+ * <p>Represents a signed value in dB. Takes values between 0x7F (representing +76.2 dB boost of
+ * the main programme center) and 0x80 (representing a full fade). Over the range 0x00 to 0x7F
+ * one lsb represents a step in boost of the programme center of 0.6 dB giving a maximum boost
+ * of +76.2 dB. Over the range 0x81 to 0x00 one lsb represents a step in attenuation of the
+ * programme center of 0.6 dB giving a maximum attenuation of -76.2 dB. The gain value of 0x80
+ * represents no main center level at all (i.e. mute).
+ */
+ public byte getAdGainCenter() {
+ return mAdGainCenter;
+ }
+
+ /**
+ * Gets AD gain byte front in dB.
+ *
+ * <p>Same as {@link #getAdGainCenter()}, but applied to left and right front channel.
+ */
+ public byte getAdGainFront() {
+ return mAdGainFront;
+ }
+
+ /**
+ * Gets AD gain byte surround in dB.
+ *
+ * <p>Same as {@link #getAdGainCenter()}, but applied to all surround channels
+ */
+ public byte getAdGainSurround() {
+ return mAdGainSurround;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/DownloadEvent.java b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
index 548fa77..591e4e5 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
@@ -16,15 +16,65 @@
package android.media.tv.tuner.filter;
+import android.media.tv.tuner.Tuner.Filter;
+
/**
- * Download event.
+ * Filter event sent from {@link Filter} objects with download type.
+ *
* @hide
*/
public class DownloadEvent extends FilterEvent {
- private int mItemId;
- private int mMpuSequenceNumber;
- private int mItemFragmentIndex;
- private int mLastItemFragmentIndex;
- private int mDataLength;
+ private final int mItemId;
+ private final int mMpuSequenceNumber;
+ private final int mItemFragmentIndex;
+ private final int mLastItemFragmentIndex;
+ private final int mDataLength;
+
+ // This constructor is used by JNI code only
+ private DownloadEvent(int itemId, int mpuSequenceNumber, int itemFragmentIndex,
+ int lastItemFragmentIndex, int dataLength) {
+ mItemId = itemId;
+ mMpuSequenceNumber = mpuSequenceNumber;
+ mItemFragmentIndex = itemFragmentIndex;
+ mLastItemFragmentIndex = lastItemFragmentIndex;
+ mDataLength = dataLength;
+ }
+
+ /**
+ * Gets item ID.
+ */
+ public int getItemId() {
+ return mItemId;
+ }
+
+ /**
+ * Gets MPU sequence number of filtered data.
+ */
+ public int getMpuSequenceNumber() {
+ return mMpuSequenceNumber;
+ }
+
+ /**
+ * Gets current index of the current item.
+ *
+ * An item can be stored in different fragments.
+ */
+ public int getItemFragmentIndex() {
+ return mItemFragmentIndex;
+ }
+
+ /**
+ * Gets last index of the current item.
+ */
+ public int getLastItemFragmentIndex() {
+ return mLastItemFragmentIndex;
+ }
+
+ /**
+ * Gets data size in bytes of filtered data.
+ */
+ public int getDataLength() {
+ return mDataLength;
+ }
}
diff --git a/media/java/android/media/tv/tuner/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
similarity index 70%
rename from media/java/android/media/tv/tuner/Filter.java
rename to media/java/android/media/tv/tuner/filter/Filter.java
index db3b97a..804c0c5 100644
--- a/media/java/android/media/tv/tuner/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -14,33 +14,33 @@
* limitations under the License.
*/
-package android.media.tv.tuner;
+package android.media.tv.tuner.filter;
+import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.tv.tuner.Tuner.FilterCallback;
-import android.media.tv.tuner.filter.FilterConfiguration;
-import android.media.tv.tuner.filter.Settings;
/**
* Tuner data filter.
*
- * <p> This class is used to filter wanted data according to the filter's configuration.
+ * <p>This class is used to filter wanted data according to the filter's configuration.
+ *
* @hide
*/
public class Filter implements AutoCloseable {
private long mNativeContext;
private FilterCallback mCallback;
- int mId;
+ private final int mId;
private native int nativeConfigureFilter(
int type, int subType, FilterConfiguration settings);
private native int nativeGetId();
- private native int nativeSetDataSource(Tuner.Filter source);
+ private native int nativeSetDataSource(Filter source);
private native int nativeStartFilter();
private native int nativeStopFilter();
private native int nativeFlushFilter();
- private native int nativeRead(byte[] buffer, int offset, int size);
+ private native int nativeRead(byte[] buffer, long offset, long size);
private native int nativeClose();
private Filter(int id) {
@@ -53,24 +53,20 @@
/**
* Configures the filter.
*
- * @param settings the settings of the filter.
+ * @param config the configuration of the filter.
* @return result status of the operation.
- * @hide
*/
- public int configure(FilterConfiguration settings) {
+ public int configure(@NonNull FilterConfiguration config) {
int subType = -1;
- Settings s = settings.getSettings();
+ Settings s = config.getSettings();
if (s != null) {
subType = s.getType();
}
- return nativeConfigureFilter(settings.getType(), subType, settings);
+ return nativeConfigureFilter(config.getType(), subType, config);
}
/**
* Gets the filter Id.
- *
- * @return the hardware resource Id for the filter.
- * @hide
*/
public int getId() {
return nativeGetId();
@@ -87,17 +83,15 @@
* @param source the filter instance which provides data input. Switch to
* use demux as data source if the filter instance is NULL.
* @return result status of the operation.
- * @hide
*/
- public int setDataSource(@Nullable Tuner.Filter source) {
+ public int setDataSource(@Nullable Filter source) {
return nativeSetDataSource(source);
}
/**
- * Starts the filter.
+ * Starts filtering data.
*
* @return result status of the operation.
- * @hide
*/
public int start() {
return nativeStartFilter();
@@ -105,35 +99,38 @@
/**
- * Stops the filter.
+ * Stops filtering data.
*
* @return result status of the operation.
- * @hide
*/
public int stop() {
return nativeStopFilter();
}
/**
- * Flushes the filter.
+ * Flushes the filter. Data in filter buffer is cleared.
*
* @return result status of the operation.
- * @hide
*/
public int flush() {
return nativeFlushFilter();
}
- /** @hide */
- public int read(@NonNull byte[] buffer, int offset, int size) {
+ /**
+ * Copies filtered data from filter buffer to the given byte array.
+ *
+ * @param buffer the buffer to store the filtered data.
+ * @param offset the index of the first byte in {@code buffer} to write.
+ * @param size the maximum number of bytes to read.
+ * @return the number of bytes read.
+ */
+ public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
size = Math.min(size, buffer.length - offset);
return nativeRead(buffer, offset, size);
}
/**
- * Release the Filter instance.
- *
- * @hide
+ * Releases the Filter instance.
*/
@Override
public void close() {
diff --git a/media/java/android/media/tv/tuner/filter/FilterCallback.java b/media/java/android/media/tv/tuner/filter/FilterCallback.java
new file mode 100644
index 0000000..888adc5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/FilterCallback.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.TunerConstants.FilterStatus;
+
+/**
+ * Callback interface for receiving information from the corresponding filters.
+ *
+ * @hide
+ */
+public interface FilterCallback {
+ /**
+ * Invoked when there are filter events.
+ *
+ * @param filter the corresponding filter which sent the events.
+ * @param events the filter events sent from the filter.
+ */
+ void onFilterEvent(@NonNull Filter filter, @NonNull FilterEvent[] events);
+ /**
+ * Invoked when filter status changed.
+ *
+ * @param filter the corresponding filter whose status is changed.
+ * @param status the new status of the filter.
+ */
+ void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
+}
diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
index 99b10cd..6496627 100644
--- a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.hardware.tv.tuner.V1_0.Constants;
import java.lang.annotation.Retention;
@@ -28,6 +29,7 @@
*
* @hide
*/
+@SystemApi
public abstract class FilterConfiguration {
/** @hide */
@@ -57,7 +59,7 @@
public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP;
@Nullable
- private final Settings mSettings;
+ /* package */ final Settings mSettings;
/* package */ FilterConfiguration(Settings settings) {
mSettings = settings;
diff --git a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
index 4da1d21..09489ed 100644
--- a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
@@ -16,10 +16,25 @@
package android.media.tv.tuner.filter;
+import android.media.tv.tuner.Tuner.Filter;
+
/**
- * IP payload event.
+ * Filter event sent from {@link Filter} objects with IP payload type.
+ *
* @hide
*/
public class IpPayloadEvent extends FilterEvent {
- private int mDataLength;
+ private final int mDataLength;
+
+ // This constructor is used by JNI code only
+ private IpPayloadEvent(int dataLength) {
+ mDataLength = dataLength;
+ }
+
+ /**
+ * Gets data size in bytes of filtered data.
+ */
+ public int getDataLength() {
+ return mDataLength;
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index 7703248..37f94ae 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -16,20 +16,111 @@
package android.media.tv.tuner.filter;
-import android.os.NativeHandle;
+import android.annotation.Nullable;
+import android.media.tv.tuner.Tuner.Filter;
/**
- * Media event.
+ * Filter event sent from {@link Filter} objects with media type.
+ *
* @hide
*/
-public class MediaEvent extends FilterEvent {
- private int mStreamId;
- private boolean mIsPtsPresent;
- private long mPts;
- private int mDataLength;
- private NativeHandle mHandle;
- private boolean mIsSecureMemory;
- private int mMpuSequenceNumber;
- private boolean mIsPrivateData;
- private AudioExtraMetaData mExtraMetaData;
+public class MediaEvent extends FilterEvent{
+ private final int mStreamId;
+ private final boolean mIsPtsPresent;
+ private final long mPts;
+ private final int mDataLength;
+ private final Object mLinearBuffer;
+ private final boolean mIsSecureMemory;
+ private final int mMpuSequenceNumber;
+ private final boolean mIsPrivateData;
+ private final AudioDescriptor mExtraMetaData;
+
+ // This constructor is used by JNI code only
+ private MediaEvent(int streamId, boolean isPtsPresent, long pts, int dataLength, Object buffer,
+ boolean isSecureMemory, int mpuSequenceNumber, boolean isPrivateData,
+ AudioDescriptor extraMetaData) {
+ mStreamId = streamId;
+ mIsPtsPresent = isPtsPresent;
+ mPts = pts;
+ mDataLength = dataLength;
+ mLinearBuffer = buffer;
+ mIsSecureMemory = isSecureMemory;
+ mMpuSequenceNumber = mpuSequenceNumber;
+ mIsPrivateData = isPrivateData;
+ mExtraMetaData = extraMetaData;
+ }
+
+ /**
+ * Gets stream ID.
+ */
+ public int getStreamId() {
+ return mStreamId;
+ }
+
+ /**
+ * Returns whether PTS is present.
+ *
+ * @return {@code true} if PTS is present in PES header; {@code false} otherwise.
+ */
+ public boolean getIsPtsPresent() {
+ return mIsPtsPresent;
+ }
+
+ /**
+ * Gets PTS (Presentation Time Stamp) for audio or video frame.
+ */
+ public long getPts() {
+ return mPts;
+ }
+
+ /**
+ * Gets data size in bytes of audio or video frame.
+ */
+ public int getDataLength() {
+ return mDataLength;
+ }
+
+ /**
+ * Gets a linear buffer associated to the memory where audio or video data stays.
+ * TODO: use LinearBuffer when it's ready.
+ *
+ * @hide
+ */
+ public Object getLinearBuffer() {
+ return mLinearBuffer;
+ }
+
+ /**
+ * Returns whether the data is secure.
+ *
+ * @return {@code true} if the data is in secure area, and isn't mappable;
+ * {@code false} otherwise.
+ */
+ public boolean getIsSecureMemory() {
+ return mIsSecureMemory;
+ }
+
+ /**
+ * Gets MPU sequence number of filtered data.
+ */
+ public int getMpuSequenceNumber() {
+ return mMpuSequenceNumber;
+ }
+
+ /**
+ * Returns whether the data is private.
+ *
+ * @return {@code true} if the data is in private; {@code false} otherwise.
+ */
+ public boolean getIsPrivateData() {
+ return mIsPrivateData;
+ }
+
+ /**
+ * Gets audio extra metadata.
+ */
+ @Nullable
+ public AudioDescriptor getExtraMetaData() {
+ return mExtraMetaData;
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
index dbd8c77..7f37994 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -16,11 +16,34 @@
package android.media.tv.tuner.filter;
+import android.media.tv.tuner.Tuner.Filter;
+
/**
- * MMPT record event.
+ * Filter event sent from {@link Filter} objects with MMTP type.
+ *
* @hide
*/
public class MmtpRecordEvent extends FilterEvent {
- private int mScHevcIndexMask;
- private long mByteNumber;
+ private final int mScHevcIndexMask;
+ private final long mByteNumber;
+
+ // This constructor is used by JNI code only
+ private MmtpRecordEvent(int scHevcIndexMask, long byteNumber) {
+ mScHevcIndexMask = scHevcIndexMask;
+ mByteNumber = byteNumber;
+ }
+
+ /**
+ * Gets indexes which can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
+ */
+ public int getScHevcIndexMask() {
+ return mScHevcIndexMask;
+ }
+
+ /**
+ * Gets the byte number from beginning of the filter's output.
+ */
+ public long getByteNumber() {
+ return mByteNumber;
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/PesEvent.java b/media/java/android/media/tv/tuner/filter/PesEvent.java
index 16536e2..60251bf 100644
--- a/media/java/android/media/tv/tuner/filter/PesEvent.java
+++ b/media/java/android/media/tv/tuner/filter/PesEvent.java
@@ -16,12 +16,43 @@
package android.media.tv.tuner.filter;
+import android.media.tv.tuner.Tuner.Filter;
+
/**
- * PES event.
+ * Filter event sent from {@link Filter} objects with PES type.
+ *
* @hide
*/
public class PesEvent extends FilterEvent {
- private int mStreamId;
- private int mDataLength;
- private int mMpuSequenceNumber;
+ private final int mStreamId;
+ private final int mDataLength;
+ private final int mMpuSequenceNumber;
+
+ // This constructor is used by JNI code only
+ private PesEvent(int streamId, int dataLength, int mpuSequenceNumber) {
+ mStreamId = streamId;
+ mDataLength = dataLength;
+ mMpuSequenceNumber = mpuSequenceNumber;
+ }
+
+ /**
+ * Gets stream ID.
+ */
+ public int getStreamId() {
+ return mStreamId;
+ }
+
+ /**
+ * Gets data size in bytes of filtered data.
+ */
+ public int getDataLength() {
+ return mDataLength;
+ }
+
+ /**
+ * Gets MPU sequence number of filtered data.
+ */
+ public int getMpuSequenceNumber() {
+ return mMpuSequenceNumber;
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/PesSettings.java b/media/java/android/media/tv/tuner/filter/PesSettings.java
index f38abf1..bfa1f8c 100644
--- a/media/java/android/media/tv/tuner/filter/PesSettings.java
+++ b/media/java/android/media/tv/tuner/filter/PesSettings.java
@@ -17,6 +17,9 @@
package android.media.tv.tuner.filter;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
import android.media.tv.tuner.TunerConstants;
import android.media.tv.tuner.TunerUtils;
import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
@@ -26,6 +29,7 @@
*
* @hide
*/
+@SystemApi
public class PesSettings extends Settings {
private final int mStreamId;
private final boolean mIsRaw;
@@ -37,12 +41,32 @@
}
/**
+ * Gets stream ID.
+ */
+ public int getStreamId() {
+ return mStreamId;
+ }
+
+ /**
+ * Returns whether the data is raw.
+ *
+ * @return {@code true} if the data is raw. Filter sends onFilterStatus callback
+ * instead of onFilterEvent for raw data. {@code false} otherwise.
+ */
+ public boolean isRaw() {
+ return mIsRaw;
+ }
+
+ /**
* Creates a builder for {@link PesSettings}.
*
* @param mainType the filter main type of the settings.
+ * @param context the context of the caller.
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
@NonNull
- public static Builder newBuilder(@FilterType int mainType) {
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
return new Builder(mainType);
}
@@ -70,13 +94,13 @@
}
/**
- * Sets whether it's raw.
+ * Sets whether the data is raw.
*
* @param isRaw {@code true} if the data is raw. Filter sends onFilterStatus callback
* instead of onFilterEvent for raw data. {@code false} otherwise.
*/
@NonNull
- public Builder setIsRaw(boolean isRaw) {
+ public Builder setRaw(boolean isRaw) {
mIsRaw = isRaw;
return this;
}
diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java
index 146aca74..9155926 100644
--- a/media/java/android/media/tv/tuner/filter/Settings.java
+++ b/media/java/android/media/tv/tuner/filter/Settings.java
@@ -16,11 +16,14 @@
package android.media.tv.tuner.filter;
+import android.annotation.SystemApi;
+
/**
* Settings for filters of different subtypes.
*
* @hide
*/
+@SystemApi
public abstract class Settings {
private final int mType;
diff --git a/media/java/android/media/tv/tuner/filter/TemiEvent.java b/media/java/android/media/tv/tuner/filter/TemiEvent.java
index 3841604..031fa5c 100644
--- a/media/java/android/media/tv/tuner/filter/TemiEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TemiEvent.java
@@ -16,12 +16,46 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.media.tv.tuner.Tuner.Filter;
+
/**
- * TEMI event.
+ * Filter event sent from {@link Filter} objects for Timed External Media Information (TEMI) data.
+ *
* @hide
*/
public class TemiEvent extends FilterEvent {
- private long mPts;
- private byte mDescrTag;
- private byte[] mDescrData;
+ private final long mPts;
+ private final byte mDescrTag;
+ private final byte[] mDescrData;
+
+ // This constructor is used by JNI code only
+ private TemiEvent(long pts, byte descrTag, byte[] descrData) {
+ mPts = pts;
+ mDescrTag = descrTag;
+ mDescrData = descrData;
+ }
+
+
+ /**
+ * Gets PTS (Presentation Time Stamp) for audio or video frame.
+ */
+ public long getPts() {
+ return mPts;
+ }
+
+ /**
+ * Gets TEMI descriptor tag.
+ */
+ public byte getDescriptorTag() {
+ return mDescrTag;
+ }
+
+ /**
+ * Gets TEMI descriptor.
+ */
+ @NonNull
+ public byte[] getDescriptorData() {
+ return mDescrData;
+ }
}
diff --git a/media/java/android/media/tv/tuner/TimeFilter.java b/media/java/android/media/tv/tuner/filter/TimeFilter.java
similarity index 98%
rename from media/java/android/media/tv/tuner/TimeFilter.java
rename to media/java/android/media/tv/tuner/filter/TimeFilter.java
index 8bd0d26..c975004 100644
--- a/media/java/android/media/tv/tuner/TimeFilter.java
+++ b/media/java/android/media/tv/tuner/filter/TimeFilter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.media.tv.tuner;
+package android.media.tv.tuner.filter;
import android.annotation.Nullable;
import android.media.tv.tuner.TunerConstants.Result;
diff --git a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
index d0241b6..5c38cfa 100644
--- a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
@@ -17,12 +17,18 @@
package android.media.tv.tuner.filter;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
/**
* Filter configuration for a TS filter.
*
* @hide
*/
+@SystemApi
public class TsFilterConfiguration extends FilterConfiguration {
private final int mTpid;
@@ -37,10 +43,28 @@
}
/**
- * Creates a builder for {@link TsFilterConfiguration}.
+ * Gets the {@link Settings} object of this filter configuration.
*/
+ @Nullable
+ public Settings getSettings() {
+ return mSettings;
+ }
+ /**
+ * Gets Tag Protocol ID.
+ */
+ public int getTpid() {
+ return mTpid;
+ }
+
+ /**
+ * Creates a builder for {@link TsFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
@NonNull
- public static Builder newBuilder() {
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
return new Builder();
}
@@ -51,6 +75,9 @@
private Settings mSettings;
private int mTpid;
+ private Builder() {
+ }
+
/**
* Sets filter settings.
*
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
index 875b5bd..fa4dd72 100644
--- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -16,12 +16,84 @@
package android.media.tv.tuner.filter;
+import android.annotation.IntDef;
+import android.media.tv.tuner.Tuner.Filter;
+import android.media.tv.tuner.TunerConstants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
- * TS record event.
+ * Filter event sent from {@link Filter} objects for TS record data.
+ *
* @hide
*/
public class TsRecordEvent extends FilterEvent {
- private int mTpid;
- private int mIndexMask;
- private long mByteNumber;
+ /**
+ * @hide
+ */
+ @IntDef(flag = true, value = {
+ TunerConstants.TS_INDEX_FIRST_PACKET,
+ TunerConstants.TS_INDEX_PAYLOAD_UNIT_START_INDICATOR,
+ TunerConstants.TS_INDEX_CHANGE_TO_NOT_SCRAMBLED,
+ TunerConstants.TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED,
+ TunerConstants.TS_INDEX_CHANGE_TO_ODD_SCRAMBLED,
+ TunerConstants.TS_INDEX_DISCONTINUITY_INDICATOR,
+ TunerConstants.TS_INDEX_RANDOM_ACCESS_INDICATOR,
+ TunerConstants.TS_INDEX_PRIORITY_INDICATOR,
+ TunerConstants.TS_INDEX_PCR_FLAG,
+ TunerConstants.TS_INDEX_OPCR_FLAG,
+ TunerConstants.TS_INDEX_SPLICING_POINT_FLAG,
+ TunerConstants.TS_INDEX_PRIVATE_DATA,
+ TunerConstants.TS_INDEX_ADAPTATION_EXTENSION_FLAG,
+ TunerConstants.SC_INDEX_I_FRAME,
+ TunerConstants.SC_INDEX_P_FRAME,
+ TunerConstants.SC_INDEX_B_FRAME,
+ TunerConstants.SC_INDEX_SEQUENCE,
+ TunerConstants.SC_HEVC_INDEX_SPS,
+ TunerConstants.SC_HEVC_INDEX_AUD,
+ TunerConstants.SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+ TunerConstants.SC_HEVC_INDEX_SLICE_BLA_W_RADL,
+ TunerConstants.SC_HEVC_INDEX_SLICE_BLA_N_LP,
+ TunerConstants.SC_HEVC_INDEX_SLICE_IDR_W_RADL,
+ TunerConstants.SC_HEVC_INDEX_SLICE_IDR_N_LP,
+ TunerConstants.SC_HEVC_INDEX_SLICE_TRAIL_CRA,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IndexMask {}
+
+ private final int mPid;
+ private final int mIndexMask;
+ private final long mByteNumber;
+
+ // This constructor is used by JNI code only
+ private TsRecordEvent(int pid, int indexMask, long byteNumber) {
+ mPid = pid;
+ mIndexMask = indexMask;
+ mByteNumber = byteNumber;
+ }
+
+ /**
+ * Gets packet ID.
+ */
+ public int getTpid() {
+ return mPid;
+ }
+
+ /**
+ * Gets index mask.
+ *
+ * <p>The index type is one of TS, SC, and SC-HEVC, and is set when configuring the filter.
+ */
+ @IndexMask
+ public int getIndexMask() {
+ return mIndexMask;
+ }
+
+ /**
+ * Gets the byte number from beginning of the filter's output.
+ */
+ public long getByteNumber() {
+ return mByteNumber;
+ }
}
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
index 2962e98..d92c786 100644
--- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
@@ -31,10 +31,14 @@
/**
* Gets type capability.
*/
+ @AnalogFrontendSettings.SignalType
public int getTypeCapability() {
return mTypeCap;
}
- /** Gets SIF standard capability. */
+ /**
+ * Gets SIF standard capability.
+ */
+ @AnalogFrontendSettings.SifStandard
public int getSifStandardCapability() {
return mSifStandardCap;
}
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
index 16308ce..a30ddc7 100644
--- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
@@ -16,33 +16,156 @@
package android.media.tv.tuner.frontend;
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
- * Frontend settings for analog.
+ * Frontend settings for analog tuner.
+ *
* @hide
*/
public class AnalogFrontendSettings extends FrontendSettings {
- private int mAnalogType;
- private int mSifStandard;
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "SIGNAL_TYPE_",
+ value = {SIGNAL_TYPE_UNDEFINED, SIGNAL_TYPE_PAL, SIGNAL_TYPE_SECAM, SIGNAL_TYPE_NTSC})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SignalType {}
+
+ /**
+ * Undefined analog signal type.
+ */
+ public static final int SIGNAL_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED;
+ /**
+ * PAL analog signal type.
+ */
+ public static final int SIGNAL_TYPE_PAL = Constants.FrontendAnalogType.PAL;
+ /**
+ * SECM analog signal type.
+ */
+ public static final int SIGNAL_TYPE_SECAM = Constants.FrontendAnalogType.SECAM;
+ /**
+ * NTSC analog signal type.
+ */
+ public static final int SIGNAL_TYPE_NTSC = Constants.FrontendAnalogType.NTSC;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "SIF_",
+ value = {SIF_UNDEFINED, SIF_BG, SIF_BG_A2, SIF_BG_NICAM, SIF_I, SIF_DK,
+ SIF_DK1, SIF_DK2, SIF_DK3, SIF_DK_NICAM, SIF_L, SIF_M, SIF_M_BTSC, SIF_M_A2,
+ SIF_M_EIA_J, SIF_I_NICAM, SIF_L_NICAM, SIF_L_PRIME})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SifStandard {}
+
+ /**
+ * Undefined Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_UNDEFINED = Constants.FrontendAnalogSifStandard.UNDEFINED;
+ /**
+ * BG Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_BG = Constants.FrontendAnalogSifStandard.BG;
+ /**
+ * BG-A2 Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2;
+ /**
+ * BG-NICAM Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_BG_NICAM = Constants.FrontendAnalogSifStandard.BG_NICAM;
+ /**
+ * I Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_I = Constants.FrontendAnalogSifStandard.I;
+ /**
+ * DK Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_DK = Constants.FrontendAnalogSifStandard.DK;
+ /**
+ * DK1 Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1;
+ /**
+ * DK2 Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2;
+ /**
+ * DK3 Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3;
+ /**
+ * DK-NICAM Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_DK_NICAM = Constants.FrontendAnalogSifStandard.DK_NICAM;
+ /**
+ * L Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_L = Constants.FrontendAnalogSifStandard.L;
+ /**
+ * M Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_M = Constants.FrontendAnalogSifStandard.M;
+ /**
+ * M-BTSC Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC;
+ /**
+ * M-A2 Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2;
+ /**
+ * M-EIA-J Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_M_EIA_J = Constants.FrontendAnalogSifStandard.M_EIA_J;
+ /**
+ * I-NICAM Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_I_NICAM = Constants.FrontendAnalogSifStandard.I_NICAM;
+ /**
+ * L-NICAM Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_L_NICAM = Constants.FrontendAnalogSifStandard.L_NICAM;
+ /**
+ * L-PRIME Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_L_PRIME = Constants.FrontendAnalogSifStandard.L_PRIME;
+
+
+ private final int mAnalogType;
+ private final int mSifStandard;
@Override
public int getType() {
- return TunerConstants.FRONTEND_TYPE_ANALOG;
+ return FrontendSettings.TYPE_ANALOG;
}
+
+ /**
+ * Gets analog signal type.
+ */
+ @SignalType
public int getAnalogType() {
return mAnalogType;
}
+ /**
+ * Gets Standard Interchange Format (SIF).
+ */
+ @SifStandard
public int getSifStandard() {
return mSifStandard;
}
/**
- * Creates a new builder object.
+ * Creates a builder for {@link AnalogFrontendSettings}.
*/
+ @NonNull
public static Builder newBuilder() {
return new Builder();
}
@@ -54,7 +177,7 @@
}
/**
- * Builder for FrontendAnalogSettings.
+ * Builder for {@link AnalogFrontendSettings}.
*/
public static class Builder {
private int mFrequency;
@@ -64,8 +187,9 @@
private Builder() {}
/**
- * Sets frequency.
+ * Sets frequency in Hz.
*/
+ @NonNull
public Builder setFrequency(int frequency) {
mFrequency = frequency;
return this;
@@ -74,22 +198,25 @@
/**
* Sets analog type.
*/
- public Builder setAnalogType(int analogType) {
+ @NonNull
+ public Builder setAnalogType(@SignalType int analogType) {
mAnalogType = analogType;
return this;
}
/**
- * Sets sif standard.
+ * Sets Standard Interchange Format (SIF).
*/
- public Builder setSifStandard(int sifStandard) {
+ @NonNull
+ public Builder setSifStandard(@SifStandard int sifStandard) {
mSifStandard = sifStandard;
return this;
}
/**
- * Builds a FrontendAnalogSettings instance.
+ * Builds a {@link AnalogFrontendSettings} object.
*/
+ @NonNull
public AnalogFrontendSettings build() {
return new AnalogFrontendSettings(mFrequency, mAnalogType, mSifStandard);
}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java
index 677f9387..1fd1f63 100644
--- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java
@@ -28,8 +28,8 @@
private final int mFecCap;
private final int mDemodOutputFormatCap;
- Atsc3FrontendCapabilities(int bandwidthCap, int modulationCap, int timeInterleaveModeCap,
- int codeRateCap, int fecCap, int demodOutputFormatCap) {
+ private Atsc3FrontendCapabilities(int bandwidthCap, int modulationCap,
+ int timeInterleaveModeCap, int codeRateCap, int fecCap, int demodOutputFormatCap) {
mBandwidthCap = bandwidthCap;
mModulationCap = modulationCap;
mTimeInterleaveModeCap = timeInterleaveModeCap;
@@ -38,27 +38,45 @@
mDemodOutputFormatCap = demodOutputFormatCap;
}
- /** Gets bandwidth capability. */
+ /**
+ * Gets bandwidth capability.
+ */
+ @Atsc3FrontendSettings.Bandwidth
public int getBandwidthCapability() {
return mBandwidthCap;
}
- /** Gets modulation capability. */
+ /**
+ * Gets modulation capability.
+ */
+ @Atsc3FrontendSettings.Modulation
public int getModulationCapability() {
return mModulationCap;
}
- /** Gets time interleave mod capability. */
+ /**
+ * Gets time interleave mod capability.
+ */
+ @Atsc3FrontendSettings.TimeInterleaveMode
public int getTimeInterleaveModeCapability() {
return mTimeInterleaveModeCap;
}
- /** Gets code rate capability. */
+ /**
+ * Gets code rate capability.
+ */
+ @Atsc3FrontendSettings.CodeRate
public int getCodeRateCapability() {
return mCodeRateCap;
}
- /** Gets FEC capability. */
+ /**
+ * Gets FEC capability.
+ */
+ @Atsc3FrontendSettings.Fec
public int getFecCapability() {
return mFecCap;
}
- /** Gets demodulator output format capability. */
+ /**
+ * Gets demodulator output format capability.
+ */
+ @Atsc3FrontendSettings.DemodOutputFormat
public int getDemodOutputFormatCapability() {
return mDemodOutputFormatCap;
}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
index bce8a64..5e1ba72 100644
--- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
@@ -16,27 +16,361 @@
package android.media.tv.tuner.frontend;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
-import java.util.List;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Frontend settings for ATSC-3.
* @hide
*/
public class Atsc3FrontendSettings extends FrontendSettings {
- public int bandwidth;
- public byte demodOutputFormat;
- public List<Atsc3PlpSettings> plpSettings;
- Atsc3FrontendSettings(int frequency) {
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "BANDWIDTH_",
+ value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_BANDWIDTH_6MHZ,
+ BANDWIDTH_BANDWIDTH_7MHZ, BANDWIDTH_BANDWIDTH_8MHZ})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Bandwidth {}
+
+ /**
+ * Bandwidth not defined.
+ */
+ public static final int BANDWIDTH_UNDEFINED =
+ Constants.FrontendAtsc3Bandwidth.UNDEFINED;
+ /**
+ * Hardware is able to detect and set bandwidth automatically
+ */
+ public static final int BANDWIDTH_AUTO = Constants.FrontendAtsc3Bandwidth.AUTO;
+ /**
+ * 6 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_BANDWIDTH_6MHZ =
+ Constants.FrontendAtsc3Bandwidth.BANDWIDTH_6MHZ;
+ /**
+ * 7 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_BANDWIDTH_7MHZ =
+ Constants.FrontendAtsc3Bandwidth.BANDWIDTH_7MHZ;
+ /**
+ * 8 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_BANDWIDTH_8MHZ =
+ Constants.FrontendAtsc3Bandwidth.BANDWIDTH_8MHZ;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO,
+ MODULATION_MOD_QPSK, MODULATION_MOD_16QAM,
+ MODULATION_MOD_64QAM, MODULATION_MOD_256QAM,
+ MODULATION_MOD_1024QAM, MODULATION_MOD_4096QAM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
+
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendAtsc3Modulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically.
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendAtsc3Modulation.AUTO;
+ /**
+ * QPSK modulation.
+ */
+ public static final int MODULATION_MOD_QPSK = Constants.FrontendAtsc3Modulation.MOD_QPSK;
+ /**
+ * 16QAM modulation.
+ */
+ public static final int MODULATION_MOD_16QAM = Constants.FrontendAtsc3Modulation.MOD_16QAM;
+ /**
+ * 64QAM modulation.
+ */
+ public static final int MODULATION_MOD_64QAM = Constants.FrontendAtsc3Modulation.MOD_64QAM;
+ /**
+ * 256QAM modulation.
+ */
+ public static final int MODULATION_MOD_256QAM = Constants.FrontendAtsc3Modulation.MOD_256QAM;
+ /**
+ * 1024QAM modulation.
+ */
+ public static final int MODULATION_MOD_1024QAM = Constants.FrontendAtsc3Modulation.MOD_1024QAM;
+ /**
+ * 4096QAM modulation.
+ */
+ public static final int MODULATION_MOD_4096QAM = Constants.FrontendAtsc3Modulation.MOD_4096QAM;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "TIME_INTERLEAVE_MODE_",
+ value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO,
+ TIME_INTERLEAVE_MODE_CTI, TIME_INTERLEAVE_MODE_HTI})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TimeInterleaveMode {}
+
+ /**
+ * Time interleave mode undefined.
+ */
+ public static final int TIME_INTERLEAVE_MODE_UNDEFINED =
+ Constants.FrontendAtsc3TimeInterleaveMode.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Time Interleave Mode automatically.
+ */
+ public static final int TIME_INTERLEAVE_MODE_AUTO =
+ Constants.FrontendAtsc3TimeInterleaveMode.AUTO;
+ /**
+ * CTI Time Interleave Mode.
+ */
+ public static final int TIME_INTERLEAVE_MODE_CTI =
+ Constants.FrontendAtsc3TimeInterleaveMode.CTI;
+ /**
+ * HTI Time Interleave Mode.
+ */
+ public static final int TIME_INTERLEAVE_MODE_HTI =
+ Constants.FrontendAtsc3TimeInterleaveMode.HTI;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "CODERATE_",
+ value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_2_15, CODERATE_3_15, CODERATE_4_15,
+ CODERATE_5_15, CODERATE_6_15, CODERATE_7_15, CODERATE_8_15, CODERATE_9_15,
+ CODERATE_10_15, CODERATE_11_15, CODERATE_12_15, CODERATE_13_15})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CodeRate {}
+
+ /**
+ * Code rate undefined.
+ */
+ public static final int CODERATE_UNDEFINED = Constants.FrontendAtsc3CodeRate.UNDEFINED;
+ /**
+ * Hardware is able to detect and set code rate automatically
+ */
+ public static final int CODERATE_AUTO = Constants.FrontendAtsc3CodeRate.AUTO;
+ /**
+ * 2/15 code rate.
+ */
+ public static final int CODERATE_2_15 = Constants.FrontendAtsc3CodeRate.CODERATE_2_15;
+ /**
+ * 3/15 code rate.
+ */
+ public static final int CODERATE_3_15 = Constants.FrontendAtsc3CodeRate.CODERATE_3_15;
+ /**
+ * 4/15 code rate.
+ */
+ public static final int CODERATE_4_15 = Constants.FrontendAtsc3CodeRate.CODERATE_4_15;
+ /**
+ * 5/15 code rate.
+ */
+ public static final int CODERATE_5_15 = Constants.FrontendAtsc3CodeRate.CODERATE_5_15;
+ /**
+ * 6/15 code rate.
+ */
+ public static final int CODERATE_6_15 = Constants.FrontendAtsc3CodeRate.CODERATE_6_15;
+ /**
+ * 7/15 code rate.
+ */
+ public static final int CODERATE_7_15 = Constants.FrontendAtsc3CodeRate.CODERATE_7_15;
+ /**
+ * 8/15 code rate.
+ */
+ public static final int CODERATE_8_15 = Constants.FrontendAtsc3CodeRate.CODERATE_8_15;
+ /**
+ * 9/15 code rate.
+ */
+ public static final int CODERATE_9_15 = Constants.FrontendAtsc3CodeRate.CODERATE_9_15;
+ /**
+ * 10/15 code rate.
+ */
+ public static final int CODERATE_10_15 = Constants.FrontendAtsc3CodeRate.CODERATE_10_15;
+ /**
+ * 11/15 code rate.
+ */
+ public static final int CODERATE_11_15 = Constants.FrontendAtsc3CodeRate.CODERATE_11_15;
+ /**
+ * 12/15 code rate.
+ */
+ public static final int CODERATE_12_15 = Constants.FrontendAtsc3CodeRate.CODERATE_12_15;
+ /**
+ * 13/15 code rate.
+ */
+ public static final int CODERATE_13_15 = Constants.FrontendAtsc3CodeRate.CODERATE_13_15;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "FEC_",
+ value = {FEC_UNDEFINED, FEC_AUTO, FEC_BCH_LDPC_16K, FEC_BCH_LDPC_64K, FEC_CRC_LDPC_16K,
+ FEC_CRC_LDPC_64K, FEC_LDPC_16K, FEC_LDPC_64K})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Fec {}
+
+ /**
+ * Forward Error Correction undefined.
+ */
+ public static final int FEC_UNDEFINED = Constants.FrontendAtsc3Fec.UNDEFINED;
+ /**
+ * Hardware is able to detect and set FEC automatically
+ */
+ public static final int FEC_AUTO = Constants.FrontendAtsc3Fec.AUTO;
+ /**
+ * BCH LDPC 16K Forward Error Correction
+ */
+ public static final int FEC_BCH_LDPC_16K = Constants.FrontendAtsc3Fec.BCH_LDPC_16K;
+ /**
+ * BCH LDPC 64K Forward Error Correction
+ */
+ public static final int FEC_BCH_LDPC_64K = Constants.FrontendAtsc3Fec.BCH_LDPC_64K;
+ /**
+ * CRC LDPC 16K Forward Error Correction
+ */
+ public static final int FEC_CRC_LDPC_16K = Constants.FrontendAtsc3Fec.CRC_LDPC_16K;
+ /**
+ * CRC LDPC 64K Forward Error Correction
+ */
+ public static final int FEC_CRC_LDPC_64K = Constants.FrontendAtsc3Fec.CRC_LDPC_64K;
+ /**
+ * LDPC 16K Forward Error Correction
+ */
+ public static final int FEC_LDPC_16K = Constants.FrontendAtsc3Fec.LDPC_16K;
+ /**
+ * LDPC 64K Forward Error Correction
+ */
+ public static final int FEC_LDPC_64K = Constants.FrontendAtsc3Fec.LDPC_64K;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "DEMOD_OUTPUT_FORMAT_",
+ value = {DEMOD_OUTPUT_FORMAT_UNDEFINED, DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET,
+ DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DemodOutputFormat {}
+
+ /**
+ * Demod output format undefined.
+ */
+ public static final int DEMOD_OUTPUT_FORMAT_UNDEFINED =
+ Constants.FrontendAtsc3DemodOutputFormat.UNDEFINED;
+ /**
+ * ALP format. Typically used in US region.
+ */
+ public static final int DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET =
+ Constants.FrontendAtsc3DemodOutputFormat.ATSC3_LINKLAYER_PACKET;
+ /**
+ * BaseBand packet format. Typically used in Korea region.
+ */
+ public static final int DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET =
+ Constants.FrontendAtsc3DemodOutputFormat.BASEBAND_PACKET;
+
+ public final int mBandwidth;
+ public final int mDemodOutputFormat;
+ public final Atsc3PlpSettings[] mPlpSettings;
+
+ private Atsc3FrontendSettings(int frequency, int bandwidth, int demodOutputFormat,
+ Atsc3PlpSettings[] plpSettings) {
super(frequency);
+ mBandwidth = bandwidth;
+ mDemodOutputFormat = demodOutputFormat;
+ mPlpSettings = plpSettings;
+ }
+
+ /**
+ * Gets bandwidth.
+ */
+ @Bandwidth
+ public int getBandwidth() {
+ return mBandwidth;
+ }
+ /**
+ * Gets Demod Output Format.
+ */
+ @DemodOutputFormat
+ public int getDemodOutputFormat() {
+ return mDemodOutputFormat;
+ }
+ /**
+ * Gets PLP Settings.
+ */
+ public Atsc3PlpSettings[] getPlpSettings() {
+ return mPlpSettings;
+ }
+
+ /**
+ * Creates a builder for {@link Atsc3FrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link Atsc3FrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mBandwidth;
+ private byte mDemodOutputFormat;
+ private Atsc3PlpSettings[] mPlpSettings;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets bandwidth.
+ */
+ @NonNull
+ public Builder setBandwidth(int bandwidth) {
+ mBandwidth = bandwidth;
+ return this;
+ }
+ /**
+ * Sets Demod Output Format.
+ */
+ @NonNull
+ public Builder setDemodOutputFormat(byte demodOutputFormat) {
+ mDemodOutputFormat = demodOutputFormat;
+ return this;
+ }
+ /**
+ * Sets PLP Settings.
+ */
+ @NonNull
+ public Builder setPlpSettings(Atsc3PlpSettings[] plpSettings) {
+ mPlpSettings = plpSettings;
+ return this;
+ }
+
+ /**
+ * Builds a {@link Atsc3FrontendSettings} object.
+ */
+ @NonNull
+ public Atsc3FrontendSettings build() {
+ return new Atsc3FrontendSettings(
+ mFrequency, mBandwidth, mDemodOutputFormat, mPlpSettings);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
@Override
public int getType() {
- return TunerConstants.FRONTEND_TYPE_ATSC3;
+ return FrontendSettings.TYPE_ATSC3;
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
index 61c6fec..43a68a0 100644
--- a/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
@@ -16,14 +16,138 @@
package android.media.tv.tuner.frontend;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
/**
* PLP settings for ATSC-3.
* @hide
*/
public class Atsc3PlpSettings {
- public byte plpId;
- public int modulation;
- public int interleaveMode;
- public int codeRate;
- public int fec;
+ private final int mPlpId;
+ private final int mModulation;
+ private final int mInterleaveMode;
+ private final int mCodeRate;
+ private final int mFec;
+
+ private Atsc3PlpSettings(int plpId, int modulation, int interleaveMode, int codeRate, int fec) {
+ mPlpId = plpId;
+ mModulation = modulation;
+ mInterleaveMode = interleaveMode;
+ mCodeRate = codeRate;
+ mFec = fec;
+ }
+
+ /**
+ * Gets Physical Layer Pipe (PLP) ID.
+ */
+ public int getPlpId() {
+ return mPlpId;
+ }
+ /**
+ * Gets Modulation.
+ */
+ @Atsc3FrontendSettings.Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Interleave Mode.
+ */
+ @Atsc3FrontendSettings.TimeInterleaveMode
+ public int getInterleaveMode() {
+ return mInterleaveMode;
+ }
+ /**
+ * Gets Code Rate.
+ */
+ @Atsc3FrontendSettings.CodeRate
+ public int getCodeRate() {
+ return mCodeRate;
+ }
+ /**
+ * Gets Forward Error Correction.
+ */
+ @Atsc3FrontendSettings.Fec
+ public int getFec() {
+ return mFec;
+ }
+
+ /**
+ * Creates a builder for {@link Atsc3PlpSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link Atsc3PlpSettings}.
+ */
+ public static class Builder {
+ private int mPlpId;
+ private int mModulation;
+ private int mInterleaveMode;
+ private int mCodeRate;
+ private int mFec;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Physical Layer Pipe (PLP) ID.
+ */
+ @NonNull
+ public Builder setPlpId(int plpId) {
+ mPlpId = plpId;
+ return this;
+ }
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Atsc3FrontendSettings.Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Interleave Mode.
+ */
+ @NonNull
+ public Builder setInterleaveMode(
+ @Atsc3FrontendSettings.TimeInterleaveMode int interleaveMode) {
+ mInterleaveMode = interleaveMode;
+ return this;
+ }
+ /**
+ * Sets Code Rate.
+ */
+ @NonNull
+ public Builder setCodeRate(@Atsc3FrontendSettings.CodeRate int codeRate) {
+ mCodeRate = codeRate;
+ return this;
+ }
+ /**
+ * Sets Forward Error Correction.
+ */
+ @NonNull
+ public Builder setFec(@Atsc3FrontendSettings.Fec int fec) {
+ mFec = fec;
+ return this;
+ }
+
+ /**
+ * Builds a {@link Atsc3PlpSettings} object.
+ */
+ @NonNull
+ public Atsc3PlpSettings build() {
+ return new Atsc3PlpSettings(mPlpId, mModulation, mInterleaveMode, mCodeRate, mFec);
+ }
+ }
}
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
index 6ae3c63..0ff516d 100644
--- a/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
@@ -23,10 +23,14 @@
public class AtscFrontendCapabilities extends FrontendCapabilities {
private final int mModulationCap;
- AtscFrontendCapabilities(int modulationCap) {
+ private AtscFrontendCapabilities(int modulationCap) {
mModulationCap = modulationCap;
}
- /** Gets modulation capability. */
+
+ /**
+ * Gets modulation capability.
+ */
+ @AtscFrontendSettings.Modulation
public int getModulationCapability() {
return mModulationCap;
}
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
index 14c5cdd..32901d8 100644
--- a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
@@ -16,22 +16,109 @@
package android.media.tv.tuner.frontend;
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Frontend settings for ATSC.
* @hide
*/
public class AtscFrontendSettings extends FrontendSettings {
- public int modulation;
- AtscFrontendSettings(int frequency) {
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_8VSB,
+ MODULATION_MOD_16VSB})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
+
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendAtscModulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendAtscModulation.AUTO;
+ /**
+ * 8VSB Modulation.
+ */
+ public static final int MODULATION_MOD_8VSB = Constants.FrontendAtscModulation.MOD_8VSB;
+ /**
+ * 16VSB Modulation.
+ */
+ public static final int MODULATION_MOD_16VSB = Constants.FrontendAtscModulation.MOD_16VSB;
+
+
+ private final int mModulation;
+
+ private AtscFrontendSettings(int frequency, int modulation) {
super(frequency);
+ mModulation = modulation;
+ }
+
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+
+ /**
+ * Creates a builder for {@link AtscFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link AtscFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mModulation;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AtscFrontendSettings} object.
+ */
+ @NonNull
+ public AtscFrontendSettings build() {
+ return new AtscFrontendSettings(mFrequency, mModulation);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
@Override
public int getType() {
- return TunerConstants.FRONTEND_TYPE_ATSC;
+ return FrontendSettings.TYPE_ATSC;
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java
index edea7af..f3fbdb5 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java
@@ -16,6 +16,8 @@
package android.media.tv.tuner.frontend;
+import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
+
/**
* DVBC Capabilities.
* @hide
@@ -25,21 +27,30 @@
private final int mFecCap;
private final int mAnnexCap;
- DvbcFrontendCapabilities(int modulationCap, int fecCap, int annexCap) {
+ private DvbcFrontendCapabilities(int modulationCap, int fecCap, int annexCap) {
mModulationCap = modulationCap;
mFecCap = fecCap;
mAnnexCap = annexCap;
}
- /** Gets modulation capability. */
+ /**
+ * Gets modulation capability.
+ */
+ @DvbcFrontendSettings.Modulation
public int getModulationCapability() {
return mModulationCap;
}
- /** Gets FEC capability. */
+ /**
+ * Gets inner FEC capability.
+ */
+ @FrontendInnerFec
public int getFecCapability() {
return mFecCap;
}
- /** Gets annex capability. */
+ /**
+ * Gets annex capability.
+ */
+ @DvbcFrontendSettings.Annex
public int getAnnexCapability() {
return mAnnexCap;
}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
index 07e49ff..3d212d3 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
@@ -16,27 +16,283 @@
package android.media.tv.tuner.frontend;
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Frontend settings for DVBC.
* @hide
*/
public class DvbcFrontendSettings extends FrontendSettings {
- public int modulation;
- public long fec;
- public int symbolRate;
- public int outerFec;
- public byte annex;
- public int spectralInversion;
- DvbcFrontendSettings(int frequency) {
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_16QAM,
+ MODULATION_MOD_32QAM, MODULATION_MOD_64QAM, MODULATION_MOD_128QAM,
+ MODULATION_MOD_256QAM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
+
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendDvbcModulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendDvbcModulation.AUTO;
+ /**
+ * 16QAM Modulation.
+ */
+ public static final int MODULATION_MOD_16QAM = Constants.FrontendDvbcModulation.MOD_16QAM;
+ /**
+ * 32QAM Modulation.
+ */
+ public static final int MODULATION_MOD_32QAM = Constants.FrontendDvbcModulation.MOD_32QAM;
+ /**
+ * 64QAM Modulation.
+ */
+ public static final int MODULATION_MOD_64QAM = Constants.FrontendDvbcModulation.MOD_64QAM;
+ /**
+ * 128QAM Modulation.
+ */
+ public static final int MODULATION_MOD_128QAM = Constants.FrontendDvbcModulation.MOD_128QAM;
+ /**
+ * 256QAM Modulation.
+ */
+ public static final int MODULATION_MOD_256QAM = Constants.FrontendDvbcModulation.MOD_256QAM;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "OUTER_FEC_",
+ value = {OUTER_FEC_UNDEFINED, OUTER_FEC_OUTER_FEC_NONE, OUTER_FEC_OUTER_FEC_RS})
+ public @interface OuterFec {}
+
+ /**
+ * Outer Forward Error Correction (FEC) Type undefined.
+ */
+ public static final int OUTER_FEC_UNDEFINED = Constants.FrontendDvbcOuterFec.UNDEFINED;
+ /**
+ * None Outer Forward Error Correction (FEC) Type.
+ */
+ public static final int OUTER_FEC_OUTER_FEC_NONE =
+ Constants.FrontendDvbcOuterFec.OUTER_FEC_NONE;
+ /**
+ * RS Outer Forward Error Correction (FEC) Type.
+ */
+ public static final int OUTER_FEC_OUTER_FEC_RS = Constants.FrontendDvbcOuterFec.OUTER_FEC_RS;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "ANNEX_",
+ value = {ANNEX_UNDEFINED, ANNEX_A, ANNEX_B, ANNEX_C})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Annex {}
+
+ /**
+ * Annex Type undefined.
+ */
+ public static final int ANNEX_UNDEFINED = Constants.FrontendDvbcAnnex.UNDEFINED;
+ /**
+ * Annex Type A.
+ */
+ public static final int ANNEX_A = Constants.FrontendDvbcAnnex.A;
+ /**
+ * Annex Type B.
+ */
+ public static final int ANNEX_B = Constants.FrontendDvbcAnnex.B;
+ /**
+ * Annex Type C.
+ */
+ public static final int ANNEX_C = Constants.FrontendDvbcAnnex.C;
+
+
+ /** @hide */
+ @IntDef(prefix = "SPECTRAL_INVERSION_",
+ value = {SPECTRAL_INVERSION_UNDEFINED, SPECTRAL_INVERSION_NORMAL,
+ SPECTRAL_INVERSION_INVERTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SpectralInversion {}
+
+ /**
+ * Spectral Inversion Type undefined.
+ */
+ public static final int SPECTRAL_INVERSION_UNDEFINED =
+ Constants.FrontendDvbcSpectralInversion.UNDEFINED;
+ /**
+ * Normal Spectral Inversion.
+ */
+ public static final int SPECTRAL_INVERSION_NORMAL =
+ Constants.FrontendDvbcSpectralInversion.NORMAL;
+ /**
+ * Inverted Spectral Inversion.
+ */
+ public static final int SPECTRAL_INVERSION_INVERTED =
+ Constants.FrontendDvbcSpectralInversion.INVERTED;
+
+
+ private final int mModulation;
+ private final long mFec;
+ private final int mSymbolRate;
+ private final int mOuterFec;
+ private final byte mAnnex;
+ private final int mSpectralInversion;
+
+ private DvbcFrontendSettings(int frequency, int modulation, long fec, int symbolRate,
+ int outerFec, byte annex, int spectralInversion) {
super(frequency);
+ mModulation = modulation;
+ mFec = fec;
+ mSymbolRate = symbolRate;
+ mOuterFec = outerFec;
+ mAnnex = annex;
+ mSpectralInversion = spectralInversion;
+ }
+
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Inner Forward Error Correction.
+ */
+ @FrontendInnerFec
+ public long getFec() {
+ return mFec;
+ }
+ /**
+ * Gets Symbol Rate in symbols per second.
+ */
+ public int getSymbolRate() {
+ return mSymbolRate;
+ }
+ /**
+ * Gets Outer Forward Error Correction.
+ */
+ @OuterFec
+ public int getOuterFec() {
+ return mOuterFec;
+ }
+ /**
+ * Gets Annex.
+ */
+ @Annex
+ public byte getAnnex() {
+ return mAnnex;
+ }
+ /**
+ * Gets Spectral Inversion.
+ */
+ @SpectralInversion
+ public int getSpectralInversion() {
+ return mSpectralInversion;
+ }
+
+ /**
+ * Creates a builder for {@link DvbcFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link DvbcFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mModulation;
+ private long mFec;
+ private int mSymbolRate;
+ private int mOuterFec;
+ private byte mAnnex;
+ private int mSpectralInversion;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Inner Forward Error Correction.
+ */
+ @NonNull
+ public Builder setFec(@FrontendInnerFec long fec) {
+ mFec = fec;
+ return this;
+ }
+ /**
+ * Sets Symbol Rate in symbols per second.
+ */
+ @NonNull
+ public Builder setSymbolRate(int symbolRate) {
+ mSymbolRate = symbolRate;
+ return this;
+ }
+ /**
+ * Sets Outer Forward Error Correction.
+ */
+ @NonNull
+ public Builder setOuterFec(@OuterFec int outerFec) {
+ mOuterFec = outerFec;
+ return this;
+ }
+ /**
+ * Sets Annex.
+ */
+ @NonNull
+ public Builder setAnnex(@Annex byte annex) {
+ mAnnex = annex;
+ return this;
+ }
+ /**
+ * Sets Spectral Inversion.
+ */
+ @NonNull
+ public Builder setSpectralInversion(@SpectralInversion int spectralInversion) {
+ mSpectralInversion = spectralInversion;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DvbcFrontendSettings} object.
+ */
+ @NonNull
+ public DvbcFrontendSettings build() {
+ return new DvbcFrontendSettings(mFrequency, mModulation, mFec, mSymbolRate, mOuterFec,
+ mAnnex, mSpectralInversion);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
@Override
public int getType() {
- return TunerConstants.FRONTEND_TYPE_DVBC;
+ return FrontendSettings.TYPE_DVBC;
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
index bfa4391..04d3375 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
@@ -16,13 +16,118 @@
package android.media.tv.tuner.frontend;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
+import android.media.tv.tuner.TunerUtils;
+
/**
* Code rate for DVBS.
* @hide
*/
public class DvbsCodeRate {
- public long fec;
- public boolean isLinear;
- public boolean isShortFrames;
- public int bitsPer1000Symbol;
+ private final long mFec;
+ private final boolean mIsLinear;
+ private final boolean mIsShortFrames;
+ private final int mBitsPer1000Symbol;
+
+ private DvbsCodeRate(long fec, boolean isLinear, boolean isShortFrames, int bitsPer1000Symbol) {
+ mFec = fec;
+ mIsLinear = isLinear;
+ mIsShortFrames = isShortFrames;
+ mBitsPer1000Symbol = bitsPer1000Symbol;
+ }
+
+ /**
+ * Gets inner FEC.
+ */
+ @FrontendInnerFec
+ public long getFec() {
+ return mFec;
+ }
+ /**
+ * Checks whether it's linear.
+ */
+ public boolean isLinear() {
+ return mIsLinear;
+ }
+ /**
+ * Checks whether short frame enabled.
+ */
+ public boolean isShortFrameEnabled() {
+ return mIsShortFrames;
+ }
+ /**
+ * Gets bits number in 1000 symbols. 0 by default.
+ */
+ public int getBitsPer1000Symbol() {
+ return mBitsPer1000Symbol;
+ }
+
+ /**
+ * Creates a builder for {@link DvbsCodeRate}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link DvbsCodeRate}.
+ */
+ public static class Builder {
+ private long mFec;
+ private boolean mIsLinear;
+ private boolean mIsShortFrames;
+ private int mBitsPer1000Symbol;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets inner FEC.
+ */
+ @NonNull
+ public Builder setFec(@FrontendInnerFec long fec) {
+ mFec = fec;
+ return this;
+ }
+ /**
+ * Sets whether it's linear.
+ */
+ @NonNull
+ public Builder setLinear(boolean isLinear) {
+ mIsLinear = isLinear;
+ return this;
+ }
+ /**
+ * Sets whether short frame enabled.
+ */
+ @NonNull
+ public Builder setShortFrameEnabled(boolean isShortFrames) {
+ mIsShortFrames = isShortFrames;
+ return this;
+ }
+ /**
+ * Sets bits number in 1000 symbols.
+ */
+ @NonNull
+ public Builder setBitsPer1000Symbol(int bitsPer1000Symbol) {
+ mBitsPer1000Symbol = bitsPer1000Symbol;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DvbsCodeRate} object.
+ */
+ @NonNull
+ public DvbsCodeRate build() {
+ return new DvbsCodeRate(mFec, mIsLinear, mIsShortFrames, mBitsPer1000Symbol);
+ }
+ }
}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java
index f5a4157..bd615d0 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java
@@ -16,6 +16,8 @@
package android.media.tv.tuner.frontend;
+import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
+
/**
* DVBS Capabilities.
* @hide
@@ -25,21 +27,30 @@
private final long mInnerFecCap;
private final int mStandard;
- DvbsFrontendCapabilities(int modulationCap, long innerFecCap, int standard) {
+ private DvbsFrontendCapabilities(int modulationCap, long innerFecCap, int standard) {
mModulationCap = modulationCap;
mInnerFecCap = innerFecCap;
mStandard = standard;
}
- /** Gets modulation capability. */
+ /**
+ * Gets modulation capability.
+ */
+ @DvbsFrontendSettings.Modulation
public int getModulationCapability() {
return mModulationCap;
}
- /** Gets inner FEC capability. */
+ /**
+ * Gets inner FEC capability.
+ */
+ @FrontendInnerFec
public long getInnerFecCapability() {
return mInnerFecCap;
}
- /** Gets DVBS standard capability. */
+ /**
+ * Gets DVBS standard capability.
+ */
+ @DvbsFrontendSettings.Standard
public int getStandardCapability() {
return mStandard;
}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
index 23c0a7b1..5b3bffc4 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -16,28 +16,348 @@
package android.media.tv.tuner.frontend;
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Frontend settings for DVBS.
* @hide
*/
public class DvbsFrontendSettings extends FrontendSettings {
- public int modulation;
- public DvbsCodeRate coderate;
- public int symbolRate;
- public int rolloff;
- public int pilot;
- public int inputStreamId;
- public byte standard;
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_QPSK,
+ MODULATION_MOD_8PSK, MODULATION_MOD_16QAM, MODULATION_MOD_16PSK,
+ MODULATION_MOD_32PSK, MODULATION_MOD_ACM, MODULATION_MOD_8APSK,
+ MODULATION_MOD_16APSK, MODULATION_MOD_32APSK, MODULATION_MOD_64APSK,
+ MODULATION_MOD_128APSK, MODULATION_MOD_256APSK, MODULATION_MOD_RESERVED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
- DvbsFrontendSettings(int frequency) {
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendDvbsModulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendDvbsModulation.AUTO;
+ /**
+ * QPSK Modulation.
+ */
+ public static final int MODULATION_MOD_QPSK = Constants.FrontendDvbsModulation.MOD_QPSK;
+ /**
+ * 8PSK Modulation.
+ */
+ public static final int MODULATION_MOD_8PSK = Constants.FrontendDvbsModulation.MOD_8PSK;
+ /**
+ * 16QAM Modulation.
+ */
+ public static final int MODULATION_MOD_16QAM = Constants.FrontendDvbsModulation.MOD_16QAM;
+ /**
+ * 16PSK Modulation.
+ */
+ public static final int MODULATION_MOD_16PSK = Constants.FrontendDvbsModulation.MOD_16PSK;
+ /**
+ * 32PSK Modulation.
+ */
+ public static final int MODULATION_MOD_32PSK = Constants.FrontendDvbsModulation.MOD_32PSK;
+ /**
+ * ACM Modulation.
+ */
+ public static final int MODULATION_MOD_ACM = Constants.FrontendDvbsModulation.MOD_ACM;
+ /**
+ * 8APSK Modulation.
+ */
+ public static final int MODULATION_MOD_8APSK = Constants.FrontendDvbsModulation.MOD_8APSK;
+ /**
+ * 16APSK Modulation.
+ */
+ public static final int MODULATION_MOD_16APSK = Constants.FrontendDvbsModulation.MOD_16APSK;
+ /**
+ * 32APSK Modulation.
+ */
+ public static final int MODULATION_MOD_32APSK = Constants.FrontendDvbsModulation.MOD_32APSK;
+ /**
+ * 64APSK Modulation.
+ */
+ public static final int MODULATION_MOD_64APSK = Constants.FrontendDvbsModulation.MOD_64APSK;
+ /**
+ * 128APSK Modulation.
+ */
+ public static final int MODULATION_MOD_128APSK = Constants.FrontendDvbsModulation.MOD_128APSK;
+ /**
+ * 256APSK Modulation.
+ */
+ public static final int MODULATION_MOD_256APSK = Constants.FrontendDvbsModulation.MOD_256APSK;
+ /**
+ * Reversed Modulation.
+ */
+ public static final int MODULATION_MOD_RESERVED = Constants.FrontendDvbsModulation.MOD_RESERVED;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "ROLLOFF_",
+ value = {ROLLOFF_UNDEFINED, ROLLOFF_0_35, ROLLOFF_0_25, ROLLOFF_0_20, ROLLOFF_0_15,
+ ROLLOFF_0_10, ROLLOFF_0_5})
+ public @interface Rolloff {}
+
+ /**
+ * Roll Off undefined.
+ */
+ public static final int ROLLOFF_UNDEFINED = Constants.FrontendDvbsRolloff.UNDEFINED;
+ /**
+ * Roll Off 0_35.
+ */
+ public static final int ROLLOFF_0_35 = Constants.FrontendDvbsRolloff.ROLLOFF_0_35;
+ /**
+ * Roll Off 0_25.
+ */
+ public static final int ROLLOFF_0_25 = Constants.FrontendDvbsRolloff.ROLLOFF_0_25;
+ /**
+ * Roll Off 0_2.
+ */
+ public static final int ROLLOFF_0_20 = Constants.FrontendDvbsRolloff.ROLLOFF_0_20;
+ /**
+ * Roll Off 0_15.
+ */
+ public static final int ROLLOFF_0_15 = Constants.FrontendDvbsRolloff.ROLLOFF_0_15;
+ /**
+ * Roll Off 0_1.
+ */
+ public static final int ROLLOFF_0_10 = Constants.FrontendDvbsRolloff.ROLLOFF_0_10;
+ /**
+ * Roll Off 0_5.
+ */
+ public static final int ROLLOFF_0_5 = Constants.FrontendDvbsRolloff.ROLLOFF_0_5;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "PILOT_",
+ value = {PILOT_UNDEFINED, PILOT_ON, PILOT_OFF, PILOT_AUTO})
+ public @interface Pilot {}
+
+ /**
+ * Pilot mode undefined.
+ */
+ public static final int PILOT_UNDEFINED = Constants.FrontendDvbsPilot.UNDEFINED;
+ /**
+ * Pilot mode on.
+ */
+ public static final int PILOT_ON = Constants.FrontendDvbsPilot.ON;
+ /**
+ * Pilot mode off.
+ */
+ public static final int PILOT_OFF = Constants.FrontendDvbsPilot.OFF;
+ /**
+ * Pilot mode auto.
+ */
+ public static final int PILOT_AUTO = Constants.FrontendDvbsPilot.AUTO;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "STANDARD_",
+ value = {STANDARD_AUTO, STANDARD_S, STANDARD_S2, STANDARD_S2X})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Standard {}
+
+ /**
+ * Standard undefined.
+ */
+ public static final int STANDARD_AUTO = Constants.FrontendDvbsStandard.AUTO;
+ /**
+ * Standard S.
+ */
+ public static final int STANDARD_S = Constants.FrontendDvbsStandard.S;
+ /**
+ * Standard S2.
+ */
+ public static final int STANDARD_S2 = Constants.FrontendDvbsStandard.S2;
+ /**
+ * Standard S2X.
+ */
+ public static final int STANDARD_S2X = Constants.FrontendDvbsStandard.S2X;
+
+
+ private final int mModulation;
+ private final DvbsCodeRate mCoderate;
+ private final int mSymbolRate;
+ private final int mRolloff;
+ private final int mPilot;
+ private final int mInputStreamId;
+ private final int mStandard;
+
+ private DvbsFrontendSettings(int frequency, int modulation, DvbsCodeRate coderate,
+ int symbolRate, int rolloff, int pilot, int inputStreamId, int standard) {
super(frequency);
+ mModulation = modulation;
+ mCoderate = coderate;
+ mSymbolRate = symbolRate;
+ mRolloff = rolloff;
+ mPilot = pilot;
+ mInputStreamId = inputStreamId;
+ mStandard = standard;
+ }
+
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Code rate.
+ */
+ @Nullable
+ public DvbsCodeRate getCoderate() {
+ return mCoderate;
+ }
+ /**
+ * Gets Symbol Rate in symbols per second.
+ */
+ public int getSymbolRate() {
+ return mSymbolRate;
+ }
+ /**
+ * Gets Rolloff.
+ */
+ @Rolloff
+ public int getRolloff() {
+ return mRolloff;
+ }
+ /**
+ * Gets Pilot mode.
+ */
+ @Pilot
+ public int getPilot() {
+ return mPilot;
+ }
+ /**
+ * Gets Input Stream ID.
+ */
+ public int getInputStreamId() {
+ return mInputStreamId;
+ }
+ /**
+ * Gets DVBS sub-standard.
+ */
+ @Standard
+ public int getStandard() {
+ return mStandard;
+ }
+
+ /**
+ * Creates a builder for {@link DvbsFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link DvbsFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mModulation;
+ private DvbsCodeRate mCoderate;
+ private int mSymbolRate;
+ private int mRolloff;
+ private int mPilot;
+ private int mInputStreamId;
+ private int mStandard;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Code rate.
+ */
+ @NonNull
+ public Builder setCoderate(@Nullable DvbsCodeRate coderate) {
+ mCoderate = coderate;
+ return this;
+ }
+ /**
+ * Sets Symbol Rate.
+ */
+ @NonNull
+ public Builder setSymbolRate(int symbolRate) {
+ mSymbolRate = symbolRate;
+ return this;
+ }
+ /**
+ * Sets Rolloff.
+ */
+ @NonNull
+ public Builder setRolloff(@Rolloff int rolloff) {
+ mRolloff = rolloff;
+ return this;
+ }
+ /**
+ * Sets Pilot mode.
+ */
+ @NonNull
+ public Builder setPilot(@Pilot int pilot) {
+ mPilot = pilot;
+ return this;
+ }
+ /**
+ * Sets Input Stream ID.
+ */
+ @NonNull
+ public Builder setInputStreamId(int inputStreamId) {
+ mInputStreamId = inputStreamId;
+ return this;
+ }
+ /**
+ * Sets Standard.
+ */
+ @NonNull
+ public Builder setStandard(@Standard int standard) {
+ mStandard = standard;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DvbsFrontendSettings} object.
+ */
+ @NonNull
+ public DvbsFrontendSettings build() {
+ return new DvbsFrontendSettings(mFrequency, mModulation, mCoderate, mSymbolRate,
+ mRolloff, mPilot, mInputStreamId, mStandard);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
@Override
public int getType() {
- return TunerConstants.FRONTEND_TYPE_DVBS;
+ return FrontendSettings.TYPE_DVBS;
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java
index e9c16ddd4..0d47179 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java
@@ -30,9 +30,9 @@
private final boolean mIsT2Supported;
private final boolean mIsMisoSupported;
- DvbtFrontendCapabilities(int transmissionModeCap, int bandwidthCap, int constellationCap,
- int coderateCap, int hierarchyCap, int guardIntervalCap, boolean isT2Supported,
- boolean isMisoSupported) {
+ private DvbtFrontendCapabilities(int transmissionModeCap, int bandwidthCap,
+ int constellationCap, int coderateCap, int hierarchyCap, int guardIntervalCap,
+ boolean isT2Supported, boolean isMisoSupported) {
mTransmissionModeCap = transmissionModeCap;
mBandwidthCap = bandwidthCap;
mConstellationCap = constellationCap;
@@ -43,36 +43,58 @@
mIsMisoSupported = isMisoSupported;
}
- /** Gets transmission mode capability. */
+ /**
+ * Gets transmission mode capability.
+ */
+ @DvbtFrontendSettings.TransmissionMode
public int getTransmissionModeCapability() {
return mTransmissionModeCap;
}
- /** Gets bandwidth capability. */
+ /**
+ * Gets bandwidth capability.
+ */
+ @DvbtFrontendSettings.Bandwidth
public int getBandwidthCapability() {
return mBandwidthCap;
}
- /** Gets constellation capability. */
+ /**
+ * Gets constellation capability.
+ */
+ @DvbtFrontendSettings.Constellation
public int getConstellationCapability() {
return mConstellationCap;
}
- /** Gets code rate capability. */
+ /**
+ * Gets code rate capability.
+ */
+ @DvbtFrontendSettings.Coderate
public int getCodeRateCapability() {
return mCoderateCap;
}
- /** Gets hierarchy capability. */
+ /**
+ * Gets hierarchy capability.
+ */
+ @DvbtFrontendSettings.Hierarchy
public int getHierarchyCapability() {
return mHierarchyCap;
}
- /** Gets guard interval capability. */
+ /**
+ * Gets guard interval capability.
+ */
+ @DvbtFrontendSettings.GuardInterval
public int getGuardIntervalCapability() {
return mGuardIntervalCap;
}
- /** Returns whether T2 is supported. */
- public boolean getIsT2Supported() {
+ /**
+ * Returns whether T2 is supported.
+ */
+ public boolean isT2Supported() {
return mIsT2Supported;
}
- /** Returns whether MISO is supported. */
- public boolean getIsMisoSupported() {
+ /**
+ * Returns whether MISO is supported.
+ */
+ public boolean isMisoSupported() {
return mIsMisoSupported;
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
index eec00f3..f0469b7 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
@@ -16,35 +16,635 @@
package android.media.tv.tuner.frontend;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Frontend settings for DVBT.
* @hide
*/
public class DvbtFrontendSettings extends FrontendSettings {
- public int transmissionMode;
- public int bandwidth;
- public int constellation;
- public int hierarchy;
- public int hpCoderate;
- public int lpCoderate;
- public int guardInterval;
- public boolean isHighPriority;
- public byte standard;
- public boolean isMiso;
- public int plpMode;
- public byte plpId;
- public byte plpGroupId;
- DvbtFrontendSettings(int frequency) {
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "TRANSMISSION_MODE_",
+ value = {TRANSMISSION_MODE_UNDEFINED, TRANSMISSION_MODE_AUTO,
+ TRANSMISSION_MODE_2K, TRANSMISSION_MODE_8K, TRANSMISSION_MODE_4K,
+ TRANSMISSION_MODE_1K, TRANSMISSION_MODE_16K, TRANSMISSION_MODE_32K})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransmissionMode {}
+
+ /**
+ * Transmission Mode undefined.
+ */
+ public static final int TRANSMISSION_MODE_UNDEFINED =
+ Constants.FrontendDvbtTransmissionMode.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Transmission Mode automatically
+ */
+ public static final int TRANSMISSION_MODE_AUTO = Constants.FrontendDvbtTransmissionMode.AUTO;
+ /**
+ * 2K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_2K = Constants.FrontendDvbtTransmissionMode.MODE_2K;
+ /**
+ * 8K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_8K = Constants.FrontendDvbtTransmissionMode.MODE_8K;
+ /**
+ * 4K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_4K = Constants.FrontendDvbtTransmissionMode.MODE_4K;
+ /**
+ * 1K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_1K = Constants.FrontendDvbtTransmissionMode.MODE_1K;
+ /**
+ * 16K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_16K = Constants.FrontendDvbtTransmissionMode.MODE_16K;
+ /**
+ * 32K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_32K = Constants.FrontendDvbtTransmissionMode.MODE_32K;
+
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "BANDWIDTH_",
+ value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ,
+ BANDWIDTH_6MHZ, BANDWIDTH_5MHZ, BANDWIDTH_1_7MHZ, BANDWIDTH_10MHZ})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Bandwidth {}
+
+ /**
+ * Bandwidth undefined.
+ */
+ public static final int BANDWIDTH_UNDEFINED = Constants.FrontendDvbtBandwidth.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Bandwidth automatically.
+ */
+ public static final int BANDWIDTH_AUTO = Constants.FrontendDvbtBandwidth.AUTO;
+ /**
+ * 8 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_8MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_8MHZ;
+ /**
+ * 7 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_7MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_7MHZ;
+ /**
+ * 6 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_6MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_6MHZ;
+ /**
+ * 5 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_5MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_5MHZ;
+ /**
+ * 1.7 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_1_7MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_1_7MHZ;
+ /**
+ * 10 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_10MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_10MHZ;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "CONSTELLATION_",
+ value = {CONSTELLATION_UNDEFINED, CONSTELLATION_AUTO, CONSTELLATION_CONSTELLATION_QPSK,
+ CONSTELLATION_CONSTELLATION_16QAM, CONSTELLATION_CONSTELLATION_64QAM,
+ CONSTELLATION_CONSTELLATION_256QAM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Constellation {}
+
+ /**
+ * Constellation not defined.
+ */
+ public static final int CONSTELLATION_UNDEFINED = Constants.FrontendDvbtConstellation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Constellation automatically.
+ */
+ public static final int CONSTELLATION_AUTO = Constants.FrontendDvbtConstellation.AUTO;
+ /**
+ * QPSK Constellation.
+ */
+ public static final int CONSTELLATION_CONSTELLATION_QPSK =
+ Constants.FrontendDvbtConstellation.CONSTELLATION_QPSK;
+ /**
+ * 16QAM Constellation.
+ */
+ public static final int CONSTELLATION_CONSTELLATION_16QAM =
+ Constants.FrontendDvbtConstellation.CONSTELLATION_16QAM;
+ /**
+ * 64QAM Constellation.
+ */
+ public static final int CONSTELLATION_CONSTELLATION_64QAM =
+ Constants.FrontendDvbtConstellation.CONSTELLATION_64QAM;
+ /**
+ * 256QAM Constellation.
+ */
+ public static final int CONSTELLATION_CONSTELLATION_256QAM =
+ Constants.FrontendDvbtConstellation.CONSTELLATION_256QAM;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "HIERARCHY_",
+ value = {HIERARCHY_UNDEFINED, HIERARCHY_AUTO, HIERARCHY_NON_NATIVE, HIERARCHY_1_NATIVE,
+ HIERARCHY_2_NATIVE, HIERARCHY_4_NATIVE, HIERARCHY_NON_INDEPTH, HIERARCHY_1_INDEPTH,
+ HIERARCHY_2_INDEPTH, HIERARCHY_4_INDEPTH})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Hierarchy {}
+
+ /**
+ * Hierarchy undefined.
+ */
+ public static final int HIERARCHY_UNDEFINED = Constants.FrontendDvbtHierarchy.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Hierarchy automatically.
+ */
+ public static final int HIERARCHY_AUTO = Constants.FrontendDvbtHierarchy.AUTO;
+ /**
+ * Non-native Hierarchy
+ */
+ public static final int HIERARCHY_NON_NATIVE =
+ Constants.FrontendDvbtHierarchy.HIERARCHY_NON_NATIVE;
+ /**
+ * 1-native Hierarchy
+ */
+ public static final int HIERARCHY_1_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_1_NATIVE;
+ /**
+ * 2-native Hierarchy
+ */
+ public static final int HIERARCHY_2_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_2_NATIVE;
+ /**
+ * 4-native Hierarchy
+ */
+ public static final int HIERARCHY_4_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_4_NATIVE;
+ /**
+ * Non-indepth Hierarchy
+ */
+ public static final int HIERARCHY_NON_INDEPTH =
+ Constants.FrontendDvbtHierarchy.HIERARCHY_NON_INDEPTH;
+ /**
+ * 1-indepth Hierarchy
+ */
+ public static final int HIERARCHY_1_INDEPTH =
+ Constants.FrontendDvbtHierarchy.HIERARCHY_1_INDEPTH;
+ /**
+ * 2-indepth Hierarchy
+ */
+ public static final int HIERARCHY_2_INDEPTH =
+ Constants.FrontendDvbtHierarchy.HIERARCHY_2_INDEPTH;
+ /**
+ * 4-indepth Hierarchy
+ */
+ public static final int HIERARCHY_4_INDEPTH =
+ Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "CODERATE_",
+ value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_2, CODERATE_2_3, CODERATE_3_4,
+ CODERATE_5_6, CODERATE_7_8, CODERATE_3_5, CODERATE_4_5, CODERATE_6_7, CODERATE_8_9})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Coderate {}
+
+ /**
+ * Code rate undefined.
+ */
+ public static final int CODERATE_UNDEFINED =
+ Constants.FrontendDvbtCoderate.UNDEFINED;
+ /**
+ * Hardware is able to detect and set code rate automatically.
+ */
+ public static final int CODERATE_AUTO = Constants.FrontendDvbtCoderate.AUTO;
+ /**
+ * 1_2 code rate.
+ */
+ public static final int CODERATE_1_2 = Constants.FrontendDvbtCoderate.CODERATE_1_2;
+ /**
+ * 2_3 code rate.
+ */
+ public static final int CODERATE_2_3 = Constants.FrontendDvbtCoderate.CODERATE_2_3;
+ /**
+ * 3_4 code rate.
+ */
+ public static final int CODERATE_3_4 = Constants.FrontendDvbtCoderate.CODERATE_3_4;
+ /**
+ * 5_6 code rate.
+ */
+ public static final int CODERATE_5_6 = Constants.FrontendDvbtCoderate.CODERATE_5_6;
+ /**
+ * 7_8 code rate.
+ */
+ public static final int CODERATE_7_8 = Constants.FrontendDvbtCoderate.CODERATE_7_8;
+ /**
+ * 4_5 code rate.
+ */
+ public static final int CODERATE_3_5 = Constants.FrontendDvbtCoderate.CODERATE_3_5;
+ /**
+ * 4_5 code rate.
+ */
+ public static final int CODERATE_4_5 = Constants.FrontendDvbtCoderate.CODERATE_4_5;
+ /**
+ * 6_7 code rate.
+ */
+ public static final int CODERATE_6_7 = Constants.FrontendDvbtCoderate.CODERATE_6_7;
+ /**
+ * 8_9 code rate.
+ */
+ public static final int CODERATE_8_9 = Constants.FrontendDvbtCoderate.CODERATE_8_9;
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "GUARD_INTERVAL_",
+ value = {GUARD_INTERVAL_UNDEFINED, GUARD_INTERVAL_AUTO,
+ GUARD_INTERVAL_INTERVAL_1_32, GUARD_INTERVAL_INTERVAL_1_16,
+ GUARD_INTERVAL_INTERVAL_1_8, GUARD_INTERVAL_INTERVAL_1_4,
+ GUARD_INTERVAL_INTERVAL_1_128,
+ GUARD_INTERVAL_INTERVAL_19_128,
+ GUARD_INTERVAL_INTERVAL_19_256})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GuardInterval {}
+
+ /**
+ * Guard Interval undefined.
+ */
+ public static final int GUARD_INTERVAL_UNDEFINED =
+ Constants.FrontendDvbtGuardInterval.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Guard Interval automatically.
+ */
+ public static final int GUARD_INTERVAL_AUTO = Constants.FrontendDvbtGuardInterval.AUTO;
+ /**
+ * 1/32 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_1_32 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_1_32;
+ /**
+ * 1/16 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_1_16 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_1_16;
+ /**
+ * 1/8 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_1_8 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_1_8;
+ /**
+ * 1/4 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_1_4 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_1_4;
+ /**
+ * 1/128 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_1_128 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_1_128;
+ /**
+ * 19/128 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_19_128 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_19_128;
+ /**
+ * 19/256 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_19_256 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_19_256;
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "STANDARD",
+ value = {STANDARD_AUTO, STANDARD_T, STANDARD_T2}
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Standard {}
+
+ /**
+ * Hardware is able to detect and set Standard automatically.
+ */
+ public static final int STANDARD_AUTO = Constants.FrontendDvbtStandard.AUTO;
+ /**
+ * T standard.
+ */
+ public static final int STANDARD_T = Constants.FrontendDvbtStandard.T;
+ /**
+ * T2 standard.
+ */
+ public static final int STANDARD_T2 = Constants.FrontendDvbtStandard.T2;
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "PLP_MODE_",
+ value = {PLP_MODE_UNDEFINED, PLP_MODE_AUTO, PLP_MODE_MANUAL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PlpMode {}
+
+ /**
+ * Physical Layer Pipe (PLP) Mode undefined.
+ */
+ public static final int PLP_MODE_UNDEFINED = Constants.FrontendDvbtPlpMode.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Physical Layer Pipe (PLP) Mode automatically.
+ */
+ public static final int PLP_MODE_AUTO = Constants.FrontendDvbtPlpMode.AUTO;
+ /**
+ * Physical Layer Pipe (PLP) manual Mode.
+ */
+ public static final int PLP_MODE_MANUAL = Constants.FrontendDvbtPlpMode.MANUAL;
+
+
+ private final int mTransmissionMode;
+ private final int mBandwidth;
+ private final int mConstellation;
+ private final int mHierarchy;
+ private final int mHpCoderate;
+ private final int mLpCoderate;
+ private final int mGuardInterval;
+ private final boolean mIsHighPriority;
+ private final int mStandard;
+ private final boolean mIsMiso;
+ private final int mPlpMode;
+ private final int mPlpId;
+ private final int mPlpGroupId;
+
+ private DvbtFrontendSettings(int frequency, int transmissionMode, int bandwidth,
+ int constellation, int hierarchy, int hpCoderate, int lpCoderate, int guardInterval,
+ boolean isHighPriority, int standard, boolean isMiso, int plpMode, int plpId,
+ int plpGroupId) {
super(frequency);
+ mTransmissionMode = transmissionMode;
+ mBandwidth = bandwidth;
+ mConstellation = constellation;
+ mHierarchy = hierarchy;
+ mHpCoderate = hpCoderate;
+ mLpCoderate = lpCoderate;
+ mGuardInterval = guardInterval;
+ mIsHighPriority = isHighPriority;
+ mStandard = standard;
+ mIsMiso = isMiso;
+ mPlpMode = plpMode;
+ mPlpId = plpId;
+ mPlpGroupId = plpGroupId;
+ }
+
+ /**
+ * Gets Transmission Mode.
+ */
+ @TransmissionMode
+ public int getTransmissionMode() {
+ return mTransmissionMode;
+ }
+ /**
+ * Gets Bandwidth.
+ */
+ @Bandwidth
+ public int getBandwidth() {
+ return mBandwidth;
+ }
+ /**
+ * Gets Constellation.
+ */
+ @Constellation
+ public int getConstellation() {
+ return mConstellation;
+ }
+ /**
+ * Gets Hierarchy.
+ */
+ @Hierarchy
+ public int getHierarchy() {
+ return mHierarchy;
+ }
+ /**
+ * Gets Code Rate for High Priority level.
+ */
+ @Coderate
+ public int getHpCoderate() {
+ return mHpCoderate;
+ }
+ /**
+ * Gets Code Rate for Low Priority level.
+ */
+ @Coderate
+ public int getLpCoderate() {
+ return mLpCoderate;
+ }
+ /**
+ * Gets Guard Interval.
+ */
+ @GuardInterval
+ public int getGuardInterval() {
+ return mGuardInterval;
+ }
+ /**
+ * Checks whether it's high priority.
+ */
+ public boolean isHighPriority() {
+ return mIsHighPriority;
+ }
+ /**
+ * Gets Standard.
+ */
+ @Standard
+ public int getStandard() {
+ return mStandard;
+ }
+ /**
+ * Gets whether it's MISO.
+ */
+ public boolean isMiso() {
+ return mIsMiso;
+ }
+ /**
+ * Gets Physical Layer Pipe (PLP) Mode.
+ */
+ @PlpMode
+ public int getPlpMode() {
+ return mPlpMode;
+ }
+ /**
+ * Gets Physical Layer Pipe (PLP) ID.
+ */
+ public int getPlpId() {
+ return mPlpId;
+ }
+ /**
+ * Gets Physical Layer Pipe (PLP) group ID.
+ */
+ public int getPlpGroupId() {
+ return mPlpGroupId;
+ }
+
+ /**
+ * Creates a builder for {@link DvbtFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link DvbtFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mTransmissionMode;
+ private int mBandwidth;
+ private int mConstellation;
+ private int mHierarchy;
+ private int mHpCoderate;
+ private int mLpCoderate;
+ private int mGuardInterval;
+ private boolean mIsHighPriority;
+ private int mStandard;
+ private boolean mIsMiso;
+ private int mPlpMode;
+ private int mPlpId;
+ private int mPlpGroupId;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Transmission Mode.
+ */
+ @NonNull
+ public Builder setTransmissionMode(@TransmissionMode int transmissionMode) {
+ mTransmissionMode = transmissionMode;
+ return this;
+ }
+ /**
+ * Sets Bandwidth.
+ */
+ @NonNull
+ public Builder setBandwidth(@Bandwidth int bandwidth) {
+ mBandwidth = bandwidth;
+ return this;
+ }
+ /**
+ * Sets Constellation.
+ */
+ @NonNull
+ public Builder setConstellation(@Constellation int constellation) {
+ mConstellation = constellation;
+ return this;
+ }
+ /**
+ * Sets Hierarchy.
+ */
+ @NonNull
+ public Builder setHierarchy(@Hierarchy int hierarchy) {
+ mHierarchy = hierarchy;
+ return this;
+ }
+ /**
+ * Sets Code Rate for High Priority level.
+ */
+ @NonNull
+ public Builder setHpCoderate(@Coderate int hpCoderate) {
+ mHpCoderate = hpCoderate;
+ return this;
+ }
+ /**
+ * Sets Code Rate for Low Priority level.
+ */
+ @NonNull
+ public Builder setLpCoderate(@Coderate int lpCoderate) {
+ mLpCoderate = lpCoderate;
+ return this;
+ }
+ /**
+ * Sets Guard Interval.
+ */
+ @NonNull
+ public Builder setGuardInterval(@GuardInterval int guardInterval) {
+ mGuardInterval = guardInterval;
+ return this;
+ }
+ /**
+ * Sets whether it's high priority.
+ */
+ @NonNull
+ public Builder setHighPriority(boolean isHighPriority) {
+ mIsHighPriority = isHighPriority;
+ return this;
+ }
+ /**
+ * Sets Standard.
+ */
+ @NonNull
+ public Builder setStandard(@Standard int standard) {
+ mStandard = standard;
+ return this;
+ }
+ /**
+ * Sets whether it's MISO.
+ */
+ @NonNull
+ public Builder setMiso(boolean isMiso) {
+ mIsMiso = isMiso;
+ return this;
+ }
+ /**
+ * Sets Physical Layer Pipe (PLP) Mode.
+ */
+ @NonNull
+ public Builder setPlpMode(@PlpMode int plpMode) {
+ mPlpMode = plpMode;
+ return this;
+ }
+ /**
+ * Sets Physical Layer Pipe (PLP) ID.
+ */
+ @NonNull
+ public Builder setPlpId(int plpId) {
+ mPlpId = plpId;
+ return this;
+ }
+ /**
+ * Sets Physical Layer Pipe (PLP) group ID.
+ */
+ @NonNull
+ public Builder setPlpGroupId(int plpGroupId) {
+ mPlpGroupId = plpGroupId;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DvbtFrontendSettings} object.
+ */
+ @NonNull
+ public DvbtFrontendSettings build() {
+ return new DvbtFrontendSettings(mFrequency, mTransmissionMode, mBandwidth,
+ mConstellation, mHierarchy, mHpCoderate, mLpCoderate, mGuardInterval,
+ mIsHighPriority, mStandard, mIsMiso, mPlpMode, mPlpId, mPlpGroupId);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
@Override
public int getType() {
- return TunerConstants.FRONTEND_TYPE_DVBT;
+ return FrontendSettings.TYPE_DVBT;
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
index 0992eb6..9c4f460 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
@@ -27,10 +27,4 @@
* Invoked when there is a frontend event.
*/
void onEvent(int frontendEventType);
-
- /**
- * Invoked when there is a scan message.
- * @param msg
- */
- void onScanMessage(ScanMessage msg);
}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
index 5d03570..99e8dd2 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -16,7 +16,7 @@
package android.media.tv.tuner.frontend;
-import android.media.tv.tuner.TunerConstants.FrontendType;
+import android.media.tv.tuner.frontend.FrontendSettings.Type;
/**
* Frontend info.
@@ -54,7 +54,7 @@
return mId;
}
/** Gets frontend type. */
- @FrontendType
+ @Type
public int getType() {
return mType;
}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
new file mode 100644
index 0000000..617d608
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for tune and scan operations.
+ *
+ * @hide
+ */
+public abstract class FrontendSettings {
+ /** @hide */
+ @IntDef({TYPE_UNDEFINED, TYPE_ANALOG, TYPE_ATSC, TYPE_ATSC3, TYPE_DVBC, TYPE_DVBS, TYPE_DVBT,
+ TYPE_ISDBS, TYPE_ISDBS3, TYPE_ISDBT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /**
+ * Undefined frontend type.
+ */
+ public static final int TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED;
+ /**
+ * Analog frontend type.
+ */
+ public static final int TYPE_ANALOG = Constants.FrontendType.ANALOG;
+ /**
+ * Advanced Television Systems Committee (ATSC) frontend type.
+ */
+ public static final int TYPE_ATSC = Constants.FrontendType.ATSC;
+ /**
+ * Advanced Television Systems Committee 3.0 (ATSC-3) frontend type.
+ */
+ public static final int TYPE_ATSC3 = Constants.FrontendType.ATSC3;
+ /**
+ * Digital Video Broadcasting-Cable (DVB-C) frontend type.
+ */
+ public static final int TYPE_DVBC = Constants.FrontendType.DVBC;
+ /**
+ * Digital Video Broadcasting-Satellite (DVB-S) frontend type.
+ */
+ public static final int TYPE_DVBS = Constants.FrontendType.DVBS;
+ /**
+ * Digital Video Broadcasting-Terrestrial (DVB-T) frontend type.
+ */
+ public static final int TYPE_DVBT = Constants.FrontendType.DVBT;
+ /**
+ * Integrated Services Digital Broadcasting-Satellite (ISDB-S) frontend type.
+ */
+ public static final int TYPE_ISDBS = Constants.FrontendType.ISDBS;
+ /**
+ * Integrated Services Digital Broadcasting-Satellite 3 (ISDB-S3) frontend type.
+ */
+ public static final int TYPE_ISDBS3 = Constants.FrontendType.ISDBS3;
+ /**
+ * Integrated Services Digital Broadcasting-Terrestrial (ISDB-T) frontend type.
+ */
+ public static final int TYPE_ISDBT = Constants.FrontendType.ISDBT;
+
+ private final int mFrequency;
+
+ /** @hide */
+ public FrontendSettings(int frequency) {
+ mFrequency = frequency;
+ }
+
+ /**
+ * Returns the frontend type.
+ */
+ @Type
+ public abstract int getType();
+
+ /**
+ * Gets the frequency.
+ *
+ * @return the frequency in Hz.
+ */
+ public int getFrequency() {
+ return mFrequency;
+ }
+
+ /**
+ * Builder for {@link FrontendSettings}.
+ *
+ * @param <T> The subclass to be built.
+ */
+ public abstract static class Builder<T extends Builder<T>> {
+ /* package */ int mFrequency;
+
+ /* package */ Builder() {}
+
+ /**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ @IntRange(from = 1)
+ public T setFrequency(int frequency) {
+ mFrequency = frequency;
+ return self();
+ }
+
+ /* package */ abstract T self();
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 89ec536..f973d88 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -16,13 +16,11 @@
package android.media.tv.tuner.frontend;
+import android.media.tv.tuner.Lnb;
import android.media.tv.tuner.TunerConstants;
-import android.media.tv.tuner.TunerConstants.FrontendDvbcSpectralInversion;
-import android.media.tv.tuner.TunerConstants.FrontendDvbtHierarchy;
import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
import android.media.tv.tuner.TunerConstants.FrontendModulation;
import android.media.tv.tuner.TunerConstants.FrontendStatusType;
-import android.media.tv.tuner.TunerConstants.LnbVoltage;
/**
* Frontend status
@@ -120,7 +118,7 @@
return (int) mValue;
}
/** Spectral Inversion for DVBC. */
- @FrontendDvbcSpectralInversion
+ @DvbcFrontendSettings.SpectralInversion
public int getSpectralInversion() {
if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SPECTRAL) {
throw new IllegalStateException();
@@ -128,7 +126,7 @@
return (int) mValue;
}
/** Power Voltage Type for LNB. */
- @LnbVoltage
+ @Lnb.Voltage
public int getLnbVoltage() {
if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNB_VOLTAGE) {
throw new IllegalStateException();
@@ -206,7 +204,7 @@
return (int) mValue;
}
/** Hierarchy Type for DVBT. */
- @FrontendDvbtHierarchy
+ @DvbtFrontendSettings.Hierarchy
public int getHierarchy() {
if (mType != TunerConstants.FRONTEND_STATUS_TYPE_HIERARCHY) {
throw new IllegalStateException();
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
index 92832b7..61cba1c 100644
--- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
@@ -24,16 +24,22 @@
private final int mModulationCap;
private final int mCoderateCap;
- Isdbs3FrontendCapabilities(int modulationCap, int coderateCap) {
+ private Isdbs3FrontendCapabilities(int modulationCap, int coderateCap) {
mModulationCap = modulationCap;
mCoderateCap = coderateCap;
}
- /** Gets modulation capability. */
+ /**
+ * Gets modulation capability.
+ */
+ @Isdbs3FrontendSettings.Modulation
public int getModulationCapability() {
return mModulationCap;
}
- /** Gets code rate capability. */
+ /**
+ * Gets code rate capability.
+ */
+ @Isdbs3FrontendSettings.Coderate
public int getCodeRateCapability() {
return mCoderateCap;
}
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
index 736d0b1..7e6f188 100644
--- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
@@ -16,27 +16,288 @@
package android.media.tv.tuner.frontend;
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Frontend settings for ISDBS-3.
* @hide
*/
public class Isdbs3FrontendSettings extends FrontendSettings {
- public int streamId;
- public int streamIdType;
- public int modulation;
- public int coderate;
- public int symbolRate;
- public int rolloff;
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK,
+ MODULATION_MOD_QPSK, MODULATION_MOD_8PSK, MODULATION_MOD_16APSK,
+ MODULATION_MOD_32APSK})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
- Isdbs3FrontendSettings(int frequency) {
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbs3Modulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically.
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendIsdbs3Modulation.AUTO;
+ /**
+ * BPSK Modulation.
+ */
+ public static final int MODULATION_MOD_BPSK = Constants.FrontendIsdbs3Modulation.MOD_BPSK;
+ /**
+ * QPSK Modulation.
+ */
+ public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbs3Modulation.MOD_QPSK;
+ /**
+ * 8PSK Modulation.
+ */
+ public static final int MODULATION_MOD_8PSK = Constants.FrontendIsdbs3Modulation.MOD_8PSK;
+ /**
+ * 16APSK Modulation.
+ */
+ public static final int MODULATION_MOD_16APSK = Constants.FrontendIsdbs3Modulation.MOD_16APSK;
+ /**
+ * 32APSK Modulation.
+ */
+ public static final int MODULATION_MOD_32APSK = Constants.FrontendIsdbs3Modulation.MOD_32APSK;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ prefix = "CODERATE_",
+ value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_3, CODERATE_2_5, CODERATE_1_2,
+ CODERATE_3_5, CODERATE_2_3, CODERATE_3_4, CODERATE_7_9, CODERATE_4_5,
+ CODERATE_5_6, CODERATE_7_8, CODERATE_9_10})
+ public @interface Coderate {}
+
+ /**
+ * Code rate undefined.
+ */
+ public static final int CODERATE_UNDEFINED = Constants.FrontendIsdbs3Coderate.UNDEFINED;
+ /**
+ * Hardware is able to detect and set code rate automatically.
+ */
+ public static final int CODERATE_AUTO = Constants.FrontendIsdbs3Coderate.AUTO;
+ /**
+ * 1_3 code rate.
+ */
+ public static final int CODERATE_1_3 = Constants.FrontendIsdbs3Coderate.CODERATE_1_3;
+ /**
+ * 2_5 code rate.
+ */
+ public static final int CODERATE_2_5 = Constants.FrontendIsdbs3Coderate.CODERATE_2_5;
+ /**
+ * 1_2 code rate.
+ */
+ public static final int CODERATE_1_2 = Constants.FrontendIsdbs3Coderate.CODERATE_1_2;
+ /**
+ * 3_5 code rate.
+ */
+ public static final int CODERATE_3_5 = Constants.FrontendIsdbs3Coderate.CODERATE_3_5;
+ /**
+ * 2_3 code rate.
+ */
+ public static final int CODERATE_2_3 = Constants.FrontendIsdbs3Coderate.CODERATE_2_3;
+ /**
+ * 3_4 code rate.
+ */
+ public static final int CODERATE_3_4 = Constants.FrontendIsdbs3Coderate.CODERATE_3_4;
+ /**
+ * 7_9 code rate.
+ */
+ public static final int CODERATE_7_9 = Constants.FrontendIsdbs3Coderate.CODERATE_7_9;
+ /**
+ * 4_5 code rate.
+ */
+ public static final int CODERATE_4_5 = Constants.FrontendIsdbs3Coderate.CODERATE_4_5;
+ /**
+ * 5_6 code rate.
+ */
+ public static final int CODERATE_5_6 = Constants.FrontendIsdbs3Coderate.CODERATE_5_6;
+ /**
+ * 7_8 code rate.
+ */
+ public static final int CODERATE_7_8 = Constants.FrontendIsdbs3Coderate.CODERATE_7_8;
+ /**
+ * 9_10 code rate.
+ */
+ public static final int CODERATE_9_10 = Constants.FrontendIsdbs3Coderate.CODERATE_9_10;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "ROLLOFF_",
+ value = {ROLLOFF_UNDEFINED, ROLLOFF_0_03})
+ public @interface Rolloff {}
+
+ /**
+ * Roll off type undefined.
+ */
+ public static final int ROLLOFF_UNDEFINED = Constants.FrontendIsdbs3Rolloff.UNDEFINED;
+ /**
+ * 0.03 roll off type.
+ */
+ public static final int ROLLOFF_0_03 = Constants.FrontendIsdbs3Rolloff.ROLLOFF_0_03;
+
+
+ private final int mStreamId;
+ private final int mStreamIdType;
+ private final int mModulation;
+ private final int mCoderate;
+ private final int mSymbolRate;
+ private final int mRolloff;
+
+ private Isdbs3FrontendSettings(int frequency, int streamId, int streamIdType, int modulation,
+ int coderate, int symbolRate, int rolloff) {
super(frequency);
+ mStreamId = streamId;
+ mStreamIdType = streamIdType;
+ mModulation = modulation;
+ mCoderate = coderate;
+ mSymbolRate = symbolRate;
+ mRolloff = rolloff;
+ }
+
+ /**
+ * Gets Stream ID.
+ */
+ public int getStreamId() {
+ return mStreamId;
+ }
+ /**
+ * Gets Stream ID Type.
+ */
+ @IsdbsFrontendSettings.StreamIdType
+ public int getStreamIdType() {
+ return mStreamIdType;
+ }
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Code rate.
+ */
+ @Coderate
+ public int getCoderate() {
+ return mCoderate;
+ }
+ /**
+ * Gets Symbol Rate in symbols per second.
+ */
+ public int getSymbolRate() {
+ return mSymbolRate;
+ }
+ /**
+ * Gets Roll off type.
+ */
+ @Rolloff
+ public int getRolloff() {
+ return mRolloff;
+ }
+
+ /**
+ * Creates a builder for {@link Isdbs3FrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link Isdbs3FrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mStreamId;
+ private int mStreamIdType;
+ private int mModulation;
+ private int mCoderate;
+ private int mSymbolRate;
+ private int mRolloff;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Stream ID.
+ */
+ @NonNull
+ public Builder setStreamId(int streamId) {
+ mStreamId = streamId;
+ return this;
+ }
+ /**
+ * Sets StreamIdType.
+ */
+ @NonNull
+ public Builder setStreamIdType(@IsdbsFrontendSettings.StreamIdType int streamIdType) {
+ mStreamIdType = streamIdType;
+ return this;
+ }
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Code rate.
+ */
+ @NonNull
+ public Builder setCoderate(@Coderate int coderate) {
+ mCoderate = coderate;
+ return this;
+ }
+ /**
+ * Sets Symbol Rate in symbols per second.
+ */
+ @NonNull
+ public Builder setSymbolRate(int symbolRate) {
+ mSymbolRate = symbolRate;
+ return this;
+ }
+ /**
+ * Sets Roll off type.
+ */
+ @NonNull
+ public Builder setRolloff(@Rolloff int rolloff) {
+ mRolloff = rolloff;
+ return this;
+ }
+
+ /**
+ * Builds a {@link Isdbs3FrontendSettings} object.
+ */
+ @NonNull
+ public Isdbs3FrontendSettings build() {
+ return new Isdbs3FrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation,
+ mCoderate, mSymbolRate, mRolloff);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
@Override
public int getType() {
- return TunerConstants.FRONTEND_TYPE_ISDBS3;
+ return FrontendSettings.TYPE_ISDBS3;
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java
index b930b25..8e5ecc4 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java
@@ -24,16 +24,22 @@
private final int mModulationCap;
private final int mCoderateCap;
- IsdbsFrontendCapabilities(int modulationCap, int coderateCap) {
+ private IsdbsFrontendCapabilities(int modulationCap, int coderateCap) {
mModulationCap = modulationCap;
mCoderateCap = coderateCap;
}
- /** Gets modulation capability. */
+ /**
+ * Gets modulation capability.
+ */
+ @IsdbsFrontendSettings.Modulation
public int getModulationCapability() {
return mModulationCap;
}
- /** Gets code rate capability. */
+ /**
+ * Gets code rate capability.
+ */
+ @IsdbsFrontendSettings.Coderate
public int getCodeRateCapability() {
return mCoderateCap;
}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
index 7fd5da7..fe100f8 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
@@ -16,27 +16,273 @@
package android.media.tv.tuner.frontend;
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Frontend settings for ISDBS.
* @hide
*/
public class IsdbsFrontendSettings extends FrontendSettings {
- public int streamId;
- public int streamIdType;
- public int modulation;
- public int coderate;
- public int symbolRate;
- public int rolloff;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "STREAM_ID_TYPE_",
+ value = {STREAM_ID_TYPE_ID, STREAM_ID_TYPE_RELATIVE_NUMBER})
+ public @interface StreamIdType {}
- IsdbsFrontendSettings(int frequency) {
+ /**
+ * Uses stream ID.
+ */
+ public static final int STREAM_ID_TYPE_ID = Constants.FrontendIsdbsStreamIdType.STREAM_ID;
+ /**
+ * Uses relative number.
+ */
+ public static final int STREAM_ID_TYPE_RELATIVE_NUMBER =
+ Constants.FrontendIsdbsStreamIdType.RELATIVE_STREAM_NUMBER;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK,
+ MODULATION_MOD_QPSK, MODULATION_MOD_TC8PSK})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
+
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbsModulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendIsdbsModulation.AUTO;
+ /**
+ * BPSK Modulation.
+ */
+ public static final int MODULATION_MOD_BPSK = Constants.FrontendIsdbsModulation.MOD_BPSK;
+ /**
+ * QPSK Modulation.
+ */
+ public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbsModulation.MOD_QPSK;
+ /**
+ * TC8PSK Modulation.
+ */
+ public static final int MODULATION_MOD_TC8PSK = Constants.FrontendIsdbsModulation.MOD_TC8PSK;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "CODERATE_",
+ value = {CODERATE_UNDEFINED, CODERATE_AUTO,
+ CODERATE_1_2, CODERATE_2_3, CODERATE_3_4,
+ CODERATE_5_6, CODERATE_7_8})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Coderate {}
+
+ /**
+ * Code rate undefined.
+ */
+ public static final int CODERATE_UNDEFINED = Constants.FrontendIsdbsCoderate.UNDEFINED;
+ /**
+ * Hardware is able to detect and set code rate automatically.
+ */
+ public static final int CODERATE_AUTO = Constants.FrontendIsdbsCoderate.AUTO;
+ /**
+ * 1_2 code rate.
+ */
+ public static final int CODERATE_1_2 = Constants.FrontendIsdbsCoderate.CODERATE_1_2;
+ /**
+ * 2_3 code rate.
+ */
+ public static final int CODERATE_2_3 = Constants.FrontendIsdbsCoderate.CODERATE_2_3;
+ /**
+ * 3_4 code rate.
+ */
+ public static final int CODERATE_3_4 = Constants.FrontendIsdbsCoderate.CODERATE_3_4;
+ /**
+ * 5_6 code rate.
+ */
+ public static final int CODERATE_5_6 = Constants.FrontendIsdbsCoderate.CODERATE_5_6;
+ /**
+ * 7_8 code rate.
+ */
+ public static final int CODERATE_7_8 = Constants.FrontendIsdbsCoderate.CODERATE_7_8;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "ROLLOFF_",
+ value = {ROLLOFF_UNDEFINED, ROLLOFF_0_35})
+ public @interface Rolloff {}
+
+ /**
+ * Roll off type undefined.
+ */
+ public static final int ROLLOFF_UNDEFINED = Constants.FrontendIsdbs3Rolloff.UNDEFINED;
+ /**
+ * 0.35 roll off type.
+ */
+ public static final int ROLLOFF_0_35 = Constants.FrontendIsdbsRolloff.ROLLOFF_0_35;
+
+
+ private final int mStreamId;
+ private final int mStreamIdType;
+ private final int mModulation;
+ private final int mCoderate;
+ private final int mSymbolRate;
+ private final int mRolloff;
+
+ private IsdbsFrontendSettings(int frequency, int streamId, int streamIdType, int modulation,
+ int coderate, int symbolRate, int rolloff) {
super(frequency);
+ mStreamId = streamId;
+ mStreamIdType = streamIdType;
+ mModulation = modulation;
+ mCoderate = coderate;
+ mSymbolRate = symbolRate;
+ mRolloff = rolloff;
+ }
+
+ /**
+ * Gets Stream ID.
+ */
+ public int getStreamId() {
+ return mStreamId;
+ }
+ /**
+ * Gets Stream ID Type.
+ */
+ @StreamIdType
+ public int getStreamIdType() {
+ return mStreamIdType;
+ }
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Code rate.
+ */
+ @Coderate
+ public int getCoderate() {
+ return mCoderate;
+ }
+ /**
+ * Gets Symbol Rate in symbols per second.
+ */
+ public int getSymbolRate() {
+ return mSymbolRate;
+ }
+ /**
+ * Gets Roll off type.
+ */
+ @Rolloff
+ public int getRolloff() {
+ return mRolloff;
+ }
+
+ /**
+ * Creates a builder for {@link IsdbsFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IsdbsFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mStreamId;
+ private int mStreamIdType;
+ private int mModulation;
+ private int mCoderate;
+ private int mSymbolRate;
+ private int mRolloff;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Stream ID.
+ */
+ @NonNull
+ public Builder setStreamId(int streamId) {
+ mStreamId = streamId;
+ return this;
+ }
+ /**
+ * Sets StreamIdType.
+ */
+ @NonNull
+ public Builder setStreamIdType(@StreamIdType int streamIdType) {
+ mStreamIdType = streamIdType;
+ return this;
+ }
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Code rate.
+ */
+ @NonNull
+ public Builder setCoderate(@Coderate int coderate) {
+ mCoderate = coderate;
+ return this;
+ }
+ /**
+ * Sets Symbol Rate in symbols per second.
+ */
+ @NonNull
+ public Builder setSymbolRate(int symbolRate) {
+ mSymbolRate = symbolRate;
+ return this;
+ }
+ /**
+ * Sets Roll off type.
+ */
+ @NonNull
+ public Builder setRolloff(@Rolloff int rolloff) {
+ mRolloff = rolloff;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IsdbsFrontendSettings} object.
+ */
+ @NonNull
+ public IsdbsFrontendSettings build() {
+ return new IsdbsFrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation,
+ mCoderate, mSymbolRate, mRolloff);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
@Override
public int getType() {
- return TunerConstants.FRONTEND_TYPE_ISDBS;
+ return FrontendSettings.TYPE_ISDBS;
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendCapabilities.java
similarity index 68%
rename from media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java
rename to media/java/android/media/tv/tuner/frontend/IsdbtFrontendCapabilities.java
index 6544b17..19f04de 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendCapabilities.java
@@ -17,18 +17,18 @@
package android.media.tv.tuner.frontend;
/**
- * ISDBC Capabilities.
+ * ISDBT Capabilities.
* @hide
*/
-public class IsdbcFrontendCapabilities extends FrontendCapabilities {
+public class IsdbtFrontendCapabilities extends FrontendCapabilities {
private final int mModeCap;
private final int mBandwidthCap;
private final int mModulationCap;
private final int mCoderateCap;
private final int mGuardIntervalCap;
- IsdbcFrontendCapabilities(int modeCap, int bandwidthCap, int modulationCap, int coderateCap,
- int guardIntervalCap) {
+ private IsdbtFrontendCapabilities(int modeCap, int bandwidthCap, int modulationCap,
+ int coderateCap, int guardIntervalCap) {
mModeCap = modeCap;
mBandwidthCap = bandwidthCap;
mModulationCap = modulationCap;
@@ -36,23 +36,38 @@
mGuardIntervalCap = guardIntervalCap;
}
- /** Gets mode capability. */
+ /**
+ * Gets mode capability.
+ */
+ @IsdbtFrontendSettings.Mode
public int getModeCapability() {
return mModeCap;
}
- /** Gets bandwidth capability. */
+ /**
+ * Gets bandwidth capability.
+ */
+ @IsdbtFrontendSettings.Bandwidth
public int getBandwidthCapability() {
return mBandwidthCap;
}
- /** Gets modulation capability. */
+ /**
+ * Gets modulation capability.
+ */
+ @IsdbtFrontendSettings.Modulation
public int getModulationCapability() {
return mModulationCap;
}
- /** Gets code rate capability. */
+ /**
+ * Gets code rate capability.
+ */
+ @DvbtFrontendSettings.Coderate
public int getCodeRateCapability() {
return mCoderateCap;
}
- /** Gets guard interval capability. */
+ /**
+ * Gets guard interval capability.
+ */
+ @DvbtFrontendSettings.GuardInterval
public int getGuardIntervalCapability() {
return mGuardIntervalCap;
}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
index 3f83267..1510193 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -16,27 +16,247 @@
package android.media.tv.tuner.frontend;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Frontend settings for ISDBT.
* @hide
*/
public class IsdbtFrontendSettings extends FrontendSettings {
- public int modulation;
- public int bandwidth;
- public int coderate;
- public int guardInterval;
- public int serviceAreaId;
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_DQPSK,
+ MODULATION_MOD_QPSK, MODULATION_MOD_16QAM, MODULATION_MOD_64QAM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
- IsdbtFrontendSettings(int frequency) {
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbtModulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendIsdbtModulation.AUTO;
+ /**
+ * DQPSK Modulation.
+ */
+ public static final int MODULATION_MOD_DQPSK = Constants.FrontendIsdbtModulation.MOD_DQPSK;
+ /**
+ * QPSK Modulation.
+ */
+ public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbtModulation.MOD_QPSK;
+ /**
+ * 16QAM Modulation.
+ */
+ public static final int MODULATION_MOD_16QAM = Constants.FrontendIsdbtModulation.MOD_16QAM;
+ /**
+ * 64QAM Modulation.
+ */
+ public static final int MODULATION_MOD_64QAM = Constants.FrontendIsdbtModulation.MOD_64QAM;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODE_",
+ value = {MODE_UNDEFINED, MODE_AUTO, MODE_1, MODE_2, MODE_3})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Mode {}
+
+ /**
+ * Mode undefined.
+ */
+ public static final int MODE_UNDEFINED = Constants.FrontendIsdbtMode.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Mode automatically.
+ */
+ public static final int MODE_AUTO = Constants.FrontendIsdbtMode.AUTO;
+ /**
+ * Mode 1
+ */
+ public static final int MODE_1 = Constants.FrontendIsdbtMode.MODE_1;
+ /**
+ * Mode 2
+ */
+ public static final int MODE_2 = Constants.FrontendIsdbtMode.MODE_2;
+ /**
+ * Mode 3
+ */
+ public static final int MODE_3 = Constants.FrontendIsdbtMode.MODE_3;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "BANDWIDTH_",
+ value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ,
+ BANDWIDTH_6MHZ})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Bandwidth {}
+
+ /**
+ * Bandwidth undefined.
+ */
+ public static final int BANDWIDTH_UNDEFINED = Constants.FrontendIsdbtBandwidth.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Bandwidth automatically.
+ */
+ public static final int BANDWIDTH_AUTO = Constants.FrontendIsdbtBandwidth.AUTO;
+ /**
+ * 8 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_8MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_8MHZ;
+ /**
+ * 7 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_7MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_7MHZ;
+ /**
+ * 6 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_6MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_6MHZ;
+
+ private final int mModulation;
+ private final int mBandwidth;
+ private final int mCoderate;
+ private final int mGuardInterval;
+ private final int mServiceAreaId;
+
+ private IsdbtFrontendSettings(int frequency, int modulation, int bandwidth, int coderate,
+ int guardInterval, int serviceAreaId) {
super(frequency);
+ mModulation = modulation;
+ mBandwidth = bandwidth;
+ mCoderate = coderate;
+ mGuardInterval = guardInterval;
+ mServiceAreaId = serviceAreaId;
+ }
+
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Bandwidth.
+ */
+ @Bandwidth
+ public int getBandwidth() {
+ return mBandwidth;
+ }
+ /**
+ * Gets Code rate.
+ */
+ @DvbtFrontendSettings.Coderate
+ public int getCoderate() {
+ return mCoderate;
+ }
+ /**
+ * Gets Guard Interval.
+ */
+ @DvbtFrontendSettings.GuardInterval
+ public int getGuardInterval() {
+ return mGuardInterval;
+ }
+ /**
+ * Gets Service Area ID.
+ */
+ public int getServiceAreaId() {
+ return mServiceAreaId;
+ }
+
+ /**
+ * Creates a builder for {@link IsdbtFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IsdbtFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mModulation;
+ private int mBandwidth;
+ private int mCoderate;
+ private int mGuardInterval;
+ private int mServiceAreaId;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Bandwidth.
+ */
+ @NonNull
+ public Builder setBandwidth(@Bandwidth int bandwidth) {
+ mBandwidth = bandwidth;
+ return this;
+ }
+ /**
+ * Sets Code rate.
+ */
+ @NonNull
+ public Builder setCoderate(@DvbtFrontendSettings.Coderate int coderate) {
+ mCoderate = coderate;
+ return this;
+ }
+ /**
+ * Sets Guard Interval.
+ */
+ @NonNull
+ public Builder setGuardInterval(@DvbtFrontendSettings.GuardInterval int guardInterval) {
+ mGuardInterval = guardInterval;
+ return this;
+ }
+ /**
+ * Sets Service Area ID.
+ */
+ @NonNull
+ public Builder setServiceAreaId(int serviceAreaId) {
+ mServiceAreaId = serviceAreaId;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IsdbtFrontendSettings} object.
+ */
+ @NonNull
+ public IsdbtFrontendSettings build() {
+ return new IsdbtFrontendSettings(
+ mFrequency, mModulation, mBandwidth, mCoderate, mGuardInterval, mServiceAreaId);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
@Override
public int getType() {
- return TunerConstants.FRONTEND_TYPE_ISDBT;
+ return FrontendSettings.TYPE_ISDBT;
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
new file mode 100644
index 0000000..5e7d218
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+/**
+ * Scan callback.
+ *
+ * @hide
+ */
+public interface ScanCallback {
+ /** Scan locked the signal. */
+ void onLocked(boolean isLocked);
+
+ /** Scan stopped. */
+ void onEnd(boolean isEnd);
+
+ /** scan progress percent (0..100) */
+ void onProgress(int percent);
+
+ /** Signal frequency in Hertz */
+ void onFrequencyReport(int frequency);
+
+ /** Symbols per second */
+ void onSymbolRate(int rate);
+
+ /** Locked Plp Ids for DVBT2 frontend. */
+ void onPlpIds(int[] plpIds);
+
+ /** Locked group Ids for DVBT2 frontend. */
+ void onGroupIds(int[] groupIds);
+
+ /** Stream Ids. */
+ void onInputStreamIds(int[] inputStreamIds);
+
+ /** Locked signal standard. */
+ void onDvbsStandard(@DvbsFrontendSettings.Standard int dvbsStandandard);
+
+ /** Locked signal standard. */
+ void onDvbtStandard(@DvbtFrontendSettings.Standard int dvbtStandard);
+
+ /** PLP status in a tuned frequency band for ATSC3 frontend. */
+ void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos);
+
+ /** PLP information for ATSC3. */
+ class Atsc3PlpInfo {
+ private final int mPlpId;
+ private final boolean mLlsFlag;
+
+ private Atsc3PlpInfo(int plpId, boolean llsFlag) {
+ mPlpId = plpId;
+ mLlsFlag = llsFlag;
+ }
+
+ /** Gets PLP IDs. */
+ public int getPlpId() {
+ return mPlpId;
+ }
+
+ /** Gets LLS flag. */
+ public boolean getLlsFlag() {
+ return mLlsFlag;
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/ScanMessage.java b/media/java/android/media/tv/tuner/frontend/ScanMessage.java
deleted file mode 100644
index dd687dd..0000000
--- a/media/java/android/media/tv/tuner/frontend/ScanMessage.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.tuner.frontend;
-
-import android.annotation.IntDef;
-import android.hardware.tv.tuner.V1_0.Constants;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Message from frontend during scan operations.
- *
- * @hide
- */
-public class ScanMessage {
-
- /** @hide */
- @IntDef({
- LOCKED,
- END,
- PROGRESS_PERCENT,
- FREQUENCY,
- SYMBOL_RATE,
- PLP_IDS,
- GROUP_IDS,
- INPUT_STREAM_IDS,
- STANDARD,
- ATSC3_PLP_INFO
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface Type {}
- /** @hide */
- public static final int LOCKED = Constants.FrontendScanMessageType.LOCKED;
- /** @hide */
- public static final int END = Constants.FrontendScanMessageType.END;
- /** @hide */
- public static final int PROGRESS_PERCENT = Constants.FrontendScanMessageType.PROGRESS_PERCENT;
- /** @hide */
- public static final int FREQUENCY = Constants.FrontendScanMessageType.FREQUENCY;
- /** @hide */
- public static final int SYMBOL_RATE = Constants.FrontendScanMessageType.SYMBOL_RATE;
- /** @hide */
- public static final int PLP_IDS = Constants.FrontendScanMessageType.PLP_IDS;
- /** @hide */
- public static final int GROUP_IDS = Constants.FrontendScanMessageType.GROUP_IDS;
- /** @hide */
- public static final int INPUT_STREAM_IDS = Constants.FrontendScanMessageType.INPUT_STREAM_IDS;
- /** @hide */
- public static final int STANDARD = Constants.FrontendScanMessageType.STANDARD;
- /** @hide */
- public static final int ATSC3_PLP_INFO = Constants.FrontendScanMessageType.ATSC3_PLP_INFO;
-
- private final int mType;
- private final Object mValue;
-
- private ScanMessage(int type, Object value) {
- mType = type;
- mValue = value;
- }
-
- /** Gets scan message type. */
- @Type
- public int getMessageType() {
- return mType;
- }
- /** Message indicates whether frontend is locked or not. */
- public boolean getIsLocked() {
- if (mType != LOCKED) {
- throw new IllegalStateException();
- }
- return (Boolean) mValue;
- }
- /** Message indicates whether the scan has reached the end or not. */
- public boolean getIsEnd() {
- if (mType != END) {
- throw new IllegalStateException();
- }
- return (Boolean) mValue;
- }
- /** Progress message in percent. */
- public int getProgressPercent() {
- if (mType != PROGRESS_PERCENT) {
- throw new IllegalStateException();
- }
- return (Integer) mValue;
- }
- /** Gets frequency. */
- public int getFrequency() {
- if (mType != FREQUENCY) {
- throw new IllegalStateException();
- }
- return (Integer) mValue;
- }
- /** Gets symbol rate. */
- public int getSymbolRate() {
- if (mType != SYMBOL_RATE) {
- throw new IllegalStateException();
- }
- return (Integer) mValue;
- }
- /** Gets PLP IDs. */
- public int[] getPlpIds() {
- if (mType != PLP_IDS) {
- throw new IllegalStateException();
- }
- return (int[]) mValue;
- }
- /** Gets group IDs. */
- public int[] getGroupIds() {
- if (mType != GROUP_IDS) {
- throw new IllegalStateException();
- }
- return (int[]) mValue;
- }
- /** Gets Input stream IDs. */
- public int[] getInputStreamIds() {
- if (mType != INPUT_STREAM_IDS) {
- throw new IllegalStateException();
- }
- return (int[]) mValue;
- }
- /** Gets the DVB-T or DVB-S standard. */
- public int getStandard() {
- if (mType != STANDARD) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
-
- /** Gets PLP information for ATSC3. */
- public Atsc3PlpInfo[] getAtsc3PlpInfos() {
- if (mType != 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/jni/Android.bp b/media/jni/Android.bp
index ee67613..aeacd8f 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -19,6 +19,7 @@
"android_media_MediaProfiles.cpp",
"android_media_MediaRecorder.cpp",
"android_media_MediaSync.cpp",
+ "android_media_MediaTranscodeManager.cpp",
"android_media_ResampleInputStream.cpp",
"android_media_Streams.cpp",
"android_media_SyncParams.cpp",
@@ -143,6 +144,10 @@
"libutils",
],
+ header_libs: [
+ "libstagefright_foundation_headers",
+ ],
+
export_include_dirs: ["."],
cflags: [
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 963b650..5cb42a9a 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1453,6 +1453,7 @@
extern int register_android_mtp_MtpDatabase(JNIEnv *env);
extern int register_android_mtp_MtpDevice(JNIEnv *env);
extern int register_android_mtp_MtpServer(JNIEnv *env);
+extern int register_android_media_MediaTranscodeManager(JNIEnv *env);
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
@@ -1565,6 +1566,11 @@
goto bail;
}
+ if (register_android_media_MediaTranscodeManager(env) < 0) {
+ ALOGE("ERROR: MediaTranscodeManager native registration failed");
+ goto bail;
+ }
+
/* success -- return valid version number */
result = JNI_VERSION_1_4;
diff --git a/media/jni/android_media_MediaTranscodeManager.cpp b/media/jni/android_media_MediaTranscodeManager.cpp
new file mode 100644
index 0000000..0b4048c
--- /dev/null
+++ b/media/jni/android_media_MediaTranscodeManager.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscodeManager_JNI"
+
+#include "android_runtime/AndroidRuntime.h"
+#include "jni.h"
+
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+
+namespace {
+
+// NOTE: Keep these enums in sync with their equivalents in MediaTranscodeManager.java.
+enum {
+ ID_INVALID = -1
+};
+
+enum {
+ EVENT_JOB_STARTED = 1,
+ EVENT_JOB_PROGRESSED = 2,
+ EVENT_JOB_FINISHED = 3,
+};
+
+enum {
+ RESULT_NONE = 1,
+ RESULT_SUCCESS = 2,
+ RESULT_ERROR = 3,
+ RESULT_CANCELED = 4,
+};
+
+struct {
+ jmethodID postEventFromNative;
+} gMediaTranscodeManagerClassInfo;
+
+using namespace android;
+
+void android_media_MediaTranscodeManager_native_init(JNIEnv *env, jclass clazz) {
+ ALOGV("android_media_MediaTranscodeManager_native_init");
+
+ gMediaTranscodeManagerClassInfo.postEventFromNative = env->GetMethodID(
+ clazz, "postEventFromNative", "(IJI)V");
+ LOG_ALWAYS_FATAL_IF(gMediaTranscodeManagerClassInfo.postEventFromNative == NULL,
+ "can't find android/media/MediaTranscodeManager.postEventFromNative");
+}
+
+jlong android_media_MediaTranscodeManager_requestUniqueJobID(
+ JNIEnv *env __unused, jobject thiz __unused) {
+ ALOGV("android_media_MediaTranscodeManager_reserveUniqueJobID");
+ static std::atomic_int32_t sJobIDCounter{0};
+ jlong id = (jlong)++sJobIDCounter;
+ return id;
+}
+
+jboolean android_media_MediaTranscodeManager_enqueueTranscodingRequest(
+ JNIEnv *env, jobject thiz, jlong id, jobject request, jobject context __unused) {
+ ALOGV("android_media_MediaTranscodeManager_enqueueTranscodingRequest");
+ if (!request) {
+ return ID_INVALID;
+ }
+
+ env->CallVoidMethod(thiz, gMediaTranscodeManagerClassInfo.postEventFromNative,
+ EVENT_JOB_FINISHED, id, RESULT_ERROR);
+ return true;
+}
+
+void android_media_MediaTranscodeManager_cancelTranscodingRequest(
+ JNIEnv *env __unused, jobject thiz __unused, jlong jobID __unused) {
+ ALOGV("android_media_MediaTranscodeManager_cancelTranscodingRequest");
+}
+
+const JNINativeMethod gMethods[] = {
+ { "native_init", "()V",
+ (void *)android_media_MediaTranscodeManager_native_init },
+ { "native_requestUniqueJobID", "()J",
+ (void *)android_media_MediaTranscodeManager_requestUniqueJobID },
+ { "native_enqueueTranscodingRequest",
+ "(JLandroid/media/MediaTranscodeManager$TranscodingRequest;Landroid/content/Context;)Z",
+ (void *)android_media_MediaTranscodeManager_enqueueTranscodingRequest },
+ { "native_cancelTranscodingRequest", "(J)V",
+ (void *)android_media_MediaTranscodeManager_cancelTranscodingRequest },
+};
+
+} // namespace anonymous
+
+int register_android_media_MediaTranscodeManager(JNIEnv *env) {
+ return AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaTranscodeManager", gMethods, NELEM(gMethods));
+}
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
index f0fbc50..ecbe2b3 100644
--- a/media/tests/MediaFrameworkTest/Android.bp
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -7,6 +7,7 @@
],
static_libs: [
"mockito-target-minus-junit4",
+ "androidx.test.ext.junit",
"androidx.test.rules",
"android-ex-camera2",
],
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java
new file mode 100644
index 0000000..eeda50e
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediaframeworktest.functional.mediatranscodemanager;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.media.MediaTranscodeManager;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaTranscodeManagerTest {
+ private static final String TAG = "MediaTranscodeManagerTest";
+
+ /** The time to wait for the transcode operation to complete before failing the test. */
+ private static final int TRANSCODE_TIMEOUT_SECONDS = 2;
+
+ @Test
+ public void testMediaTranscodeManager() throws InterruptedException {
+ Log.d(TAG, "Starting: testMediaTranscodeManager");
+
+ Semaphore transcodeCompleteSemaphore = new Semaphore(0);
+ MediaTranscodeManager.TranscodingRequest request =
+ new MediaTranscodeManager.TranscodingRequest.Builder().build();
+ Executor listenerExecutor = Executors.newSingleThreadExecutor();
+
+ MediaTranscodeManager mediaTranscodeManager =
+ MediaTranscodeManager.getInstance(ApplicationProvider.getApplicationContext());
+ assertNotNull(mediaTranscodeManager);
+
+ MediaTranscodeManager.TranscodingJob job;
+ job = mediaTranscodeManager.enqueueTranscodingRequest(request, listenerExecutor,
+ transcodingJob -> {
+ Log.d(TAG, "Transcoding completed with result: " + transcodingJob.getResult());
+ transcodeCompleteSemaphore.release();
+ });
+ assertNotNull(job);
+
+ job.setOnProgressChangedListener(
+ listenerExecutor, progress -> Log.d(TAG, "Progress: " + progress));
+
+ if (job != null) {
+ Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to complete.");
+ boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+ TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ assertTrue("Transcode failed to complete in time.", finishedOnTime);
+ }
+ }
+}
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index 8c0273b..ed93112 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -16,14 +16,13 @@
package com.android.mediarouteprovider.example;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_TV;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV;
import android.content.Intent;
import android.media.MediaRoute2Info;
-import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.IBinder;
import android.text.TextUtils;
@@ -46,8 +45,8 @@
public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
- public static final String ROUTE_ID_SPECIAL_TYPE = "route_special_type";
- public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type Route";
+ public static final String ROUTE_ID_SPECIAL_FEATURE = "route_special_feature";
+ public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
public static final int VOLUME_MAX = 100;
public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
@@ -58,49 +57,49 @@
public static final String ACTION_REMOVE_ROUTE =
"com.android.mediarouteprovider.action_remove_route";
- public static final String TYPE_SAMPLE =
- "com.android.mediarouteprovider.TYPE_SAMPLE";
- public static final String TYPE_SPECIAL =
- "com.android.mediarouteprovider.TYPE_SPECIAL";
+ public static final String FEATURE_SAMPLE =
+ "com.android.mediarouteprovider.FEATURE_SAMPLE";
+ public static final String FEATURE_SPECIAL =
+ "com.android.mediarouteprovider.FEATURE_SPECIAL";
Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
- Map<String, String> mRouteSessionMap = new HashMap<>();
+ Map<String, String> mRouteIdToSessionId = new HashMap<>();
private int mNextSessionId = 1000;
private void initializeRoutes() {
MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
- .addRouteType(TYPE_SAMPLE)
- .setDeviceType(DEVICE_TYPE_TV)
+ .addFeature(FEATURE_SAMPLE)
+ .setDeviceType(DEVICE_TYPE_REMOTE_TV)
.build();
MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2)
- .addRouteType(TYPE_SAMPLE)
- .setDeviceType(DEVICE_TYPE_SPEAKER)
+ .addFeature(FEATURE_SAMPLE)
+ .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
.build();
MediaRoute2Info route3 = new MediaRoute2Info.Builder(
ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.build();
MediaRoute2Info route4 = new MediaRoute2Info.Builder(
ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.build();
MediaRoute2Info route5 = new MediaRoute2Info.Builder(
ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.build();
MediaRoute2Info routeSpecial =
- new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_TYPE, ROUTE_NAME_SPECIAL_TYPE)
- .addRouteType(TYPE_SAMPLE)
- .addRouteType(TYPE_SPECIAL)
+ new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_FEATURE, ROUTE_NAME_SPECIAL_FEATURE)
+ .addFeature(FEATURE_SAMPLE)
+ .addFeature(FEATURE_SPECIAL)
.build();
MediaRoute2Info fixedVolumeRoute =
new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED)
.build();
MediaRoute2Info variableVolumeRoute =
new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(VOLUME_MAX)
.build();
@@ -154,6 +153,7 @@
@Override
public void onUpdateVolume(String routeId, int delta) {
+ android.util.Log.d(TAG, "onUpdateVolume routeId= " + routeId + "delta=" + delta);
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null) {
return;
@@ -167,12 +167,12 @@
}
@Override
- public void onCreateSession(String packageName, String routeId, String routeType,
+ public void onCreateSession(String packageName, String routeId, String routeFeature,
long requestId) {
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
// Tell the router that session cannot be created by passing null as sessionInfo.
- notifySessionCreated(/* sessionInfo= */ null, requestId);
+ notifySessionCreationFailed(requestId);
return;
}
maybeDeselectRoute(routeId);
@@ -183,10 +183,10 @@
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(packageName)
.build());
- mRouteSessionMap.put(routeId, sessionId);
+ mRouteIdToSessionId.put(routeId, sessionId);
- RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder(
- sessionId, packageName, routeType)
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ sessionId, packageName, routeFeature)
.addSelectedRoute(routeId)
.addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
.addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO)
@@ -196,9 +196,14 @@
}
@Override
- public void onDestroySession(String sessionId, RouteSessionInfo lastSessionInfo) {
- for (String routeId : lastSessionInfo.getSelectedRoutes()) {
- mRouteSessionMap.remove(routeId);
+ public void onReleaseSession(String sessionId) {
+ RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+ if (sessionInfo == null) {
+ return;
+ }
+
+ for (String routeId : sessionInfo.getSelectedRoutes()) {
+ mRouteIdToSessionId.remove(routeId);
MediaRoute2Info route = mRoutes.get(routeId);
if (route != null) {
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
@@ -206,11 +211,12 @@
.build());
}
}
+ notifySessionReleased(sessionId);
}
@Override
public void onSelectRoute(String sessionId, String routeId) {
- RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+ RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null || sessionInfo == null) {
return;
@@ -220,65 +226,66 @@
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(sessionInfo.getClientPackageName())
.build());
- mRouteSessionMap.put(routeId, sessionId);
+ mRouteIdToSessionId.put(routeId, sessionId);
- RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.addSelectedRoute(routeId)
.removeSelectableRoute(routeId)
.addDeselectableRoute(routeId)
.build();
- updateSessionInfo(newSessionInfo);
- notifySessionInfoChanged(newSessionInfo);
+ notifySessionUpdated(newSessionInfo);
}
@Override
public void onDeselectRoute(String sessionId, String routeId) {
- RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+ RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
MediaRoute2Info route = mRoutes.get(routeId);
- mRouteSessionMap.remove(routeId);
- if (sessionInfo == null || route == null) {
+ if (sessionInfo == null || route == null
+ || !sessionInfo.getSelectedRoutes().contains(routeId)) {
return;
}
+
+ mRouteIdToSessionId.remove(routeId);
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(null)
.build());
- RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ if (sessionInfo.getSelectedRoutes().size() == 1) {
+ notifySessionReleased(sessionId);
+ return;
+ }
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.removeSelectedRoute(routeId)
.addSelectableRoute(routeId)
.removeDeselectableRoute(routeId)
.build();
- updateSessionInfo(newSessionInfo);
- notifySessionInfoChanged(newSessionInfo);
+ notifySessionUpdated(newSessionInfo);
}
@Override
public void onTransferToRoute(String sessionId, String routeId) {
- RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
- RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.clearSelectedRoutes()
.addSelectedRoute(routeId)
.removeDeselectableRoute(routeId)
.removeTransferrableRoute(routeId)
.build();
- updateSessionInfo(newSessionInfo);
- notifySessionInfoChanged(newSessionInfo);
+ notifySessionUpdated(newSessionInfo);
}
void maybeDeselectRoute(String routeId) {
- if (!mRouteSessionMap.containsKey(routeId)) {
+ if (!mRouteIdToSessionId.containsKey(routeId)) {
return;
}
- String sessionId = mRouteSessionMap.get(routeId);
+ String sessionId = mRouteIdToSessionId.get(routeId);
onDeselectRoute(sessionId, routeId);
}
void publishRoutes() {
- MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
- .addRoutes(mRoutes.values())
- .build();
- updateProviderInfo(info);
+ notifyRoutes(mRoutes.values());
}
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index ce4bb8e..007229a 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -18,23 +18,23 @@
import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTED;
import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTING;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_TV;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV;
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_ALL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_SPECIAL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SAMPLE;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SPECIAL;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID1;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID2;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID4_TO_SELECT_AND_DESELECT;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID5_TO_TRANSFER_TO;
-import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_TYPE;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_FEATURE;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_ALL;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_SPECIAL;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SAMPLE;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SPECIAL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -48,10 +48,10 @@
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
import android.media.MediaRouter2.RouteCallback;
-import android.media.MediaRouter2.RouteSessionController;
+import android.media.MediaRouter2.RoutingController;
import android.media.MediaRouter2.SessionCallback;
-import android.media.RouteDiscoveryRequest;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
import android.net.Uri;
import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
@@ -100,10 +100,10 @@
*/
@Test
public void testGetRoutes() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(TYPES_SPECIAL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL);
assertEquals(1, routes.size());
- assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE));
+ assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
}
@Test
@@ -115,9 +115,9 @@
.setIconUri(new Uri.Builder().path("icon").build())
.setVolume(5)
.setVolumeMax(20)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
- .setDeviceType(DEVICE_TYPE_SPEAKER)
+ .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
.build();
MediaRoute2Info routeInfoRebuilt = new MediaRoute2Info.Builder(routeInfo).build();
@@ -138,21 +138,13 @@
.setClientPackageName("com.android.mediaroutertest")
.setConnectionState(CONNECTION_STATE_CONNECTING)
.setIconUri(new Uri.Builder().path("icon").build())
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.setVolume(5)
.setVolumeMax(20)
.setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
- .setDeviceType(DEVICE_TYPE_SPEAKER)
+ .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
.build();
- MediaRoute2Info routeId = new MediaRoute2Info.Builder(route)
- .setId("another id").build();
- assertNotEquals(route, routeId);
-
- MediaRoute2Info routeName = new MediaRoute2Info.Builder(route)
- .setName("another name").build();
- assertNotEquals(route, routeName);
-
MediaRoute2Info routeDescription = new MediaRoute2Info.Builder(route)
.setDescription("another description").build();
assertNotEquals(route, routeDescription);
@@ -170,7 +162,7 @@
assertNotEquals(route, routeClient);
MediaRoute2Info routeType = new MediaRoute2Info.Builder(route)
- .addRouteType(TYPE_SPECIAL).build();
+ .addFeature(FEATURE_SPECIAL).build();
assertNotEquals(route, routeType);
MediaRoute2Info routeVolume = new MediaRoute2Info.Builder(route)
@@ -186,13 +178,13 @@
assertNotEquals(route, routeVolumeHandling);
MediaRoute2Info routeDeviceType = new MediaRoute2Info.Builder(route)
- .setVolume(DEVICE_TYPE_TV).build();
+ .setVolume(DEVICE_TYPE_REMOTE_TV).build();
assertNotEquals(route, routeDeviceType);
}
@Test
public void testControlVolumeWithRouter() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_ALL);
MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
assertNotNull(volRoute);
@@ -204,13 +196,13 @@
() -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
ROUTE_ID_VARIABLE_VOLUME,
(route -> route.getVolume() == originalVolume + deltaVolume),
- TYPES_ALL);
+ FEATURES_ALL);
awaitOnRouteChanged(
() -> mRouter2.requestSetVolume(volRoute, originalVolume),
ROUTE_ID_VARIABLE_VOLUME,
(route -> route.getVolume() == originalVolume),
- TYPES_ALL);
+ FEATURES_ALL);
}
@Test
@@ -237,13 +229,13 @@
@Test
public void testRequestCreateSessionWithInvalidArguments() {
MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build();
- String routeType = "routeType";
+ String routeFeature = "routeFeature";
// Tests null route
assertThrows(NullPointerException.class,
- () -> mRouter2.requestCreateSession(null, routeType));
+ () -> mRouter2.requestCreateSession(null, routeFeature));
- // Tests null or empty route type
+ // Tests null or empty route feature
assertThrows(IllegalArgumentException.class,
() -> mRouter2.requestCreateSession(route, null));
assertThrows(IllegalArgumentException.class,
@@ -252,42 +244,42 @@
@Test
public void testRequestCreateSessionSuccess() throws Exception {
- final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ final List<String> sampleRouteFeature = new ArrayList<>();
+ sampleRouteFeature.add(FEATURE_SAMPLE);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
MediaRoute2Info route = routes.get(ROUTE_ID1);
assertNotNull(route);
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
assertNotNull(controller);
assertTrue(createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
controllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedRouteType) {
+ String requestedRouteFeature) {
failureLatch.countDown();
}
};
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// onSessionCreationFailed should not be called.
@@ -302,7 +294,7 @@
@Test
public void testRequestCreateSessionFailure() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
@@ -310,32 +302,32 @@
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
controllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedRouteType) {
+ String requestedRouteFeature) {
assertEquals(route, requestedRoute);
- assertTrue(TextUtils.equals(TYPE_SAMPLE, requestedRouteType));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, requestedRouteFeature));
failureLatch.countDown();
}
};
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// onSessionCreated should not be called.
@@ -350,23 +342,23 @@
@Test
public void testRequestCreateSessionMultipleSessions() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
final CountDownLatch successLatch = new CountDownLatch(2);
final CountDownLatch failureLatch = new CountDownLatch(1);
- final List<RouteSessionController> createdControllers = new ArrayList<>();
+ final List<RoutingController> createdControllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
createdControllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedRouteType) {
+ String requestedRouteFeature) {
failureLatch.countDown();
}
};
@@ -379,12 +371,12 @@
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route1, TYPE_SAMPLE);
- mRouter2.requestCreateSession(route2, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(route1, FEATURE_SAMPLE);
+ mRouter2.requestCreateSession(route2, FEATURE_SAMPLE);
assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// onSessionCreationFailed should not be called.
@@ -392,14 +384,14 @@
// Created controllers should have proper info
assertEquals(2, createdControllers.size());
- RouteSessionController controller1 = createdControllers.get(0);
- RouteSessionController controller2 = createdControllers.get(1);
+ RoutingController controller1 = createdControllers.get(0);
+ RoutingController controller2 = createdControllers.get(1);
assertNotEquals(controller1.getSessionId(), controller2.getSessionId());
assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(ROUTE_ID1));
assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller1.getRouteType()));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller2.getRouteType()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller1.getRouteFeature()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller2.getRouteFeature()));
} finally {
releaseControllers(createdControllers);
@@ -411,7 +403,7 @@
@Test
public void testSessionCallbackIsNotCalledAfterUnregistered() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info route = routes.get(ROUTE_ID1);
@@ -419,30 +411,30 @@
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
controllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedRouteType) {
+ String requestedRouteFeature) {
failureLatch.countDown();
}
};
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
// Unregisters session callback
mRouter2.unregisterSessionCallback(sessionCallback);
@@ -459,9 +451,9 @@
// TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route)
@Test
- public void testRouteSessionControllerSelectAndDeselectRoute() throws Exception {
+ public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
@@ -470,22 +462,22 @@
final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
final CountDownLatch onSessionInfoChangedLatchForSelect = new CountDownLatch(1);
final CountDownLatch onSessionInfoChangedLatchForDeselect = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with ROUTE_ID1
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
assertNotNull(controller);
assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
controllers.add(controller);
onSessionCreatedLatch.countDown();
}
@Override
- public void onSessionInfoChanged(RouteSessionController controller,
- RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ public void onSessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
if (onSessionCreatedLatch.getCount() != 0
|| !TextUtils.equals(
controllers.get(0).getSessionId(), controller.getSessionId())) {
@@ -527,15 +519,15 @@
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals(1, controllers.size());
- RouteSessionController controller = controllers.get(0);
+ RoutingController controller = controllers.get(0);
assertTrue(getRouteIds(controller.getSelectableRoutes())
.contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
@@ -558,9 +550,9 @@
}
@Test
- public void testRouteSessionControllerTransferToRoute() throws Exception {
+ public void testRoutingControllerTransferToRoute() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
@@ -568,26 +560,22 @@
final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with ROUTE_ID1
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
assertNotNull(controller);
- android.util.Log.d(TAG, "selected route ids ");
- for (String routeId : getRouteIds(controller.getSelectedRoutes())) {
- android.util.Log.d(TAG, "route id : " + routeId);
- }
assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
controllers.add(controller);
onSessionCreatedLatch.countDown();
}
@Override
- public void onSessionInfoChanged(RouteSessionController controller,
- RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ public void onSessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
if (onSessionCreatedLatch.getCount() != 0
|| !TextUtils.equals(
controllers.get(0).getSessionId(), controller.getSessionId())) {
@@ -613,15 +601,15 @@
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals(1, controllers.size());
- RouteSessionController controller = controllers.get(0);
+ RoutingController controller = controllers.get(0);
assertTrue(getRouteIds(controller.getTransferrableRoutes())
.contains(ROUTE_ID5_TO_TRANSFER_TO));
@@ -641,9 +629,9 @@
// TODO: Add tests for onSessionReleased() call.
@Test
- public void testRouteSessionControllerReleaseShouldIgnoreTransferTo() throws Exception {
+ public void testRoutingControllerReleaseShouldIgnoreTransferTo() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
@@ -651,22 +639,22 @@
final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with ROUTE_ID1
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
assertNotNull(controller);
assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
controllers.add(controller);
onSessionCreatedLatch.countDown();
}
@Override
- public void onSessionInfoChanged(RouteSessionController controller,
- RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ public void onSessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
if (onSessionCreatedLatch.getCount() != 0
|| !TextUtils.equals(
controllers.get(0).getSessionId(), controller.getSessionId())) {
@@ -678,15 +666,15 @@
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals(1, controllers.size());
- RouteSessionController controller = controllers.get(0);
+ RoutingController controller = controllers.get(0);
assertTrue(getRouteIds(controller.getTransferrableRoutes())
.contains(ROUTE_ID5_TO_TRANSFER_TO));
@@ -735,7 +723,7 @@
};
mRouter2.registerRouteCallback(mExecutor, routeCallback,
- new RouteDiscoveryRequest.Builder(routeTypes, true).build());
+ new RouteDiscoveryPreference.Builder(routeTypes, true).build());
try {
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mRouter2.getRoutes());
@@ -744,8 +732,8 @@
}
}
- static void releaseControllers(@NonNull List<RouteSessionController> controllers) {
- for (RouteSessionController controller : controllers) {
+ static void releaseControllers(@NonNull List<RoutingController> controllers) {
+ for (RoutingController controller : controllers) {
controller.release();
}
}
@@ -775,7 +763,7 @@
}
};
mRouter2.registerRouteCallback(mExecutor, routeCallback,
- new RouteDiscoveryRequest.Builder(routeTypes, true).build());
+ new RouteDiscoveryPreference.Builder(routeTypes, true).build());
try {
task.run();
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 9ff9177c..ae15b91 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -31,7 +31,7 @@
import android.media.MediaRouter2.RouteCallback;
import android.media.MediaRouter2.SessionCallback;
import android.media.MediaRouter2Manager;
-import android.media.RouteDiscoveryRequest;
+import android.media.RouteDiscoveryPreference;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -76,9 +76,9 @@
SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id5_to_transfer_to";
public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
- public static final String ROUTE_ID_SPECIAL_TYPE =
- SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_type";
- public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type Route";
+ public static final String ROUTE_ID_SPECIAL_FEATURE =
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_feature";
+ public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
public static final String SYSTEM_PROVIDER_ID =
"com.android.server.media/.SystemMediaRoute2Provider";
@@ -94,12 +94,12 @@
public static final String ACTION_REMOVE_ROUTE =
"com.android.mediarouteprovider.action_remove_route";
- public static final String TYPE_SAMPLE =
- "com.android.mediarouteprovider.TYPE_SAMPLE";
- public static final String TYPE_SPECIAL =
- "com.android.mediarouteprovider.TYPE_SPECIAL";
+ public static final String FEATURE_SAMPLE =
+ "com.android.mediarouteprovider.FEATURE_SAMPLE";
+ public static final String FEATURE_SPECIAL =
+ "com.android.mediarouteprovider.FEATURE_SPECIAL";
- private static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO";
+ private static final String FEATURE_LIVE_AUDIO = "android.media.intent.route.LIVE_AUDIO";
private static final int TIMEOUT_MS = 5000;
@@ -113,18 +113,18 @@
private final List<RouteCallback> mRouteCallbacks = new ArrayList<>();
private final List<SessionCallback> mSessionCallbacks = new ArrayList<>();
- public static final List<String> TYPES_ALL = new ArrayList();
- public static final List<String> TYPES_SPECIAL = new ArrayList();
- private static final List<String> TYPES_LIVE_AUDIO = new ArrayList<>();
+ public static final List<String> FEATURES_ALL = new ArrayList();
+ public static final List<String> FEATURES_SPECIAL = new ArrayList();
+ private static final List<String> FEATURES_LIVE_AUDIO = new ArrayList<>();
static {
- TYPES_ALL.add(TYPE_SAMPLE);
- TYPES_ALL.add(TYPE_SPECIAL);
- TYPES_ALL.add(TYPE_LIVE_AUDIO);
+ FEATURES_ALL.add(FEATURE_SAMPLE);
+ FEATURES_ALL.add(FEATURE_SPECIAL);
+ FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
- TYPES_SPECIAL.add(TYPE_SPECIAL);
+ FEATURES_SPECIAL.add(FEATURE_SPECIAL);
- TYPES_LIVE_AUDIO.add(TYPE_LIVE_AUDIO);
+ FEATURES_LIVE_AUDIO.add(FEATURE_LIVE_AUDIO);
}
@Before
@@ -181,7 +181,7 @@
@Test
public void testOnRoutesRemoved() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@@ -203,14 +203,14 @@
}
/**
- * Tests if we get proper routes for application that has special route type.
+ * Tests if we get proper routes for application that has special route feature.
*/
@Test
- public void testRouteType() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_SPECIAL);
+ public void testRouteFeatures() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_SPECIAL);
assertEquals(1, routes.size());
- assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE));
+ assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
}
/**
@@ -219,7 +219,7 @@
*/
@Test
public void testRouterOnSessionCreated() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
CountDownLatch latch = new CountDownLatch(1);
@@ -228,7 +228,7 @@
addRouterCallback(new MediaRouter2.RouteCallback());
addSessionCallback(new SessionCallback() {
@Override
- public void onSessionCreated(MediaRouter2.RouteSessionController controller) {
+ public void onSessionCreated(MediaRouter2.RoutingController controller) {
if (createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) {
latch.countDown();
}
@@ -255,7 +255,7 @@
@Ignore("TODO: test session created callback instead of onRouteSelected")
public void testManagerOnRouteSelected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@@ -285,7 +285,7 @@
public void testGetActiveRoutes() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
@@ -321,7 +321,7 @@
@Test
@Ignore("TODO: enable when session is released")
public void testSingleProviderSelect() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
awaitOnRouteChangedManager(
@@ -346,7 +346,7 @@
@Test
public void testControlVolumeWithManager() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
int originalVolume = volRoute.getVolume();
@@ -365,7 +365,7 @@
@Test
public void testVolumeHandling() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
@@ -375,11 +375,11 @@
assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
}
- Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeTypes)
+ Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
throws Exception {
CountDownLatch latch = new CountDownLatch(2);
- // A dummy callback is required to send route type info.
+ // A dummy callback is required to send route feature info.
RouteCallback routeCallback = new RouteCallback();
MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
@Override
@@ -394,16 +394,17 @@
}
@Override
- public void onControlCategoriesChanged(String packageName, List<String> routeTypes) {
+ public void onControlCategoriesChanged(String packageName,
+ List<String> preferredFeatures) {
if (TextUtils.equals(mPackageName, packageName)
- && routeTypes.equals(routeTypes)) {
+ && preferredFeatures.equals(preferredFeatures)) {
latch.countDown();
}
}
};
mManager.registerCallback(mExecutor, managerCallback);
mRouter2.registerRouteCallback(mExecutor, routeCallback,
- new RouteDiscoveryRequest.Builder(routeTypes, true).build());
+ new RouteDiscoveryPreference.Builder(routeFeatures, true).build());
try {
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mManager.getAvailableRoutes(mPackageName));
@@ -450,7 +451,7 @@
private void addRouterCallback(RouteCallback routeCallback) {
mRouteCallbacks.add(routeCallback);
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
}
private void addSessionCallback(SessionCallback sessionCallback) {
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
similarity index 75%
rename from media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java
rename to media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
index 60d131a..fa12935 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import android.media.RouteDiscoveryRequest;
+import android.media.RouteDiscoveryPreference;
import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -34,7 +34,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class RouteDiscoveryRequestTest {
+public class RouteDiscoveryPreferenceTest {
@Before
public void setUp() throws Exception { }
@@ -46,10 +46,10 @@
List<String> testTypes = new ArrayList<>();
testTypes.add("TEST_TYPE_1");
testTypes.add("TEST_TYPE_2");
- RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true)
+ RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
.build();
- RouteDiscoveryRequest requestRebuilt = new RouteDiscoveryRequest.Builder(request)
+ RouteDiscoveryPreference requestRebuilt = new RouteDiscoveryPreference.Builder(request)
.build();
assertEquals(request, requestRebuilt);
@@ -57,7 +57,7 @@
Parcel parcel = Parcel.obtain();
parcel.writeParcelable(request, 0);
parcel.setDataPosition(0);
- RouteDiscoveryRequest requestFromParcel = parcel.readParcelable(null);
+ RouteDiscoveryPreference requestFromParcel = parcel.readParcelable(null);
assertEquals(request, requestFromParcel);
}
@@ -71,15 +71,15 @@
List<String> testTypes2 = new ArrayList<>();
testTypes.add("TEST_TYPE_3");
- RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true)
+ RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
.build();
- RouteDiscoveryRequest requestTypes = new RouteDiscoveryRequest.Builder(request)
- .setRouteTypes(testTypes2)
+ RouteDiscoveryPreference requestTypes = new RouteDiscoveryPreference.Builder(request)
+ .setPreferredFeatures(testTypes2)
.build();
assertNotEquals(request, requestTypes);
- RouteDiscoveryRequest requestActiveScan = new RouteDiscoveryRequest.Builder(request)
+ RouteDiscoveryPreference requestActiveScan = new RouteDiscoveryPreference.Builder(request)
.setActiveScan(false)
.build();
assertNotEquals(request, requestActiveScan);
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
deleted file mode 100644
index 9971fc3..0000000
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.mediaroutertest;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.media.RouteSessionInfo;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RouteSessionTest {
- private static final String TEST_SESSION_ID = "test_session_id";
- 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(TEST_SESSION_ID,
- "",
- TEST_CONTROL_CATEGORY)
- .addSelectedRoute(TEST_ROUTE_ID1)
- .build();
- RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(TEST_SESSION_ID,
- TEST_PACKAGE_NAME, "")
- .addSelectedRoute(TEST_ROUTE_ID1)
- .build();
-
- RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(TEST_SESSION_ID,
- TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY)
- .build();
-
- RouteSessionInfo validSession = new RouteSessionInfo.Builder(emptySelectedRouteSession)
- .addSelectedRoute(TEST_ROUTE_ID1)
- .build();
-
- assertFalse(emptyPackageSession.isValid());
- assertFalse(emptyCategorySession.isValid());
- assertFalse(emptySelectedRouteSession.isValid());
- assertTrue(validSession.isValid());
- }
-}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
new file mode 100644
index 0000000..3f59736
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediaroutertest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.media.RoutingSessionInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link RoutingSessionInfo} and its {@link RoutingSessionInfo.Builder builder}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RoutingSessionInfoTest {
+ public static final String TEST_ID = "test_id";
+ public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
+ public static final String TEST_ROUTE_FEATURE = "test_route_feature";
+
+ public static final String TEST_ROUTE_ID_0 = "test_route_type_0";
+ public static final String TEST_ROUTE_ID_1 = "test_route_type_1";
+ public static final String TEST_ROUTE_ID_2 = "test_route_type_2";
+ public static final String TEST_ROUTE_ID_3 = "test_route_type_3";
+ public static final String TEST_ROUTE_ID_4 = "test_route_type_4";
+ public static final String TEST_ROUTE_ID_5 = "test_route_type_5";
+ public static final String TEST_ROUTE_ID_6 = "test_route_type_6";
+ public static final String TEST_ROUTE_ID_7 = "test_route_type_7";
+
+ public static final String TEST_KEY = "test_key";
+ public static final String TEST_VALUE = "test_value";
+
+ @Test
+ public void testBuilderConstructorWithInvalidValues() {
+ final String nullId = null;
+ final String nullClientPackageName = null;
+ final String nullRouteFeature = null;
+
+ final String emptyId = "";
+ // Note: An empty string as client package name is valid.
+ final String emptyRouteFeature = "";
+
+ final String validId = TEST_ID;
+ final String validClientPackageName = TEST_CLIENT_PACKAGE_NAME;
+ final String validRouteFeature = TEST_ROUTE_FEATURE;
+
+ // ID is invalid
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, validClientPackageName, validRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, validClientPackageName, validRouteFeature));
+
+ // client package name is invalid (null)
+ assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+ validId, nullClientPackageName, validRouteFeature));
+
+ // route feature is invalid
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ validId, validClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ validId, validClientPackageName, emptyRouteFeature));
+
+ // Two arguments are invalid - (1) ID and clientPackageName
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, nullClientPackageName, validRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, nullClientPackageName, validRouteFeature));
+
+ // Two arguments are invalid - (2) ID and routeFeature
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, validClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, validClientPackageName, emptyRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, validClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, validClientPackageName, emptyRouteFeature));
+
+ // Two arguments are invalid - (3) clientPackageName and routeFeature
+ // Note that this throws NullPointerException.
+ assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+ validId, nullClientPackageName, nullRouteFeature));
+ assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+ validId, nullClientPackageName, emptyRouteFeature));
+
+ // All arguments are invalid
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, nullClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, nullClientPackageName, emptyRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, nullClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, nullClientPackageName, emptyRouteFeature));
+
+ // Null RouteInfo (1-argument constructor)
+ final RoutingSessionInfo nullRoutingSessionInfo = null;
+ assertThrows(NullPointerException.class,
+ () -> new RoutingSessionInfo.Builder(nullRoutingSessionInfo));
+ }
+
+ @Test
+ public void testBuilderConstructorWithEmptyClientPackageName() {
+ // An empty string for client package name is valid. (for unknown cases)
+ // Creating builder with it should not throw any exception.
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, "" /* clientPackageName*/, TEST_ROUTE_FEATURE);
+ }
+
+ @Test
+ public void testBuilderBuildWithEmptySelectedRoutesThrowsIAE() {
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+ // Note: Calling build() without adding any selected routes.
+ assertThrows(IllegalArgumentException.class, () -> builder.build());
+ }
+
+ @Test
+ public void testBuilderAddRouteMethodsWithIllegalArgumentsThrowsIAE() {
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+
+ final String nullRouteId = null;
+ final String emptyRouteId = "";
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectedRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addDeselectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addTransferrableRoute(nullRouteId));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectedRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addDeselectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addTransferrableRoute(emptyRouteId));
+ }
+
+ @Test
+ public void testBuilderRemoveRouteMethodsWithIllegalArgumentsThrowsIAE() {
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+
+ final String nullRouteId = null;
+ final String emptyRouteId = "";
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectedRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeDeselectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeTransferrableRoute(nullRouteId));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectedRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeDeselectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeTransferrableRoute(emptyRouteId));
+ }
+
+ @Test
+ public void testBuilderAndGettersOfRoutingSessionInfo() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ assertEquals(TEST_ID, sessionInfo.getId());
+ assertEquals(TEST_CLIENT_PACKAGE_NAME, sessionInfo.getClientPackageName());
+ assertEquals(TEST_ROUTE_FEATURE, sessionInfo.getRouteFeature());
+
+ assertEquals(2, sessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_1, sessionInfo.getSelectedRoutes().get(1));
+
+ assertEquals(2, sessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_3, sessionInfo.getSelectableRoutes().get(1));
+
+ assertEquals(2, sessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_5, sessionInfo.getDeselectableRoutes().get(1));
+
+ assertEquals(2, sessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_7, sessionInfo.getTransferrableRoutes().get(1));
+
+ Bundle controlHintsOut = sessionInfo.getControlHints();
+ assertNotNull(controlHintsOut);
+ assertTrue(controlHintsOut.containsKey(TEST_KEY));
+ assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+ }
+
+ @Test
+ public void testBuilderAddRouteMethodsWithBuilderCopyConstructor() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .build();
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ assertEquals(2, newSessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_1, newSessionInfo.getSelectedRoutes().get(1));
+
+ assertEquals(2, newSessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_3, newSessionInfo.getSelectableRoutes().get(1));
+
+ assertEquals(2, newSessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_5, newSessionInfo.getDeselectableRoutes().get(1));
+
+ assertEquals(2, newSessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_7, newSessionInfo.getTransferrableRoutes().get(1));
+ }
+
+ @Test
+ public void testBuilderRemoveRouteMethods() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .removeSelectedRoute(TEST_ROUTE_ID_1)
+
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .removeSelectableRoute(TEST_ROUTE_ID_3)
+
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .removeDeselectableRoute(TEST_ROUTE_ID_5)
+
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .removeTransferrableRoute(TEST_ROUTE_ID_7)
+
+ .build();
+
+ assertEquals(1, sessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+ assertEquals(1, sessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+
+ assertEquals(1, sessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+
+ assertEquals(1, sessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0));
+ }
+
+ @Test
+ public void testBuilderRemoveRouteMethodsWithBuilderCopyConstructor() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .removeSelectedRoute(TEST_ROUTE_ID_1)
+ .removeSelectableRoute(TEST_ROUTE_ID_3)
+ .removeDeselectableRoute(TEST_ROUTE_ID_5)
+ .removeTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+ assertEquals(1, newSessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+
+ assertEquals(1, newSessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+
+ assertEquals(1, newSessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0));
+ }
+
+ @Test
+ public void testBuilderClearRouteMethods() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .clearSelectedRoutes()
+
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .clearSelectableRoutes()
+
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .clearDeselectableRoutes()
+
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .clearTransferrableRoutes()
+
+ // SelectedRoutes must not be empty
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .build();
+
+ assertEquals(1, sessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+ assertTrue(sessionInfo.getSelectableRoutes().isEmpty());
+ assertTrue(sessionInfo.getDeselectableRoutes().isEmpty());
+ assertTrue(sessionInfo.getTransferrableRoutes().isEmpty());
+ }
+
+ @Test
+ public void testBuilderClearRouteMethodsWithBuilderCopyConstructor() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .clearSelectedRoutes()
+ .clearSelectableRoutes()
+ .clearDeselectableRoutes()
+ .clearTransferrableRoutes()
+ // SelectedRoutes must not be empty
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .build();
+
+ assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+ assertTrue(newSessionInfo.getSelectableRoutes().isEmpty());
+ assertTrue(newSessionInfo.getDeselectableRoutes().isEmpty());
+ assertTrue(newSessionInfo.getTransferrableRoutes().isEmpty());
+ }
+
+ @Test
+ public void testEqualsCreatedWithSameArguments() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ assertEquals(sessionInfo1, sessionInfo2);
+ assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+ }
+
+ @Test
+ public void testEqualsCreatedWithBuilderCopyConstructor() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(sessionInfo1).build();
+
+ assertEquals(sessionInfo1, sessionInfo2);
+ assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+ }
+
+ @Test
+ public void testEqualsReturnFalse() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ // Now, we will use copy constructor
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addSelectedRoute("randomRoute")
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addSelectableRoute("randomRoute")
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addDeselectableRoute("randomRoute")
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addTransferrableRoute("randomRoute")
+ .build());
+
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeSelectedRoute(TEST_ROUTE_ID_1)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeSelectableRoute(TEST_ROUTE_ID_3)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeDeselectableRoute(TEST_ROUTE_ID_5)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeTransferrableRoute(TEST_ROUTE_ID_7)
+ .build());
+
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearSelectedRoutes()
+ // Note: Calling build() with empty selected routes will throw IAE.
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearSelectableRoutes()
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearDeselectableRoutes()
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearTransferrableRoutes()
+ .build());
+
+ // Note: ControlHints will not affect the equals.
+ }
+
+ @Test
+ public void testParcelingAndUnParceling() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ sessionInfo.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ RoutingSessionInfo sessionInfoFromParcel =
+ RoutingSessionInfo.CREATOR.createFromParcel(parcel);
+ assertEquals(sessionInfo, sessionInfoFromParcel);
+ assertEquals(sessionInfo.hashCode(), sessionInfoFromParcel.hashCode());
+
+ // Check controlHints
+ Bundle controlHintsOut = sessionInfoFromParcel.getControlHints();
+ assertNotNull(controlHintsOut);
+ assertTrue(controlHintsOut.containsKey(TEST_KEY));
+ assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+ }
+}
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index cb04d92..c1f8b3f 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -46,6 +46,7 @@
# <extension1> and <extension2> are mapped (or remapped) to <mimeType>.
?application/epub+zip epub
+?application/lrc lrc
?application/pkix-cert cer
?application/rss+xml rss
?application/sdp sdp
@@ -65,6 +66,7 @@
?application/x-mpegurl m3u m3u8
?application/x-pem-file pem
?application/x-pkcs12 p12 pfx
+?application/x-subrip srt
?application/x-webarchive webarchive
?application/x-webarchive-xml webarchivexml
?application/x-x509-server-cert crt
diff --git a/mms/java/android/telephony/MmsManager.java b/mms/java/android/telephony/MmsManager.java
index 24ea3cf..cf55eba 100644
--- a/mms/java/android/telephony/MmsManager.java
+++ b/mms/java/android/telephony/MmsManager.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemService;
import android.app.ActivityThread;
import android.app.PendingIntent;
import android.content.Context;
@@ -30,8 +31,10 @@
/**
* Manages MMS operations such as sending multimedia messages.
+ * Get this object by calling Context#getSystemService(Context#MMS_SERVICE).
*/
-public final class MmsManager {
+@SystemService(Context.MMS_SERVICE)
+public class MmsManager {
private static final String TAG = "MmsManager";
private final Context mContext;
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 942eafd..376ea77 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -24,12 +24,21 @@
// our source files
//
- srcs: ["bitmap.cpp"],
+ srcs: [
+ "aassetstreamadaptor.cpp",
+ "bitmap.cpp",
+ "imagedecoder.cpp",
+ ],
shared_libs: [
+ "libandroid",
"libandroid_runtime",
+ "libhwui",
+ "liblog",
],
+ static_libs: ["libarect"],
+
arch: {
arm: {
// TODO: This is to work around b/24465209. Remove after root cause is fixed
diff --git a/native/graphics/jni/aassetstreamadaptor.cpp b/native/graphics/jni/aassetstreamadaptor.cpp
new file mode 100644
index 0000000..016008b
--- /dev/null
+++ b/native/graphics/jni/aassetstreamadaptor.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "aassetstreamadaptor.h"
+
+#include <log/log.h>
+
+AAssetStreamAdaptor::AAssetStreamAdaptor(AAsset* asset)
+ : mAAsset(asset)
+{
+}
+
+bool AAssetStreamAdaptor::rewind() {
+ off64_t pos = AAsset_seek64(mAAsset, 0, SEEK_SET);
+ if (pos == (off64_t)-1) {
+ ALOGE("rewind failed!");
+ return false;
+ }
+ return true;
+}
+
+size_t AAssetStreamAdaptor::getLength() const {
+ return AAsset_getLength64(mAAsset);
+}
+
+bool AAssetStreamAdaptor::isAtEnd() const {
+ return AAsset_getRemainingLength64(mAAsset) == 0;
+}
+
+SkStreamRewindable* AAssetStreamAdaptor::onDuplicate() const {
+ // Cannot sensibly create a duplicate, since each AAssetStreamAdaptor
+ // would be modifying the same AAsset.
+ //return new AAssetStreamAdaptor(mAAsset);
+ return nullptr;
+}
+
+bool AAssetStreamAdaptor::hasPosition() const {
+ return AAsset_seek64(mAAsset, 0, SEEK_CUR) != -1;
+}
+
+size_t AAssetStreamAdaptor::getPosition() const {
+ const off64_t offset = AAsset_seek64(mAAsset, 0, SEEK_CUR);
+ if (offset == -1) {
+ ALOGE("getPosition failed!");
+ return 0;
+ }
+
+ return offset;
+}
+
+bool AAssetStreamAdaptor::seek(size_t position) {
+ if (AAsset_seek64(mAAsset, position, SEEK_SET) == -1) {
+ ALOGE("seek failed!");
+ return false;
+ }
+
+ return true;
+}
+
+bool AAssetStreamAdaptor::move(long offset) {
+ if (AAsset_seek64(mAAsset, offset, SEEK_CUR) == -1) {
+ ALOGE("move failed!");
+ return false;
+ }
+
+ return true;
+}
+
+size_t AAssetStreamAdaptor::read(void* buffer, size_t size) {
+ ssize_t amount;
+
+ if (!buffer) {
+ if (!size) {
+ return 0;
+ }
+
+ // asset->seek returns new total offset
+ // we want to return amount that was skipped
+ const off64_t oldOffset = AAsset_seek64(mAAsset, 0, SEEK_CUR);
+ if (oldOffset == -1) {
+ ALOGE("seek(oldOffset) failed!");
+ return 0;
+ }
+
+ const off64_t newOffset = AAsset_seek64(mAAsset, size, SEEK_CUR);
+ if (-1 == newOffset) {
+ ALOGE("seek(%zu) failed!", size);
+ return 0;
+ }
+ amount = newOffset - oldOffset;
+ } else {
+ amount = AAsset_read(mAAsset, buffer, size);
+ }
+
+ if (amount < 0) {
+ amount = 0;
+ }
+ return amount;
+}
+
+const void* AAssetStreamAdaptor::getMemoryBase() {
+ return AAsset_getBuffer(mAAsset);
+}
+
diff --git a/native/graphics/jni/aassetstreamadaptor.h b/native/graphics/jni/aassetstreamadaptor.h
new file mode 100644
index 0000000..c1fddb0
--- /dev/null
+++ b/native/graphics/jni/aassetstreamadaptor.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "SkStream.h"
+
+#include <android/asset_manager.h>
+
+// Like AssetStreamAdaptor, but operates on AAsset, a public NDK API.
+class AAssetStreamAdaptor : public SkStreamRewindable {
+public:
+ /**
+ * Create an SkStream that reads from an AAsset.
+ *
+ * The AAsset must remain open for the lifetime of the Adaptor. The Adaptor
+ * does *not* close the AAsset.
+ */
+ explicit AAssetStreamAdaptor(AAsset*);
+
+ bool rewind() override;
+ size_t read(void* buffer, size_t size) override;
+ bool hasLength() const override { return true; }
+ size_t getLength() const override;
+ bool hasPosition() const override;
+ size_t getPosition() const override;
+ bool seek(size_t position) override;
+ bool move(long offset) override;
+ bool isAtEnd() const override;
+ const void* getMemoryBase() override;
+protected:
+ SkStreamRewindable* onDuplicate() const override;
+
+private:
+ AAsset* mAAsset;
+};
+
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
new file mode 100644
index 0000000..2ef203d
--- /dev/null
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "aassetstreamadaptor.h"
+
+#include <android/asset_manager.h>
+#include <android/bitmap.h>
+#include <android/imagedecoder.h>
+#include <android/graphics/MimeType.h>
+#include <android/rect.h>
+#include <hwui/ImageDecoder.h>
+#include <log/log.h>
+#include <SkAndroidCodec.h>
+
+#include <fcntl.h>
+#include <optional>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace android;
+
+int ResultToErrorCode(SkCodec::Result result) {
+ switch (result) {
+ case SkCodec::kIncompleteInput:
+ return ANDROID_IMAGE_DECODER_INCOMPLETE;
+ case SkCodec::kErrorInInput:
+ return ANDROID_IMAGE_DECODER_ERROR;
+ case SkCodec::kInvalidInput:
+ return ANDROID_IMAGE_DECODER_INVALID_INPUT;
+ case SkCodec::kCouldNotRewind:
+ return ANDROID_IMAGE_DECODER_SEEK_ERROR;
+ case SkCodec::kUnimplemented:
+ return ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT;
+ case SkCodec::kInvalidConversion:
+ return ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+ case SkCodec::kInvalidParameters:
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ case SkCodec::kSuccess:
+ return ANDROID_IMAGE_DECODER_SUCCESS;
+ case SkCodec::kInvalidScale:
+ return ANDROID_IMAGE_DECODER_INVALID_SCALE;
+ case SkCodec::kInternalError:
+ return ANDROID_IMAGE_DECODER_INTERNAL_ERROR;
+ }
+}
+
+static int createFromStream(std::unique_ptr<SkStreamRewindable> stream, AImageDecoder** outDecoder) {
+ SkCodec::Result result;
+ auto codec = SkCodec::MakeFromStream(std::move(stream), &result, nullptr,
+ SkCodec::SelectionPolicy::kPreferAnimation);
+ auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec),
+ SkAndroidCodec::ExifOrientationBehavior::kRespect);
+ if (!androidCodec) {
+ return ResultToErrorCode(result);
+ }
+
+ *outDecoder = reinterpret_cast<AImageDecoder*>(new ImageDecoder(std::move(androidCodec)));
+ return ANDROID_IMAGE_DECODER_SUCCESS;
+}
+
+int AImageDecoder_createFromAAsset(AAsset* asset, AImageDecoder** outDecoder) {
+ if (!asset || !outDecoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+ *outDecoder = nullptr;
+
+ auto stream = std::make_unique<AAssetStreamAdaptor>(asset);
+ return createFromStream(std::move(stream), outDecoder);
+}
+
+static bool isSeekable(int descriptor) {
+ return ::lseek64(descriptor, 0, SEEK_CUR) != -1;
+}
+
+int AImageDecoder_createFromFd(int fd, AImageDecoder** outDecoder) {
+ if (fd <= 0 || !outDecoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ struct stat fdStat;
+ if (fstat(fd, &fdStat) == -1) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ if (!isSeekable(fd)) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ // SkFILEStream will close its descriptor. Duplicate it so the client will
+ // still be responsible for closing the original.
+ int dupDescriptor = fcntl(fd, F_DUPFD_CLOEXEC, 0);
+ FILE* file = fdopen(dupDescriptor, "r");
+ if (!file) {
+ close(dupDescriptor);
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ auto stream = std::unique_ptr<SkStreamRewindable>(new SkFILEStream(file));
+ return createFromStream(std::move(stream), outDecoder);
+}
+
+int AImageDecoder_createFromBuffer(const void* buffer, size_t length,
+ AImageDecoder** outDecoder) {
+ if (!buffer || !length || !outDecoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+ *outDecoder = nullptr;
+
+ // The client is expected to keep the buffer alive as long as the
+ // AImageDecoder, so we do not need to copy the buffer.
+ auto stream = std::unique_ptr<SkStreamRewindable>(
+ new SkMemoryStream(buffer, length, false /* copyData */));
+ return createFromStream(std::move(stream), outDecoder);
+}
+
+static ImageDecoder* toDecoder(AImageDecoder* d) {
+ return reinterpret_cast<ImageDecoder*>(d);
+}
+
+// Note: This differs from the version in android_bitmap.cpp in that this
+// version returns kGray_8_SkColorType for ANDROID_BITMAP_FORMAT_A_8. SkCodec
+// allows decoding single channel images to gray, which Android then treats
+// as A_8/ALPHA_8.
+static SkColorType getColorType(AndroidBitmapFormat format) {
+ switch (format) {
+ case ANDROID_BITMAP_FORMAT_RGBA_8888:
+ return kN32_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGB_565:
+ return kRGB_565_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_4444:
+ return kARGB_4444_SkColorType;
+ case ANDROID_BITMAP_FORMAT_A_8:
+ return kGray_8_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_F16:
+ return kRGBA_F16_SkColorType;
+ default:
+ return kUnknown_SkColorType;
+ }
+}
+
+int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) {
+ if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE
+ || format > ANDROID_BITMAP_FORMAT_RGBA_F16) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+ return toDecoder(decoder)->setOutColorType(getColorType((AndroidBitmapFormat) format))
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+}
+
+const AImageDecoderHeaderInfo* AImageDecoder_getHeaderInfo(const AImageDecoder* decoder) {
+ return reinterpret_cast<const AImageDecoderHeaderInfo*>(decoder);
+}
+
+static const ImageDecoder* toDecoder(const AImageDecoderHeaderInfo* info) {
+ return reinterpret_cast<const ImageDecoder*>(info);
+}
+
+int32_t AImageDecoderHeaderInfo_getWidth(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return 0;
+ }
+ return toDecoder(info)->mCodec->getInfo().width();
+}
+
+int32_t AImageDecoderHeaderInfo_getHeight(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return 0;
+ }
+ return toDecoder(info)->mCodec->getInfo().height();
+}
+
+const char* AImageDecoderHeaderInfo_getMimeType(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return nullptr;
+ }
+ return getMimeType(toDecoder(info)->mCodec->getEncodedFormat());
+}
+
+bool AImageDecoderHeaderInfo_isAnimated(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return false;
+ }
+ return toDecoder(info)->mCodec->codec()->getFrameCount() > 1;
+}
+
+// FIXME: Share with getFormat in android_bitmap.cpp?
+static AndroidBitmapFormat getFormat(SkColorType colorType) {
+ switch (colorType) {
+ case kN32_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_8888;
+ case kRGB_565_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGB_565;
+ case kARGB_4444_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_4444;
+ case kAlpha_8_SkColorType:
+ return ANDROID_BITMAP_FORMAT_A_8;
+ case kRGBA_F16_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_F16;
+ default:
+ return ANDROID_BITMAP_FORMAT_NONE;
+ }
+}
+
+AndroidBitmapFormat AImageDecoderHeaderInfo_getAndroidBitmapFormat(
+ const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return ANDROID_BITMAP_FORMAT_NONE;
+ }
+ return getFormat(toDecoder(info)->mCodec->computeOutputColorType(kN32_SkColorType));
+}
+
+int AImageDecoderHeaderInfo_getAlphaFlags(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ // FIXME: Better invalid?
+ return -1;
+ }
+ switch (toDecoder(info)->mCodec->getInfo().alphaType()) {
+ case kUnknown_SkAlphaType:
+ LOG_ALWAYS_FATAL("Invalid alpha type");
+ return -1;
+ case kUnpremul_SkAlphaType:
+ // fall through. premul is the default.
+ case kPremul_SkAlphaType:
+ return ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
+ case kOpaque_SkAlphaType:
+ return ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE;
+ }
+}
+
+SkAlphaType toAlphaType(int androidBitmapFlags) {
+ switch (androidBitmapFlags) {
+ case ANDROID_BITMAP_FLAGS_ALPHA_PREMUL:
+ return kPremul_SkAlphaType;
+ case ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL:
+ return kUnpremul_SkAlphaType;
+ case ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE:
+ return kOpaque_SkAlphaType;
+ default:
+ return kUnknown_SkAlphaType;
+ }
+}
+
+int AImageDecoder_setAlphaFlags(AImageDecoder* decoder, int alphaFlag) {
+ if (!decoder || alphaFlag < ANDROID_BITMAP_FLAGS_ALPHA_PREMUL
+ || alphaFlag > ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ return toDecoder(decoder)->setOutAlphaType(toAlphaType(alphaFlag))
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+}
+
+int AImageDecoder_setTargetSize(AImageDecoder* decoder, int width, int height) {
+ if (!decoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ return toDecoder(decoder)->setTargetSize(width, height)
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_SCALE;
+}
+
+int AImageDecoder_setCrop(AImageDecoder* decoder, ARect crop) {
+ if (!decoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ SkIRect cropIRect;
+ cropIRect.setLTRB(crop.left, crop.top, crop.right, crop.bottom);
+ SkIRect* cropPtr = cropIRect == SkIRect::MakeEmpty() ? nullptr : &cropIRect;
+ return toDecoder(decoder)->setCropRect(cropPtr)
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+}
+
+
+size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) {
+ if (!decoder) {
+ return 0;
+ }
+
+ SkImageInfo info = toDecoder(decoder)->getOutputInfo();
+ return info.minRowBytes();
+}
+
+int AImageDecoder_decodeImage(AImageDecoder* decoder,
+ void* pixels, size_t stride,
+ size_t size) {
+ if (!decoder || !pixels || !stride) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ ImageDecoder* imageDecoder = toDecoder(decoder);
+
+ const int height = imageDecoder->getOutputInfo().height();
+ const size_t minStride = AImageDecoder_getMinimumStride(decoder);
+ // If this calculation were to overflow, it would have been caught in
+ // setTargetSize.
+ if (stride < minStride || size < stride * (height - 1) + minStride) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ return ResultToErrorCode(imageDecoder->decode(pixels, stride));
+}
+
+void AImageDecoder_delete(AImageDecoder* decoder) {
+ delete toDecoder(decoder);
+}
diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt
index 6adb955..832770f 100644
--- a/native/graphics/jni/libjnigraphics.map.txt
+++ b/native/graphics/jni/libjnigraphics.map.txt
@@ -1,5 +1,22 @@
LIBJNIGRAPHICS {
global:
+ AImageDecoder_createFromAAsset;
+ AImageDecoder_createFromFd;
+ AImageDecoder_createFromBuffer;
+ AImageDecoder_delete;
+ AImageDecoder_setAndroidBitmapFormat;
+ AImageDecoder_setAlphaFlags;
+ AImageDecoder_getHeaderInfo;
+ AImageDecoder_getMinimumStride;
+ AImageDecoder_decodeImage;
+ AImageDecoder_setTargetSize;
+ AImageDecoder_setCrop;
+ AImageDecoderHeaderInfo_getWidth;
+ AImageDecoderHeaderInfo_getHeight;
+ AImageDecoderHeaderInfo_getMimeType;
+ AImageDecoderHeaderInfo_getAlphaFlags;
+ AImageDecoderHeaderInfo_isAnimated;
+ AImageDecoderHeaderInfo_getAndroidBitmapFormat;
AndroidBitmap_getInfo;
AndroidBitmap_getDataSpace;
AndroidBitmap_lockPixels;
diff --git a/packages/CarSystemUI/res/layout/sysui_primary_window.xml b/packages/CarSystemUI/res/layout/sysui_primary_window.xml
new file mode 100644
index 0000000..cc36e87
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/sysui_primary_window.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- Fullscreen views in sysui should be listed here in increasing Z order. -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="@android:color/transparent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout="@layout/car_fullscreen_user_switcher"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
index f7802d2..cfe1c70 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -19,14 +19,19 @@
import android.service.notification.StatusBarNotification;
import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.util.leak.LeakDetector;
import javax.inject.Inject;
import javax.inject.Singleton;
+import dagger.Lazy;
+
/**
* Car specific notification entry manager that does nothing when adding a notification.
*
@@ -42,8 +47,12 @@
NotificationGroupManager groupManager,
NotificationRankingManager rankingManager,
KeyguardEnvironment keyguardEnvironment,
- FeatureFlags featureFlags) {
- super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags);
+ FeatureFlags featureFlags,
+ Lazy<NotificationRowBinder> notificationRowBinderLazy,
+ Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+ LeakDetector leakDetector) {
+ super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags,
+ notificationRowBinderLazy, notificationRemoteInputManagerLazy, leakDetector);
}
@Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
new file mode 100644
index 0000000..c7e14d6
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Binder;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Controls the expansion state of the primary window which will contain all of the fullscreen sysui
+ * behavior. This window still has a collapsed state in order to watch for swipe events to expand
+ * this window for the notification panel.
+ */
+@Singleton
+public class SystemUIPrimaryWindowController implements
+ ConfigurationController.ConfigurationListener {
+
+ private final Context mContext;
+ private final Resources mResources;
+ private final WindowManager mWindowManager;
+
+ private final int mStatusBarHeight;
+ private final int mNavBarHeight;
+ private final int mDisplayHeight;
+ private ViewGroup mBaseLayout;
+ private WindowManager.LayoutParams mLp;
+ private WindowManager.LayoutParams mLpChanged;
+ private boolean mIsAttached = false;
+
+ @Inject
+ public SystemUIPrimaryWindowController(
+ Context context,
+ @Main Resources resources,
+ WindowManager windowManager,
+ ConfigurationController configurationController
+ ) {
+ mContext = context;
+ mResources = resources;
+ mWindowManager = windowManager;
+
+ Point display = new Point();
+ mWindowManager.getDefaultDisplay().getSize(display);
+ mDisplayHeight = display.y;
+
+ mStatusBarHeight = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ mNavBarHeight = mResources.getDimensionPixelSize(R.dimen.navigation_bar_height);
+
+ mLpChanged = new WindowManager.LayoutParams();
+ mBaseLayout = (ViewGroup) LayoutInflater.from(context)
+ .inflate(R.layout.sysui_primary_window, /* root= */ null, false);
+
+ configurationController.addCallback(this);
+ }
+
+ /** Returns the base view of the primary window. */
+ public ViewGroup getBaseLayout() {
+ return mBaseLayout;
+ }
+
+ /** Returns {@code true} if the window is already attached. */
+ public boolean isAttached() {
+ return mIsAttached;
+ }
+
+ /** Attaches the window to the window manager. */
+ public void attach() {
+ if (mIsAttached) {
+ return;
+ }
+ mIsAttached = true;
+ // Now that the status bar window encompasses the sliding panel and its
+ // translucent backdrop, the entire thing is made TRANSLUCENT and is
+ // hardware-accelerated.
+ mLp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ mStatusBarHeight,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+ PixelFormat.TRANSLUCENT);
+ mLp.token = new Binder();
+ mLp.gravity = Gravity.TOP;
+ mLp.setFitWindowInsetsTypes(/* types= */ 0);
+ mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ mLp.setTitle("SystemUIPrimaryWindow");
+ mLp.packageName = mContext.getPackageName();
+ mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+ mWindowManager.addView(mBaseLayout, mLp);
+ mLpChanged.copyFrom(mLp);
+ }
+
+ /** Sets the window to the expanded state. */
+ public void setWindowExpanded(boolean expanded) {
+ if (expanded) {
+ // TODO: Update this so that the windowing type gets the full height of the display
+ // when we use MATCH_PARENT.
+ mLpChanged.height = mDisplayHeight + mNavBarHeight;
+ mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ } else {
+ mLpChanged.height = mStatusBarHeight;
+ // TODO: Allow touches to go through to the status bar to handle notification panel.
+ mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ }
+ updateWindow();
+ }
+
+ /** Returns {@code true} if the window is expanded */
+ public boolean isWindowExpanded() {
+ return mLp.height != mStatusBarHeight;
+ }
+
+ private void updateWindow() {
+ if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
+ if (isAttached()) {
+ mWindowManager.updateViewLayout(mBaseLayout, mLp);
+ }
+ }
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 3c3ebe2..18485dc 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.car;
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import android.animation.Animator;
@@ -68,6 +67,7 @@
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarDeviceProvisionedListener;
import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.SystemUIPrimaryWindowController;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -107,7 +107,8 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
@@ -168,6 +169,7 @@
// acceleration rate for the fling animation
private static final float FLING_SPEED_UP_FACTOR = 0.6f;
+ private final UserSwitcherController mUserSwitcherController;
private final ScrimController mScrimController;
private final LockscreenLockIconController mLockscreenLockIconController;
@@ -177,17 +179,16 @@
private float mBackgroundAlphaDiff;
private float mInitialBackgroundAlpha;
- private final Lazy<FullscreenUserSwitcher> mFullscreenUserSwitcherLazy;
- private FullscreenUserSwitcher mFullscreenUserSwitcher;
-
private CarBatteryController mCarBatteryController;
private BatteryMeterView mBatteryMeterView;
private Drawable mNotificationPanelBackground;
private final Object mQueueLock = new Object();
+ private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController;
private final CarNavigationBarController mCarNavigationBarController;
private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
private final Lazy<PowerManagerHelper> mPowerManagerHelperLazy;
+ private final FullscreenUserSwitcher mFullscreenUserSwitcher;
private final ShadeController mShadeController;
private final CarServiceProvider mCarServiceProvider;
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
@@ -268,8 +269,7 @@
HeadsUpManagerPhone headsUpManagerPhone,
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
- @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- Lazy<NewNotifPipeline> newNotifPipeline,
+ Lazy<NotifPipelineInitializer> notifPipelineInitializer,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -334,11 +334,13 @@
KeyguardDismissUtil keyguardDismissUtil,
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
+ NotificationRowBinderImpl notificationRowBinder,
DismissCallbackRegistry dismissCallbackRegistry,
/* Car Settings injected components. */
CarServiceProvider carServiceProvider,
Lazy<PowerManagerHelper> powerManagerHelperLazy,
- Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy,
+ FullscreenUserSwitcher fullscreenUserSwitcher,
+ SystemUIPrimaryWindowController systemUIPrimaryWindowController,
CarNavigationBarController carNavigationBarController,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
super(
@@ -355,8 +357,7 @@
headsUpManagerPhone,
dynamicPrivacyController,
bypassHeadsUpNotifier,
- allowNotificationLongPress,
- newNotifPipeline,
+ notifPipelineInitializer,
falsingManager,
broadcastDispatcher,
remoteInputQuickSettingsDisabler,
@@ -421,7 +422,9 @@
keyguardDismissUtil,
extensionController,
userInfoControllerImpl,
+ notificationRowBinder,
dismissCallbackRegistry);
+ mUserSwitcherController = userSwitcherController;
mScrimController = scrimController;
mLockscreenLockIconController = lockscreenLockIconController;
mCarDeviceProvisionedController =
@@ -429,7 +432,8 @@
mShadeController = shadeController;
mCarServiceProvider = carServiceProvider;
mPowerManagerHelperLazy = powerManagerHelperLazy;
- mFullscreenUserSwitcherLazy = fullscreenUserSwitcherLazy;
+ mFullscreenUserSwitcher = fullscreenUserSwitcher;
+ mSystemUIPrimaryWindowController = systemUIPrimaryWindowController;
mCarNavigationBarController = carNavigationBarController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
}
@@ -444,6 +448,13 @@
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
mScreenLifecycle.addObserver(mScreenObserver);
+ // TODO: Remove the setup of user switcher from Car Status Bar.
+ mSystemUIPrimaryWindowController.attach();
+ mFullscreenUserSwitcher.setStatusBar(this);
+ mFullscreenUserSwitcher.setContainer(
+ mSystemUIPrimaryWindowController.getBaseLayout().findViewById(
+ R.id.fullscreen_user_switcher_stub));
+
// Notification bar related setup.
mInitialBackgroundAlpha = (float) mContext.getResources().getInteger(
R.integer.config_initialNotificationBackgroundAlpha) / 100;
@@ -510,16 +521,6 @@
});
}
- /**
- * Allows for showing or hiding just the navigation bars. This is indented to be used when
- * the full screen user selector is shown.
- */
- void setNavBarVisibility(@View.Visibility int visibility) {
- mCarNavigationBarController.setBottomWindowVisibility(visibility);
- mCarNavigationBarController.setLeftWindowVisibility(visibility);
- mCarNavigationBarController.setRightWindowVisibility(visibility);
- }
-
@Override
public boolean hideKeyguard() {
boolean result = super.hideKeyguard();
@@ -924,9 +925,6 @@
+ " scroll " + mStackScroller.getScrollX()
+ "," + mStackScroller.getScrollY());
}
-
- pw.print(" mFullscreenUserSwitcher=");
- pw.println(mFullscreenUserSwitcher);
pw.print(" mCarBatteryController=");
pw.println(mCarBatteryController);
pw.print(" mBatteryMeterView=");
@@ -972,14 +970,7 @@
@Override
protected void createUserSwitcher() {
- UserSwitcherController userSwitcherController =
- Dependency.get(UserSwitcherController.class);
- if (userSwitcherController.useFullscreenUserSwitcher()) {
- mFullscreenUserSwitcher = mFullscreenUserSwitcherLazy.get();
- mFullscreenUserSwitcher.setStatusBar(this);
- mFullscreenUserSwitcher.setContainer(
- mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub));
- } else {
+ if (!mUserSwitcherController.useFullscreenUserSwitcher()) {
super.createUserSwitcher();
}
}
@@ -996,25 +987,12 @@
super.onStateChanged(newState);
if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) {
- hideUserSwitcher();
+ mFullscreenUserSwitcher.hide();
} else {
dismissKeyguardWhenUserSwitcherNotDisplayed();
}
}
- /** Makes the full screen user switcher visible, if applicable. */
- public void showUserSwitcher() {
- if (mFullscreenUserSwitcher != null && mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
- mFullscreenUserSwitcher.show(); // Makes the switcher visible.
- }
- }
-
- private void hideUserSwitcher() {
- if (mFullscreenUserSwitcher != null) {
- mFullscreenUserSwitcher.hide();
- }
- }
-
final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurnedOn() {
@@ -1024,7 +1002,7 @@
// We automatically dismiss keyguard unless user switcher is being shown on the keyguard.
private void dismissKeyguardWhenUserSwitcherNotDisplayed() {
- if (mFullscreenUserSwitcher == null) {
+ if (!mUserSwitcherController.useFullscreenUserSwitcher()) {
return; // Not using the full screen user switcher.
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
index 0ad0992..2a2eb69 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
@@ -24,6 +24,7 @@
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.R;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.navigationbar.car.CarNavigationBarController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.NavigationModeController;
@@ -40,6 +41,8 @@
public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager {
protected boolean mShouldHideNavBar;
+ private final CarNavigationBarController mCarNavigationBarController;
+ private final FullscreenUserSwitcher mFullscreenUserSwitcher;
@Inject
public CarStatusBarKeyguardViewManager(Context context,
@@ -52,13 +55,17 @@
DockManager dockManager,
StatusBarWindowController statusBarWindowController,
KeyguardStateController keyguardStateController,
- NotificationMediaManager notificationMediaManager) {
+ NotificationMediaManager notificationMediaManager,
+ CarNavigationBarController carNavigationBarController,
+ FullscreenUserSwitcher fullscreenUserSwitcher) {
super(context, callback, lockPatternUtils, sysuiStatusBarStateController,
configurationController, keyguardUpdateMonitor, navigationModeController,
dockManager, statusBarWindowController, keyguardStateController,
notificationMediaManager);
mShouldHideNavBar = context.getResources()
.getBoolean(R.bool.config_hideNavWhenKeyguardBouncerShown);
+ mCarNavigationBarController = carNavigationBarController;
+ mFullscreenUserSwitcher = fullscreenUserSwitcher;
}
@Override
@@ -66,8 +73,10 @@
if (!mShouldHideNavBar) {
return;
}
- CarStatusBar statusBar = (CarStatusBar) mStatusBar;
- statusBar.setNavBarVisibility(navBarVisible ? View.VISIBLE : View.GONE);
+ int visibility = navBarVisible ? View.VISIBLE : View.GONE;
+ mCarNavigationBarController.setBottomWindowVisibility(visibility);
+ mCarNavigationBarController.setLeftWindowVisibility(visibility);
+ mCarNavigationBarController.setRightWindowVisibility(visibility);
}
/**
@@ -86,8 +95,7 @@
*/
@Override
public void onCancelClicked() {
- CarStatusBar statusBar = (CarStatusBar) mStatusBar;
- statusBar.showUserSwitcher();
+ mFullscreenUserSwitcher.show();
}
/**
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index a1eccce..3abbe32 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.car;
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import android.content.Context;
@@ -32,6 +31,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.SystemUIPrimaryWindowController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -67,7 +67,8 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
@@ -138,8 +139,7 @@
HeadsUpManagerPhone headsUpManagerPhone,
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
- @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- Lazy<NewNotifPipeline> newNotifPipeline,
+ Lazy<NotifPipelineInitializer> notifPipelineInitializer,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -204,10 +204,12 @@
KeyguardDismissUtil keyguardDismissUtil,
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
+ NotificationRowBinderImpl notificationRowBinder,
DismissCallbackRegistry dismissCallbackRegistry,
CarServiceProvider carServiceProvider,
Lazy<PowerManagerHelper> powerManagerHelperLazy,
- Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy,
+ FullscreenUserSwitcher fullscreenUserSwitcher,
+ SystemUIPrimaryWindowController systemUIPrimaryWindowController,
CarNavigationBarController carNavigationBarController,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
return new CarStatusBar(
@@ -224,8 +226,7 @@
headsUpManagerPhone,
dynamicPrivacyController,
bypassHeadsUpNotifier,
- allowNotificationLongPress,
- newNotifPipeline,
+ notifPipelineInitializer,
falsingManager,
broadcastDispatcher,
remoteInputQuickSettingsDisabler,
@@ -289,10 +290,12 @@
keyguardDismissUtil,
extensionController,
userInfoControllerImpl,
+ notificationRowBinder,
dismissCallbackRegistry,
carServiceProvider,
powerManagerHelperLazy,
- fullscreenUserSwitcherLazy,
+ fullscreenUserSwitcher,
+ systemUIPrimaryWindowController,
carNavigationBarController,
flingAnimationUtilsBuilder);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index f8fc3bb..3cd66c2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -38,6 +38,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.SystemUIPrimaryWindowController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.car.CarTrustAgentUnlockDialogHelper.OnHideListener;
import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord;
@@ -56,9 +57,10 @@
private final UserManager mUserManager;
private final CarServiceProvider mCarServiceProvider;
private final CarTrustAgentUnlockDialogHelper mUnlockDialogHelper;
+ private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController;
+ private CarStatusBar mCarStatusBar;
private final int mShortAnimDuration;
- private CarStatusBar mStatusBar;
private View mParent;
private UserGridRecyclerView mUserGridView;
private CarTrustAgentEnrollmentManager mEnrollmentManager;
@@ -81,23 +83,35 @@
@Main Resources resources,
UserManager userManager,
CarServiceProvider carServiceProvider,
- CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper) {
+ CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper,
+ SystemUIPrimaryWindowController systemUIPrimaryWindowController) {
mContext = context;
mResources = resources;
mUserManager = userManager;
mCarServiceProvider = carServiceProvider;
mUnlockDialogHelper = carTrustAgentUnlockDialogHelper;
+ mSystemUIPrimaryWindowController = systemUIPrimaryWindowController;
mShortAnimDuration = mResources.getInteger(android.R.integer.config_shortAnimTime);
}
- /** Sets the status bar which controls the keyguard. */
+ /** Sets the status bar which gives an entry point to dismiss the keyguard. */
+ // TODO: Remove this in favor of a keyguard controller.
public void setStatusBar(CarStatusBar statusBar) {
- mStatusBar = statusBar;
+ mCarStatusBar = statusBar;
+ }
+
+ /** Returns {@code true} if the user switcher already has a parent view. */
+ public boolean isAttached() {
+ return mParent != null;
}
/** Sets the {@link ViewStub} to show the user switcher. */
public void setContainer(ViewStub containerStub) {
+ if (isAttached()) {
+ return;
+ }
+
mParent = containerStub.inflate();
View container = mParent.findViewById(R.id.container);
@@ -148,20 +162,31 @@
* Makes user grid visible.
*/
public void show() {
+ if (!isAttached()) {
+ return;
+ }
mParent.setVisibility(View.VISIBLE);
+ mSystemUIPrimaryWindowController.setWindowExpanded(true);
}
/**
* Hides the user grid.
*/
public void hide() {
+ if (!isAttached()) {
+ return;
+ }
mParent.setVisibility(View.INVISIBLE);
+ mSystemUIPrimaryWindowController.setWindowExpanded(false);
}
/**
* @return {@code true} if user grid is visible, {@code false} otherwise.
*/
public boolean isVisible() {
+ if (!isAttached()) {
+ return false;
+ }
return mParent.getVisibility() == View.VISIBLE;
}
@@ -196,7 +221,7 @@
}
if (mSelectedUser.mType == UserRecord.FOREGROUND_USER) {
hide();
- mStatusBar.dismissKeyguard();
+ mCarStatusBar.dismissKeyguard();
return;
}
// Switching is about to happen, since it takes time, fade out the switcher gradually.
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index cdabeeb..8c756ec 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -43,6 +43,9 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
@@ -53,7 +56,6 @@
import com.android.internal.util.UserIcons;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -337,7 +339,7 @@
.setPositiveButton(android.R.string.ok, null)
.create();
// Sets window flags for the SysUI dialog
- SystemUIDialog.applyFlags(maxUsersDialog);
+ applyCarSysUIDialogFlags(maxUsersDialog);
maxUsersDialog.show();
}
@@ -356,10 +358,19 @@
.setOnCancelListener(this)
.create();
// Sets window flags for the SysUI dialog
- SystemUIDialog.applyFlags(addUserDialog);
+ applyCarSysUIDialogFlags(addUserDialog);
addUserDialog.show();
}
+ private void applyCarSysUIDialogFlags(AlertDialog dialog) {
+ final Window window = dialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ window.setFitWindowInsetsTypes(
+ window.getFitWindowInsetsTypes() & ~WindowInsets.Type.statusBars());
+ }
+
private void notifyUserSelected(UserRecord userRecord) {
// Notify the listener which user was selected
if (mUserSelectionListener != null) {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 642dc82..41a9b24 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -36,6 +36,7 @@
import android.os.Bundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -49,7 +50,6 @@
import android.widget.TextView;
import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.TrafficStatsConstants;
@@ -203,7 +203,7 @@
}
private URL getUrlForCaptivePortal() {
- String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
+ String url = getIntent().getStringExtra(TelephonyManager.EXTRA_REDIRECTION_URL);
if (TextUtils.isEmpty(url)) url = mCm.getCaptivePortalServerUrl();
final CarrierConfigManager configManager = getApplicationContext()
.getSystemService(CarrierConfigManager.class);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index 2697a10..cb062a6 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -30,8 +30,6 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.telephony.PhoneConstants;
-
/**
* This util class provides common logic for carrier actions
*/
@@ -103,7 +101,7 @@
}
private static void onDisableAllMeteredApns(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onDisableAllMeteredApns subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -111,7 +109,7 @@
}
private static void onEnableAllMeteredApns(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onEnableAllMeteredApns subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -135,7 +133,7 @@
}
private static void onRegisterDefaultNetworkAvail(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onRegisterDefaultNetworkAvail subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -143,7 +141,7 @@
}
private static void onDeregisterDefaultNetworkAvail(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onDeregisterDefaultNetworkAvail subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -151,7 +149,7 @@
}
private static void onDisableRadio(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onDisableRadio subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -159,7 +157,7 @@
}
private static void onEnableRadio(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onEnableRadio subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -202,7 +200,7 @@
}
private static void onResetAllCarrierActions(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onResetAllCarrierActions subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
index 46b1d5f..c7f5e9a 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -19,10 +19,10 @@
import android.content.Intent;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
@@ -50,7 +50,7 @@
* @param intent passing signal for config match
* @return a list of carrier action for the given signal based on the carrier config.
*
- * Example: input intent TelephonyIntent.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
+ * Example: input intent TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
* This intent allows fined-grained matching based on both intent type & extra values:
* apnType and errorCode.
* apnType read from passing intent is "default" and errorCode is 0x26 for example and
@@ -78,25 +78,25 @@
String arg1 = null;
String arg2 = null;
switch (intent.getAction()) {
- case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+ case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED:
configs = b.getStringArray(CarrierConfigManager
.KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY);
break;
- case TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED:
+ case TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED:
configs = b.getStringArray(CarrierConfigManager
.KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY);
- arg1 = intent.getStringExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY);
- arg2 = intent.getStringExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY);
+ arg1 = intent.getStringExtra(TelephonyManager.EXTRA_APN_TYPE);
+ arg2 = intent.getStringExtra(TelephonyManager.EXTRA_ERROR_CODE);
break;
- case TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET:
+ case TelephonyManager.ACTION_CARRIER_SIGNAL_RESET:
configs = b.getStringArray(CarrierConfigManager
.KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET);
break;
- case TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE:
+ case TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE:
configs = b.getStringArray(CarrierConfigManager
.KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE);
- arg1 = String.valueOf(intent.getBooleanExtra(TelephonyIntents
- .EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false));
+ arg1 = String.valueOf(intent.getBooleanExtra(TelephonyManager
+ .EXTRA_DEFAULT_NETWORK_AVAILABLE, false));
break;
default:
Log.e(TAG, "load carrier config failure with un-configured key: "
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
index 3e34f0a..78a02d7 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
@@ -27,10 +27,9 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.provider.Settings;
+import android.telephony.TelephonyManager;
import android.util.Log;
-import com.android.internal.telephony.TelephonyIntents;
-
/**
* Service to run {@link android.app.job.JobScheduler} job.
* Service to monitor when there is a change to conent URI
@@ -93,7 +92,7 @@
}
int jobId;
switch(intent.getAction()) {
- case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+ case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED:
jobId = PROVISION_OBSERVER_REEVALUATION_JOB_ID;
break;
default:
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
index 1928ad9..6229434 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
@@ -15,18 +15,22 @@
*/
package com.android.carrierdefaultapp;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.test.InstrumentationTestCase;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -34,10 +38,6 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
public class CarrierDefaultReceiverTest extends InstrumentationTestCase {
@Mock
@@ -69,6 +69,7 @@
mContext.injectSystemService(NotificationManager.class, mNotificationMgr);
mContext.injectSystemService(TelephonyManager.class, mTelephonyMgr);
mContext.injectSystemService(CarrierConfigManager.class, mCarrierConfigMgr);
+ doReturn(mTelephonyMgr).when(mTelephonyMgr).createForSubscriptionId(anyInt());
mReceiver = new CarrierDefaultBroadcastReceiver();
}
@@ -87,8 +88,8 @@
.KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY, new String[]{"4,1"});
doReturn(b).when(mCarrierConfigMgr).getConfig();
- Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED);
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+ Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED);
+ intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
mReceiver.onReceive(mContext, intent);
mContext.waitForMs(100);
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index c2ce840..beb3aff 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -56,6 +56,7 @@
import android.os.RemoteException;
import android.os.image.DynamicSystemClient;
import android.os.image.DynamicSystemManager;
+import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
@@ -74,6 +75,8 @@
// TODO (b/131866826): This is currently for test only. Will move this to System API.
static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
+ static final String KEY_DSU_SLOT = "KEY_DSU_SLOT";
+ static final String DEFAULT_DSU_SLOT = "dsu";
/*
* Intent actions
@@ -244,10 +247,15 @@
long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
+ String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT);
+ if (TextUtils.isEmpty(dsuSlot)) {
+ dsuSlot = DEFAULT_DSU_SLOT;
+ }
// TODO: better constructor or builder
- mInstallTask = new InstallationAsyncTask(
- url, systemSize, userdataSize, this, mDynSystem, this);
+ mInstallTask =
+ new InstallationAsyncTask(
+ url, dsuSlot, systemSize, userdataSize, this, mDynSystem, this);
mInstallTask.execute();
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index b206a1f..9aea0e7 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -89,10 +89,12 @@
interface ProgressListener {
void onProgressUpdate(Progress progress);
+
void onResult(int resultCode, Throwable detail);
}
private final String mUrl;
+ private final String mDsuSlot;
private final long mSystemSize;
private final long mUserdataSize;
private final Context mContext;
@@ -106,9 +108,16 @@
private InputStream mStream;
private ZipFile mZipFile;
- InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context,
- DynamicSystemManager dynSystem, ProgressListener listener) {
+ InstallationAsyncTask(
+ String url,
+ String dsuSlot,
+ long systemSize,
+ long userdataSize,
+ Context context,
+ DynamicSystemManager dynSystem,
+ ProgressListener listener) {
mUrl = url;
+ mDsuSlot = dsuSlot;
mSystemSize = systemSize;
mUserdataSize = userdataSize;
mContext = context;
@@ -126,14 +135,17 @@
verifyAndPrepare();
- mDynSystem.startInstallation();
+ mDynSystem.startInstallation(mDsuSlot);
installUserdata();
if (isCancelled()) {
mDynSystem.remove();
return null;
}
-
+ if (mUrl == null) {
+ mDynSystem.finishInstallation();
+ return null;
+ }
installImages();
if (isCancelled()) {
mDynSystem.remove();
@@ -194,6 +206,9 @@
}
private void verifyAndPrepare() throws Exception {
+ if (mUrl == null) {
+ return;
+ }
String extension = mUrl.substring(mUrl.lastIndexOf('.') + 1);
if ("gz".equals(extension) || "gzip".equals(extension)) {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
index 3b3933b..e42ded7 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
@@ -28,11 +28,9 @@
import android.util.FeatureFlagUtils;
import android.util.Log;
-
/**
- * This Activity starts KeyguardManager and ask the user to confirm
- * before any installation request. If the device is not protected by
- * a password, it approves the request by default.
+ * This Activity starts KeyguardManager and ask the user to confirm before any installation request.
+ * If the device is not protected by a password, it approves the request by default.
*/
public class VerificationActivity extends Activity {
@@ -88,11 +86,15 @@
Uri url = callingIntent.getData();
Bundle extras = callingIntent.getExtras();
- sVerifiedUrl = url.toString();
+ if (url != null) {
+ sVerifiedUrl = url.toString();
+ }
// start service
Intent intent = new Intent(this, DynamicSystemInstallationService.class);
- intent.setData(url);
+ if (url != null) {
+ intent.setData(url);
+ }
intent.setAction(DynamicSystemClient.ACTION_START_INSTALL);
intent.putExtras(extras);
@@ -106,6 +108,7 @@
}
static boolean isVerified(String url) {
+ if (url == null) return true;
return sVerifiedUrl != null && sVerifiedUrl.equals(url);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
index 69bd0ed..ff00fb3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -16,8 +16,6 @@
package com.android.settingslib;
-import static android.content.Context.TELEPHONY_SERVICE;
-
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -172,36 +170,38 @@
}
}
- public static String getFormattedPhoneNumber(Context context, SubscriptionInfo subscriptionInfo) {
+ /**
+ * Format a phone number.
+ * @param subscriptionInfo {@link SubscriptionInfo} subscription information.
+ * @return Returns formatted phone number.
+ */
+ public static String getFormattedPhoneNumber(Context context,
+ SubscriptionInfo subscriptionInfo) {
String formattedNumber = null;
if (subscriptionInfo != null) {
- final TelephonyManager telephonyManager =
- (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
- final String rawNumber =
- telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId());
+ final TelephonyManager telephonyManager = context.getSystemService(
+ TelephonyManager.class);
+ final String rawNumber = telephonyManager.createForSubscriptionId(
+ subscriptionInfo.getSubscriptionId()).getLine1Number();
if (!TextUtils.isEmpty(rawNumber)) {
formattedNumber = PhoneNumberUtils.formatNumber(rawNumber);
}
-
}
return formattedNumber;
}
public static String getFormattedPhoneNumbers(Context context,
- List<SubscriptionInfo> subscriptionInfo) {
+ List<SubscriptionInfo> subscriptionInfoList) {
StringBuilder sb = new StringBuilder();
- if (subscriptionInfo != null) {
- final TelephonyManager telephonyManager =
- (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
- final int count = subscriptionInfo.size();
- for (int i = 0; i < count; i++) {
- final String rawNumber = telephonyManager.getLine1Number(
- subscriptionInfo.get(i).getSubscriptionId());
+ if (subscriptionInfoList != null) {
+ final TelephonyManager telephonyManager = context.getSystemService(
+ TelephonyManager.class);
+ final int count = subscriptionInfoList.size();
+ for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) {
+ final String rawNumber = telephonyManager.createForSubscriptionId(
+ subscriptionInfo.getSubscriptionId()).getLine1Number();
if (!TextUtils.isEmpty(rawNumber)) {
- sb.append(PhoneNumberUtils.formatNumber(rawNumber));
- if (i < count - 1) {
- sb.append("\n");
- }
+ sb.append(PhoneNumberUtils.formatNumber(rawNumber)).append("\n");
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 747ceb1..50d3a5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -262,13 +262,28 @@
}
}
+ /**
+ * Connect this device.
+ *
+ * @param connectAllProfiles {@code true} to connect all profile, {@code false} otherwise.
+ *
+ * @deprecated use {@link #connect()} instead.
+ */
+ @Deprecated
public void connect(boolean connectAllProfiles) {
+ connect();
+ }
+
+ /**
+ * Connect this device.
+ */
+ public void connect() {
if (!ensurePaired()) {
return;
}
mConnectAttempted = SystemClock.elapsedRealtime();
- connectWithoutResettingTimer(connectAllProfiles);
+ connectAllEnabledProfiles();
}
public long getHiSyncId() {
@@ -289,10 +304,10 @@
void onBondingDockConnect() {
// Attempt to connect if UUIDs are available. Otherwise,
// we will connect when the ACTION_UUID intent arrives.
- connect(false);
+ connect();
}
- private void connectWithoutResettingTimer(boolean connectAllProfiles) {
+ private void connectAllEnabledProfiles() {
synchronized (mProfileLock) {
// Try to initialize the profiles if they were not.
if (mProfiles.isEmpty()) {
@@ -307,36 +322,7 @@
return;
}
- int preferredProfiles = 0;
- for (LocalBluetoothProfile profile : mProfiles) {
- if (connectAllProfiles ? profile.accessProfileEnabled()
- : profile.isAutoConnectable()) {
- if (profile.isPreferred(mDevice)) {
- ++preferredProfiles;
- connectInt(profile);
- }
- }
- }
- if (BluetoothUtils.D) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
-
- if (preferredProfiles == 0) {
- connectAutoConnectableProfiles();
- }
- }
- }
-
- private void connectAutoConnectableProfiles() {
- if (!ensurePaired()) {
- return;
- }
-
- synchronized (mProfileLock) {
- for (LocalBluetoothProfile profile : mProfiles) {
- if (profile.isAutoConnectable()) {
- profile.setPreferred(mDevice, true);
- connectInt(profile);
- }
- }
+ mLocalAdapter.connectAllEnabledProfiles(mDevice);
}
}
@@ -703,7 +689,7 @@
*/
if (!mProfiles.isEmpty()
&& ((mConnectAttempted + timeout) > SystemClock.elapsedRealtime())) {
- connectWithoutResettingTimer(false);
+ connectAllEnabledProfiles();
}
dispatchAttributesChanged();
@@ -722,7 +708,7 @@
refresh();
if (bondState == BluetoothDevice.BOND_BONDED && mDevice.isBondingInitiatedLocally()) {
- connect(false);
+ connect();
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
index 61e47f8..6e7a429 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -87,8 +87,10 @@
if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) {
return;
}
- final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
- mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+ if (mCreationTimestamp != 0L) {
+ final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
+ mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+ }
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 4ab9a9a..b07fc2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -61,6 +61,8 @@
"com.android.settings.category.ia.my_device_info";
public static final String CATEGORY_BATTERY_SAVER_SETTINGS =
"com.android.settings.category.ia.battery_saver_settings";
+ public static final String CATEGORY_SMART_BATTERY_SETTINGS =
+ "com.android.settings.category.ia.smart_battery_settings";
public static final Map<String, String> KEY_COMPAT_MAP;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 70b56ed..e85a102 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -122,7 +122,7 @@
final CachedBluetoothDevice cachedDevice =
((BluetoothMediaDevice) device).getCachedDevice();
if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) {
- cachedDevice.connect(true);
+ cachedDevice.connect();
return;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index ebca962..05a6ce4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -38,9 +38,9 @@
final SubscriptionManager subscriptionManager = context.getSystemService(
SubscriptionManager.class);
final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
- telephonyManager.getSubscriberId(subId));
+ telephonyManager.createForSubscriptionId(subId).getSubscriberId());
- if (!subscriptionManager.isActiveSubId(subId)) {
+ if (!subscriptionManager.isActiveSubscriptionId(subId)) {
Log.i(TAG, "Subscription is not active: " + subId);
return mobileAll;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 81739e0..9d4c24e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -211,13 +211,6 @@
private static final int EAP_WPA = 1; // WPA-EAP
private static final int EAP_WPA2_WPA3 = 2; // RSN-EAP
- /**
- * The number of distinct wifi levels.
- *
- * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}.
- */
- public static final int SIGNAL_LEVELS = 5;
-
public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE;
public static final String KEY_PREFIX_AP = "AP:";
@@ -453,9 +446,10 @@
return other.getSpeed() - getSpeed();
}
+ WifiManager wifiManager = getWifiManager();
// Sort by signal strength, bucketed by level
- int difference = WifiManager.calculateSignalLevel(other.mRssi, SIGNAL_LEVELS)
- - WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
+ int difference = wifiManager.calculateSignalLevel(other.mRssi)
+ - wifiManager.calculateSignalLevel(mRssi);
if (difference != 0) {
return difference;
}
@@ -869,13 +863,14 @@
}
/**
- * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1.
+ * Returns the number of levels to show for a Wifi icon, from 0 to
+ * {@link WifiManager#getMaxSignalLevel()}.
*
- * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will
+ * <p>Use {@link #isReachable()} to determine if an AccessPoint is in range, as this method will
* always return at least 0.
*/
public int getLevel() {
- return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
+ return getWifiManager().calculateSignalLevel(mRssi);
}
public int getRssi() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 4a4f0e6..f21e466 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -22,6 +22,7 @@
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Parcelable;
@@ -126,13 +127,15 @@
@Keep
public TestAccessPointBuilder setLevel(int level) {
// Reversal of WifiManager.calculateSignalLevels
+ WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+ int maxSignalLevel = wifiManager.getMaxSignalLevel();
if (level == 0) {
mRssi = MIN_RSSI;
- } else if (level >= AccessPoint.SIGNAL_LEVELS) {
+ } else if (level > maxSignalLevel) {
mRssi = MAX_RSSI;
} else {
float inputRange = MAX_RSSI - MIN_RSSI;
- float outputRange = AccessPoint.SIGNAL_LEVELS - 1;
+ float outputRange = maxSignalLevel;
mRssi = (int) (level * inputRange / outputRange + MIN_RSSI);
}
return this;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 8591116..3f55cea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -89,7 +89,7 @@
public void setListening(boolean listening) {
if (listening) {
mNetworkScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
- mWifiNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
+ mWifiNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK);
mWifiNetworkScoreCache.registerListener(mCacheListener);
mConnectivityManager.registerNetworkCallback(
mNetworkRequest, mNetworkCallback, mHandler);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index ba6a8ea..ed4ff08 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -361,7 +361,7 @@
mNetworkScoreManager.registerNetworkScoreCache(
NetworkKey.TYPE_WIFI,
mScoreCache,
- NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
+ NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS);
}
private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index 4600793..2ccff1e 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -14,7 +14,10 @@
android_test {
name: "SettingsLibTests",
- defaults: ["SettingsLibDefaults"],
+ defaults: [
+ "SettingsLibDefaults",
+ "framework-wifi-test-defaults"
+ ],
certificate: "platform",
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 61cbbd3..03201ae 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -83,7 +83,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AccessPointTest {
-
private static final String TEST_SSID = "\"test_ssid\"";
private static final String ROAMING_SSID = "\"roaming_ssid\"";
private static final String OSU_FRIENDLY_NAME = "osu_friendly_name";
@@ -98,6 +97,7 @@
20 * DateUtils.MINUTE_IN_MILLIS;
private Context mContext;
+ private int mMaxSignalLevel;
private WifiInfo mWifiInfo;
@Mock private Context mMockContext;
@Mock private WifiManager mMockWifiManager;
@@ -128,6 +128,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getTargetContext();
+ mMaxSignalLevel = mContext.getSystemService(WifiManager.class).getMaxSignalLevel();
mWifiInfo = new WifiInfo();
mWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID));
mWifiInfo.setBSSID(TEST_BSSID);
@@ -180,7 +181,7 @@
@Test
public void testCompareTo_GivesHighLevelBeforeLowLevel() {
- final int highLevel = AccessPoint.SIGNAL_LEVELS - 1;
+ final int highLevel = mMaxSignalLevel;
final int lowLevel = 1;
assertThat(highLevel).isGreaterThan(lowLevel);
@@ -226,7 +227,7 @@
.setReachable(true).build();
AccessPoint saved = new TestAccessPointBuilder(mContext).setSaved(true).build();
AccessPoint highLevelAndReachable = new TestAccessPointBuilder(mContext)
- .setLevel(AccessPoint.SIGNAL_LEVELS - 1).build();
+ .setLevel(mMaxSignalLevel).build();
AccessPoint firstName = new TestAccessPointBuilder(mContext).setSsid("a").build();
AccessPoint lastname = new TestAccessPointBuilder(mContext).setSsid("z").build();
@@ -520,6 +521,8 @@
when(packageManager.getApplicationInfoAsUser(eq(appPackageName), anyInt(), anyInt()))
.thenReturn(applicationInfo);
when(applicationInfo.loadLabel(packageManager)).thenReturn(appLabel);
+ when(context.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ when(mMockWifiManager.calculateSignalLevel(rssi)).thenReturn(4);
NetworkInfo networkInfo =
new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
@@ -636,14 +639,14 @@
public void testBuilder_setLevel() {
AccessPoint testAp;
- for (int i = 0; i < AccessPoint.SIGNAL_LEVELS; i++) {
+ for (int i = 0; i <= mMaxSignalLevel; i++) {
testAp = new TestAccessPointBuilder(mContext).setLevel(i).build();
assertThat(testAp.getLevel()).isEqualTo(i);
}
// numbers larger than the max level should be set to max
- testAp = new TestAccessPointBuilder(mContext).setLevel(AccessPoint.SIGNAL_LEVELS).build();
- assertThat(testAp.getLevel()).isEqualTo(AccessPoint.SIGNAL_LEVELS - 1);
+ testAp = new TestAccessPointBuilder(mContext).setLevel(mMaxSignalLevel + 1).build();
+ assertThat(testAp.getLevel()).isEqualTo(mMaxSignalLevel);
// numbers less than 0 should give level 0
testAp = new TestAccessPointBuilder(mContext).setLevel(-100).build();
@@ -653,7 +656,7 @@
@Test
public void testBuilder_settingReachableAfterLevelDoesNotAffectLevel() {
int level = 1;
- assertThat(level).isLessThan(AccessPoint.SIGNAL_LEVELS - 1);
+ assertThat(level).isLessThan(mMaxSignalLevel);
AccessPoint testAp =
new TestAccessPointBuilder(mContext).setLevel(level).setReachable(true).build();
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER
new file mode 100644
index 0000000..5c2a7b8
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER
@@ -0,0 +1,4 @@
+# People who can approve changes for submission
+arcwang@google.com
+govenliu@google.com
+qal@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index 605c861..340a6c7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -59,8 +59,9 @@
allKeys.add(CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
allKeys.add(CategoryKey.CATEGORY_GESTURES);
allKeys.add(CategoryKey.CATEGORY_NIGHT_DISPLAY);
+ allKeys.add(CategoryKey.CATEGORY_SMART_BATTERY_SETTINGS);
// DO NOT REMOVE ANYTHING ABOVE
- assertThat(allKeys.size()).isEqualTo(18);
+ assertThat(allKeys.size()).isEqualTo(19);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 894aa78..c780a64 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -127,7 +127,7 @@
mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.connectDevice(device);
- verify(cachedDevice).connect(true);
+ verify(cachedDevice).connect();
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
index d98f50b..b0a647e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
@@ -31,6 +31,7 @@
import android.telephony.TelephonyManager;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -71,12 +72,13 @@
when(mTelephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID);
when(mTelephonyManager.getSubscriberId(SUB_ID_2)).thenReturn(SUBSCRIBER_ID_2);
when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
- when(mSubscriptionManager.isActiveSubId(anyInt())).thenReturn(true);
+ when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true);
}
@Test
+ @Ignore
public void getMobileTemplate_infoNull_returnMobileAll() {
- when(mSubscriptionManager.isActiveSubId(SUB_ID)).thenReturn(false);
+ when(mSubscriptionManager.isActiveSubscriptionId(SUB_ID)).thenReturn(false);
final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
@@ -84,6 +86,7 @@
}
@Test
+ @Ignore
public void getMobileTemplate_groupUuidNull_returnMobileAll() {
when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
when(mInfo1.getGroupUuid()).thenReturn(null);
@@ -96,6 +99,7 @@
}
@Test
+ @Ignore
public void getMobileTemplate_groupUuidExist_returnMobileMerged() {
when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
index 21aa526..2bd20a9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
@@ -72,7 +72,7 @@
assertThat(AccessPointPreference.buildContentDescription(
RuntimeEnvironment.application, pref, ap))
- .isEqualTo("ssid,connected,Wifi signal full.,Secure network");
+ .isEqualTo("ssid,connected,Wifi disconnected.,Secure network");
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER
new file mode 100644
index 0000000..5c2a7b8
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER
@@ -0,0 +1,4 @@
+# People who can approve changes for submission
+arcwang@google.com
+govenliu@google.com
+qal@google.com
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 72923a3..dd94d2e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -141,9 +141,6 @@
VALIDATORS.put(Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.DEVICE_DEMO_MODE, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Global.WIFI_PNO_RECENCY_SORTING_ENABLED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Global.WIFI_LINK_PROBING_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.AWARE_ALLOWED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 5));
VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 375a650..266bfe0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -2554,7 +2554,7 @@
for (int phoneId = 0; phoneId < phoneCount; phoneId++) {
int mode = defaultNetworks.size() <= phoneId
|| defaultNetworks.get(phoneId) == null
- ? RILConstants.PREFERRED_NETWORK_MODE : defaultNetworks.get(phoneId);
+ ? TelephonyManager.DEFAULT_PREFERRED_NETWORK_MODE : defaultNetworks.get(phoneId);
if (phoneId > 0) val.append(',');
val.append(mode);
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index aa36dca..449a135 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1070,9 +1070,6 @@
Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
GlobalSettingsProto.Network.RECOMMENDATIONS_PACKAGE);
dumpSetting(s, p,
- Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
- GlobalSettingsProto.Network.RECOMMENDATION_REQUEST_TIMEOUT_MS);
- dumpSetting(s, p,
Settings.Global.NETWORK_WATCHLIST_ENABLED,
GlobalSettingsProto.Network.WATCHLIST_ENABLED);
dumpSetting(s, p,
@@ -1587,9 +1584,6 @@
Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
GlobalSettingsProto.Wifi.WATCHDOG_POOR_NETWORK_TEST_ENABLED);
dumpSetting(s, p,
- Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
- GlobalSettingsProto.Wifi.SUSPEND_OPTIMIZATIONS_ENABLED);
- dumpSetting(s, p,
Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED,
GlobalSettingsProto.Wifi.VERBOSE_LOGGING_ENABLED);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1e0c1d8..4443524 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -20,6 +20,7 @@
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
import android.Manifest;
@@ -3304,7 +3305,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 185;
+ private static final int SETTINGS_VERSION = 186;
private final int mUserId;
@@ -4547,6 +4548,32 @@
currentVersion = 185;
}
+ if (currentVersion == 185) {
+ // Deprecate ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, and migrate it
+ // to ACCESSIBILITY_BUTTON_TARGET_COMPONENT.
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting magnifyNavbarEnabled = secureSettings.getSettingLocked(
+ Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+ if ("1".equals(magnifyNavbarEnabled.getValue())) {
+ secureSettings.insertSettingLocked(
+ Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER,
+ null /* tag */, false /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ } else {
+ // Clear a11y button targets list setting. A11yManagerService will end up
+ // adding all legacy enabled services that want the button to the list, so
+ // there's no need to keep tracking them.
+ secureSettings.insertSettingLocked(
+ Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ null, null /* tag */, false /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ secureSettings.deleteSettingLocked(
+ Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+ currentVersion = 186;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 7278225..6ea2c74 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -369,7 +369,6 @@
Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
Settings.Global.NETWORK_PREFERENCE,
Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
- Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
Settings.Global.NETWORK_SCORER_APP,
Settings.Global.NETWORK_SCORING_PROVISIONED,
Settings.Global.NETWORK_SCORING_UI_ENABLED,
@@ -523,8 +522,6 @@
Settings.Global.WIFI_BADGING_THRESHOLDS,
Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
Settings.Global.WIFI_COUNTRY_CODE,
- Settings.Global.WIFI_DATA_STALL_MIN_TX_BAD,
- Settings.Global.WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX,
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON,
Settings.Global.WIFI_DISPLAY_ON,
@@ -534,11 +531,6 @@
Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
Settings.Global.WIFI_FREQUENCY_BAND,
Settings.Global.WIFI_IDLE_MS,
- Settings.Global.WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED,
- Settings.Global.WIFI_LINK_SPEED_METRICS_ENABLED,
- Settings.Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED,
- Settings.Global.WIFI_PNO_RECENCY_SORTING_ENABLED,
- Settings.Global.WIFI_LINK_PROBING_ENABLED,
Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
Settings.Global.WIFI_NETWORK_SHOW_RSSI,
@@ -547,7 +539,6 @@
Settings.Global.WIFI_ON,
Settings.Global.WIFI_P2P_DEVICE_NAME,
Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET,
- Settings.Global.WIFI_RTT_BACKGROUND_EXEC_GAP_MS,
Settings.Global.WIFI_SAVED_STATE,
Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
@@ -555,7 +546,6 @@
Settings.Global.WIFI_SCORE_PARAMS,
Settings.Global.WIFI_SLEEP_POLICY,
Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
- Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED,
Settings.Global.WIFI_WATCHDOG_ON,
Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON,
@@ -734,7 +724,8 @@
Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
Settings.Secure.FACE_UNLOCK_RE_ENROLL,
Settings.Secure.TAP_GESTURE,
- Settings.Secure.WINDOW_MAGNIFICATION);
+ Settings.Secure.WINDOW_MAGNIFICATION,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER);
@Test
public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 347d6c2..1c63efc 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -194,6 +194,9 @@
<uses-permission android:name="android.permission.MANAGE_APPOPS" />
+ <!-- Permission required for storage tests - FuseDaemonHostTest -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
<!-- Permission needed to run network tests in CTS -->
<uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" />
<!-- Permission needed to test tcp keepalive offload. -->
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2a1e74e..0bcadce 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -70,7 +70,7 @@
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
- <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
@@ -211,6 +211,7 @@
<!-- accessibility -->
<uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" />
+ <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" />
<!-- to control accessibility volume -->
<uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
@@ -307,7 +308,8 @@
</receiver>
<activity android:name=".screenrecord.ScreenRecordDialog"
- android:theme="@style/ScreenRecord" />
+ android:theme="@style/ScreenRecord"
+ android:excludeFromRecents="true" />
<service android:name=".screenrecord.RecordingService" />
<receiver android:name=".SysuiRestartReceiver"
@@ -636,6 +638,23 @@
</intent-filter>
</activity>
+ <activity android:name=".controls.management.ControlsProviderSelectorActivity"
+ android:label="Controls Providers"
+ android:theme="@style/Theme.SystemUI"
+ android:exported="true"
+ android:excludeFromRecents="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:visibleToInstantApps="true">
+ </activity>
+
+ <activity android:name=".controls.management.ControlsFavoritingActivity"
+ android:parentActivityName=".controls.management.ControlsProviderSelectorActivity"
+ android:theme="@style/Theme.SystemUI"
+ android:excludeFromRecents="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:visibleToInstantApps="true">
+ </activity>
+
<!-- Doze with notifications, run in main sysui process for every user -->
<service
android:name=".doze.DozeService"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 8db0d02..02c4c5e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -46,6 +46,8 @@
*/
public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
+ public void snooze(StatusBarNotification sbn, int hours);
+
public float getMinDismissVelocity();
public boolean isDismissGesture(MotionEvent ev);
diff --git a/packages/SystemUI/res/drawable/ic_add_to_home.xml b/packages/SystemUI/res/drawable/ic_add_to_home.xml
new file mode 100644
index 0000000..2b40c62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_add_to_home.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,1.01L8,1c-1.1,0 -2,0.9 -2,2v3h2V5h10v14H8v-1H6v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM10,15h2V8H5v2h3.59L3,15.59 4.41,17 10,11.41V15z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_demote_conversation.xml b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
new file mode 100644
index 0000000..5a88160
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,2L4.83,2l2,2L20,4v12h-1.17l1.87,1.87c0.75,-0.29 1.3,-1.02 1.3,-1.87L22,4c0,-1.1 -0.9,-2 -2,-2zM6,12h2v2L6,14zM18,11L18,9h-6.17l2,2zM18,6h-8v1.17l0.83,0.83L18,8zM0.69,3.51l1.32,1.32L2,22l4,-4h9.17l5.31,5.31 1.41,-1.41L2.1,2.1 0.69,3.51zM6,16h-0.83l-0.59,0.59 -0.58,0.58L4,6.83l2,2L6,11h2.17l5,5L6,16z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
new file mode 100644
index 0000000..687c9c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,10.48L18,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4.48l4,3.98v-11l-4,3.98zM16,9.69L16,18L4,18L4,6h12v3.69z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_snooze.xml b/packages/SystemUI/res/drawable/ic_snooze.xml
index b0b03a9..f4c074d 100644
--- a/packages/SystemUI/res/drawable/ic_snooze.xml
+++ b/packages/SystemUI/res/drawable/ic_snooze.xml
@@ -1,12 +1,24 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
- android:fillColor="#757575"/>
- <path
- android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
- android:fillColor="#757575"/>
+ android:fillColor="@android:color/white"
+ android:pathData="M9,11h3.63L9,15.2L9,17h6v-2h-3.63L15,10.8L15,9L9,9v2zM16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM3.336,7.19l-1.28,-1.536L6.662,1.81l1.28,1.536zM12,6c3.86,0 7,3.14 7,7s-3.14,7 -7,7 -7,-3.14 -7,-7 3.14,-7 7,-7m0,-2c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml
new file mode 100644
index 0000000..4a731b3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml
new file mode 100644
index 0000000..9ede40b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star_border.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
index bc12338..0f9deaa 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
@@ -57,15 +57,5 @@
android:textColor="@color/global_actions_text"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
-
- <TextView
- android:visibility="gone"
- android:id="@*android:id/status"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:textColor="@color/global_actions_text"
- android:textAppearance="?android:attr/textAppearanceSmall"
- />
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/app_item.xml b/packages/SystemUI/res/layout/app_item.xml
new file mode 100644
index 0000000..83e7887
--- /dev/null
+++ b/packages/SystemUI/res/layout/app_item.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="start|center_vertical"
+ android:minWidth="56dp"
+ android:orientation="horizontal"
+ android:paddingEnd="8dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="@dimen/app_icon_size"
+ android:layout_height="@dimen/app_icon_size"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical|end"
+ android:minWidth="64dp"
+ android:orientation="vertical"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/control_item.xml b/packages/SystemUI/res/layout/control_item.xml
new file mode 100644
index 0000000..85701aa
--- /dev/null
+++ b/packages/SystemUI/res/layout/control_item.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:padding="15dp"
+ android:clickable="true"
+ android:focusable="true">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <TextView
+ android:id="@+id/status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:paddingLeft="3dp"
+ app:layout_constraintBottom_toBottomOf="@+id/icon"
+ app:layout_constraintStart_toEndOf="@+id/icon" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ app:layout_constraintBottom_toTopOf="@+id/subtitle"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/icon" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="16sp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <CheckBox
+ android:id="@+id/favorite"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml
index 4404c874..31c7cbf 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml
@@ -56,15 +56,5 @@
android:textColor="@color/global_actions_text"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
-
- <TextView
- android:visibility="gone"
- android:id="@*android:id/status"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:textColor="@color/global_actions_text"
- android:textAppearance="?android:attr/textAppearanceSmall"
- />
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
new file mode 100644
index 0000000..50aa212
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- RelativeLayouts have an issue enforcing minimum heights, so just
+ work around this for now with LinearLayouts. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingTop="@dimen/global_actions_grid_item_vertical_margin"
+ android:paddingBottom="@dimen/global_actions_grid_item_vertical_margin"
+ android:paddingLeft="@dimen/global_actions_grid_item_side_margin"
+ android:paddingRight="@dimen/global_actions_grid_item_side_margin"
+ android:layout_marginRight="3dp"
+ android:layout_marginLeft="3dp"
+ android:background="@drawable/rounded_bg_full">
+ <LinearLayout
+ android:layout_width="@dimen/global_actions_grid_item_width"
+ android:layout_height="@dimen/global_actions_grid_item_height"
+ android:gravity="top|center_horizontal"
+ android:orientation="vertical">
+ <ImageView
+ android:id="@*android:id/icon"
+ android:layout_width="@dimen/global_actions_grid_item_icon_width"
+ android:layout_height="@dimen/global_actions_grid_item_icon_height"
+ android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin"
+ android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin"
+ android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin"
+ android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin"
+ android:scaleType="centerInside"
+ android:tint="@color/global_actions_text" />
+
+ <TextView
+ android:id="@*android:id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:singleLine="true"
+ android:gravity="center"
+ android:textSize="12dp"
+ android:textColor="@color/global_actions_text"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <TextView
+ android:visibility="gone"
+ android:id="@*android:id/status"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="@color/global_actions_text"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
new file mode 100644
index 0000000..4cfb47e
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/global_actions_grid_root"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset">
+
+ <com.android.systemui.globalactions.GlobalActionsFlatLayout
+ android:id="@id/global_actions_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:theme="@style/qs_theme"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ android:gravity="top | center_horizontal"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset"
+ android:layout_marginTop="@dimen/global_actions_top_margin"
+ android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset">
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layoutDirection="ltr"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin">
+ <!-- For separated items-->
+ <LinearLayout
+ android:id="@+id/separated_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
+ android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+ android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingTop="@dimen/global_actions_grid_vertical_padding"
+ android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:translationZ="@dimen/global_actions_translate"
+ />
+ <!-- Grid of action items -->
+ <com.android.systemui.globalactions.ListGridLayout
+ android:id="@android:id/list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="right"
+ android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+ android:translationZ="@dimen/global_actions_translate"
+ android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingTop="@dimen/global_actions_grid_vertical_padding"
+ android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+ >
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layoutDirection="locale"
+ />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layoutDirection="locale"
+ />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layoutDirection="locale"
+ />
+ </com.android.systemui.globalactions.ListGridLayout>
+ </LinearLayout>
+ </com.android.systemui.globalactions.GlobalActionsFlatLayout>
+
+ <LinearLayout
+ android:id="@+id/global_actions_panel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/global_actions_view">
+
+ <FrameLayout
+ android:translationY="@dimen/global_actions_plugin_offset"
+ android:id="@+id/global_actions_panel_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:translationY="@dimen/global_actions_plugin_offset"
+ android:id="@+id/global_actions_controls"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/global_actions_panel">
+ <TextView
+ android:text="Home"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:gravity="center"
+ android:textSize="25dp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@*android:string/config_headlineFontFamily" />
+ <LinearLayout
+ android:id="@+id/global_actions_controls_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+ </LinearLayout>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</ScrollView>
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 6ac9da4..995cb7d 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- Copyright (C) 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
index 6b42400..366abaa 100644
--- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
@@ -14,12 +14,27 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/global_screenshot_action_chip"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginHorizontal="@dimen/screenshot_action_chip_margin_horizontal"
- android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
- android:paddingHorizontal="@dimen/screenshot_action_chip_padding_horizontal"
- android:background="@drawable/action_chip_background"
- android:textColor="@color/global_screenshot_button_text"/>
+<com.android.systemui.screenshot.ScreenshotActionChip
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/global_screenshot_action_chip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/screenshot_action_chip_margin_horizontal"
+ android:layout_gravity="center"
+ android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
+ android:background="@drawable/action_chip_background"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/screenshot_action_chip_icon"
+ android:layout_width="@dimen/screenshot_action_chip_icon_size"
+ android:layout_height="@dimen/screenshot_action_chip_icon_size"
+ android:layout_marginStart="@dimen/screenshot_action_chip_padding_start"
+ android:layout_marginEnd="@dimen/screenshot_action_chip_padding_middle"/>
+ <TextView
+ android:id="@+id/screenshot_action_chip_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end"
+ android:textSize="@dimen/screenshot_action_chip_text_size"
+ android:textColor="@color/global_screenshot_button_text"/>
+</com.android.systemui.screenshot.ScreenshotActionChip>
diff --git a/packages/SystemUI/res/layout/global_screenshot_legacy.xml b/packages/SystemUI/res/layout/global_screenshot_legacy.xml
new file mode 100644
index 0000000..791c7ea
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_screenshot_legacy.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ImageView android:id="@+id/global_screenshot_legacy_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@android:color/black"
+ android:visibility="gone" />
+ <ImageView android:id="@+id/global_screenshot_legacy"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/screenshot_panel"
+ android:visibility="gone"
+ android:adjustViewBounds="true" />
+ <ImageView android:id="@+id/global_screenshot_legacy_flash"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@android:color/white"
+ android:visibility="gone" />
+ <com.android.systemui.screenshot.ScreenshotSelectorView
+ android:id="@+id/global_screenshot_legacy_selector"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:pointerIcon="crosshair"/>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
new file mode 100644
index 0000000..8749b1a
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.systemui.statusbar.notification.row.NotificationConversationInfo
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/notification_guts"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:clipChildren="false"
+ android:clipToPadding="true"
+ android:orientation="vertical"
+ android:background="@color/notification_material_background_color"
+ android:paddingStart="@*android:dimen/notification_content_margin_start">
+
+ <!-- Package Info -->
+ <RelativeLayout
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_guts_conversation_header_height"
+ android:gravity="center_vertical"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+ <ImageView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/notification_guts_conversation_icon_size"
+ android:layout_height="@dimen/notification_guts_conversation_icon_size"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="6dp" />
+ <LinearLayout
+ android:id="@+id/names"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_guts_conversation_icon_size"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ android:layout_toEndOf="@id/conversation_icon">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/parent_channel_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/pkg_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.NotificationImportanceChannelGroup"
+ android:ellipsize="end"
+ android:maxLines="1"/>
+ <TextView
+ android:id="@+id/group_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/group_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/pkg_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:layout_toEndOf="@id/name"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/delegate_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:ellipsize="end"
+ android:text="@string/notification_delegate_header"
+ android:layout_toEndOf="@id/pkg_divider"
+ android:maxLines="1" />
+
+ <!-- end aligned fields -->
+ <ImageButton
+ android:id="@+id/demote"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/demote"
+ android:src="@drawable/ic_demote_conversation"
+ android:layout_toStartOf="@id/app_settings"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ <!-- Optional link to app. Only appears if the channel is not disabled and the app
+asked for it -->
+ <ImageButton
+ android:id="@+id/app_settings"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:visibility="gone"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/notification_app_settings"
+ android:src="@drawable/ic_info"
+ android:layout_toStartOf="@id/info"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ <ImageButton
+ android:id="@+id/info"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/notification_more_settings"
+ android:src="@drawable/ic_settings"
+ android:layout_alignParentEnd="true"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ </RelativeLayout>
+
+ <LinearLayout
+ android:id="@+id/actions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/bubble"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_favorite"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_create_bubble"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/home"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_home_screen"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_add_to_home"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/fave"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:gravity="left|center_vertical"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/snooze"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_menu_snooze_action"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_snooze"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/mute"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_mute"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_notifications_silence"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ </LinearLayout>
+
+</com.android.systemui.statusbar.notification.row.NotificationConversationInfo>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
deleted file mode 100644
index 3d63b7d..0000000
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:gravity="top"
- android:orientation="vertical"
- android:padding="@dimen/global_actions_padding"
- android:background="@drawable/rounded_bg_full">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <CheckBox
- android:id="@+id/checkbox_mic"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@string/screenrecord_mic_label"/>
- <CheckBox
- android:id="@+id/checkbox_taps"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@string/screenrecord_taps_label"/>
- <Button
- android:id="@+id/record_button"
- android:layout_width="match_parent"
- android:layout_height="50dp"
- android:text="@string/screenrecord_start_label"
- />
- </LinearLayout>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 640f31b..8963157 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -109,7 +109,7 @@
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
- wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
+ wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord
</string>
<!-- The minimum number of tiles to display in QuickSettings -->
@@ -117,7 +117,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls,screenrecord
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index da0323a..53df025 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -213,6 +213,11 @@
<!-- The horizontal space around the buttons in the inline settings -->
<dimen name="notification_guts_button_horizontal_spacing">8dp</dimen>
+ <dimen name="notification_guts_conversation_header_height">84dp</dimen>
+ <dimen name="notification_guts_conversation_icon_size">52dp</dimen>
+ <dimen name="notification_guts_conversation_action_height">56dp</dimen>
+ <dimen name="notification_guts_conversation_action_text_padding_start">32dp</dimen>
+
<!-- The height of the header in inline settings -->
<dimen name="notification_guts_header_height">24dp</dimen>
@@ -288,14 +293,20 @@
<!-- Dimensions related to screenshots -->
<!-- The padding on the global screenshot background image -->
+ <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen>
<dimen name="global_screenshot_bg_padding">20dp</dimen>
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
- <dimen name="screenshot_action_container_padding">20dp</dimen>
+ <dimen name="screenshot_action_container_padding">10dp</dimen>
<!-- Radius of the chip background on global screenshot actions -->
<dimen name="screenshot_button_corner_radius">20dp</dimen>
- <dimen name="screenshot_action_chip_margin_horizontal">10dp</dimen>
+ <dimen name="screenshot_action_chip_margin_horizontal">4dp</dimen>
<dimen name="screenshot_action_chip_padding_vertical">10dp</dimen>
- <dimen name="screenshot_action_chip_padding_horizontal">15dp</dimen>
+ <dimen name="screenshot_action_chip_icon_size">20dp</dimen>
+ <dimen name="screenshot_action_chip_padding_start">4dp</dimen>
+ <!-- Padding between icon and text -->
+ <dimen name="screenshot_action_chip_padding_middle">8dp</dimen>
+ <dimen name="screenshot_action_chip_padding_end">12dp</dimen>
+ <dimen name="screenshot_action_chip_text_size">14sp</dimen>
<!-- The width of the view containing navigation buttons -->
@@ -950,6 +961,9 @@
<dimen name="cell_overlay_padding">18dp</dimen>
<!-- Global actions power menu -->
+ <dimen name="global_actions_top_margin">12dp</dimen>
+ <dimen name="global_actions_plugin_offset">-145dp</dimen>
+
<dimen name="global_actions_panel_width">120dp</dimen>
<dimen name="global_actions_padding">12dp</dimen>
<dimen name="global_actions_translate">9dp</dimen>
@@ -1151,4 +1165,5 @@
<dimen name="magnifier_up_down_controls_width">45dp</dimen>
<dimen name="magnifier_up_down_controls_height">40dp</dimen>
+ <dimen name="app_icon_size">32dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4f532b7..9129938 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -928,6 +928,13 @@
<!-- QuickSettings: NFC (on) [CHAR LIMIT=NONE] -->
<string name="quick_settings_nfc_on">NFC is enabled</string>
+ <!-- QuickSettings: Screen record tile [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_screen_record_label">Screen Record</string>
+ <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] -->
+ <string name="quick_settings_screen_record_start">Start</string>
+ <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
+ <string name="quick_settings_screen_record_stop">Stop</string>
+
<!-- Recents: Text that shows above the navigation bar after launching a few apps. [CHAR LIMIT=NONE] -->
<string name="recents_swipe_up_onboarding">Swipe up to switch apps</string>
<!-- Recents: Text that shows above the navigation bar after launching several apps. [CHAR LIMIT=NONE] -->
@@ -1791,6 +1798,29 @@
<string name="notification_done">Done</string>
<!-- Notification: inline controls: undo block button -->
<string name="inline_undo">Undo</string>
+ <!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification -->
+ <string name="demote">Mark this notification as not a conversation</string>
+
+ <!-- [CHAR LIMIT=100] Mark this conversation as a favorite -->
+ <string name="notification_conversation_favorite">Favorite</string>
+
+ <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite -->
+ <string name="notification_conversation_unfavorite">Unfavorite</string>
+
+ <!-- [CHAR LIMIT=100] Mute this conversation -->
+ <string name="notification_conversation_mute">Mute</string>
+
+ <!-- [CHAR LIMIT=100] Umute this conversation -->
+ <string name="notification_conversation_unmute">Unmute</string>
+
+ <!-- [CHAR LIMIT=100] Show notification as bubble -->
+ <string name="notification_conversation_bubble">Show as bubble</string>
+
+ <!-- [CHAR LIMIT=100] Turn off bubbles for notification -->
+ <string name="notification_conversation_unbubble">Turn off bubbles</string>
+
+ <!-- [CHAR LIMIT=100] Add this conversation to home screen -->
+ <string name="notification_conversation_home_screen">Add to home screen</string>
<!-- Notification: Menu row: Content description for menu items. [CHAR LIMIT=NONE] -->
<string name="notification_menu_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> <xliff:g id="menu_description" example="notification controls">%2$s</xliff:g></string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index df0dc46..e475ef1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -387,8 +387,8 @@
* Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
*/
public void refresh() {
- mClockView.refresh();
- mClockViewBold.refresh();
+ mClockView.refreshTime();
+ mClockViewBold.refreshTime();
if (mClockPlugin != null) {
mClockPlugin.onTimeTick();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
index eba2400..99e122e 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
@@ -188,7 +188,7 @@
public void onTimeTick() {
mAnalogClock.onTimeChanged();
mBigClockView.onTimeChanged();
- mLockClock.refresh();
+ mLockClock.refreshTime();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
index 3a2fbe5..fac923c01 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -195,7 +195,7 @@
public void onTimeTick() {
mAnalogClock.onTimeChanged();
mView.onTimeChanged();
- mLockClock.refresh();
+ mLockClock.refreshTime();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index bbe972d..d149591 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -56,6 +56,7 @@
import com.android.systemui.power.PowerUI;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
+import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
@@ -323,6 +324,7 @@
@Inject Lazy<DisplayWindowController> mDisplayWindowController;
@Inject Lazy<SystemWindows> mSystemWindows;
@Inject Lazy<DisplayImeController> mDisplayImeController;
+ @Inject Lazy<RecordingController> mRecordingController;
@Inject
public Dependency() {
@@ -519,6 +521,8 @@
// Dependency problem.
mProviders.put(AutoHideController.class, mAutoHideController::get);
+ mProviders.put(RecordingController.class, mRecordingController::get);
+
sDependency = this;
}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index 8105faa..eab9706 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -27,9 +27,9 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -49,7 +49,7 @@
public ForegroundServiceNotificationListener(Context context,
ForegroundServiceController foregroundServiceController,
NotificationEntryManager notificationEntryManager,
- NotifCollection notifCollection) {
+ NotifPipeline notifPipeline) {
mContext = context;
mForegroundServiceController = foregroundServiceController;
@@ -77,7 +77,7 @@
});
mEntryManager.addNotificationLifetimeExtender(new ForegroundServiceLifetimeExtender());
- notifCollection.addCollectionListener(new NotifCollectionListener() {
+ notifPipeline.addCollectionListener(new NotifCollectionListener() {
@Override
public void onEntryAdded(NotificationEntry entry) {
addNotification(entry, entry.getImportance());
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index b083123..23d6458 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -18,7 +18,6 @@
import android.app.AppOpsManager;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -64,7 +63,6 @@
private H mBGHandler;
private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>();
- private final PermissionFlagsCache mFlagsCache;
private boolean mListening;
@GuardedBy("mActiveItems")
@@ -81,17 +79,10 @@
};
@Inject
- public AppOpsControllerImpl(Context context, @Background Looper bgLooper,
- DumpController dumpController) {
- this(context, bgLooper, new PermissionFlagsCache(context), dumpController);
- }
-
- @VisibleForTesting
- protected AppOpsControllerImpl(Context context, Looper bgLooper, PermissionFlagsCache cache,
- DumpController dumpController) {
+ public AppOpsControllerImpl(Context context,
+ @Background Looper bgLooper, DumpController dumpController) {
mContext = context;
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mFlagsCache = cache;
mBGHandler = new H(bgLooper);
final int numOps = OPS.length;
for (int i = 0; i < numOps; i++) {
@@ -209,7 +200,14 @@
mNotedItems.remove(item);
if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
}
- notifySuscribers(code, uid, packageName, false);
+ boolean active;
+ // Check if the item is also active
+ synchronized (mActiveItems) {
+ active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
+ }
+ if (!active) {
+ notifySuscribers(code, uid, packageName, false);
+ }
}
private boolean addNoted(int code, int uid, String packageName) {
@@ -224,64 +222,13 @@
createdNew = true;
}
}
+ // We should keep this so we make sure it cannot time out.
+ mBGHandler.removeCallbacksAndMessages(item);
mBGHandler.scheduleRemoval(item, NOTED_OP_TIME_DELAY_MS);
return createdNew;
}
/**
- * Does the app-op code refer to a user sensitive permission for the specified user id
- * and package. Only user sensitive permission should be shown to the user by default.
- *
- * @param appOpCode The code of the app-op.
- * @param uid The uid of the user.
- * @param packageName The name of the package.
- *
- * @return {@code true} iff the app-op item is user sensitive
- */
- private boolean isUserSensitive(int appOpCode, int uid, String packageName) {
- String permission = AppOpsManager.opToPermission(appOpCode);
- if (permission == null) {
- return false;
- }
- int permFlags = mFlagsCache.getPermissionFlags(permission,
- packageName, UserHandle.getUserHandleForUid(uid));
- return (permFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
- }
-
- /**
- * Does the app-op item refer to an operation that should be shown to the user.
- * Only specficic ops (like SYSTEM_ALERT_WINDOW) or ops that refer to user sensitive
- * permission should be shown to the user by default.
- *
- * @param item The item
- *
- * @return {@code true} iff the app-op item should be shown to the user
- */
- private boolean isUserVisible(AppOpItem item) {
- return isUserVisible(item.getCode(), item.getUid(), item.getPackageName());
- }
-
-
- /**
- * Does the app-op, uid and package name, refer to an operation that should be shown to the
- * user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or
- * ops that refer to user sensitive permission should be shown to the user by default.
- *
- * @param item The item
- *
- * @return {@code true} iff the app-op for should be shown to the user
- */
- private boolean isUserVisible(int appOpCode, int uid, String packageName) {
- // currently OP_SYSTEM_ALERT_WINDOW does not correspond to a platform permission
- // which may be user senstive, so for now always show it to the user.
- if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW) {
- return true;
- }
-
- return isUserSensitive(appOpCode, uid, packageName);
- }
-
- /**
* Returns a copy of the list containing all the active AppOps that the controller tracks.
*
* @return List of active AppOps information
@@ -304,8 +251,8 @@
final int numActiveItems = mActiveItems.size();
for (int i = 0; i < numActiveItems; i++) {
AppOpItem item = mActiveItems.get(i);
- if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
- && isUserVisible(item)) {
+ if ((userId == UserHandle.USER_ALL
+ || UserHandle.getUserId(item.getUid()) == userId)) {
list.add(item);
}
}
@@ -314,8 +261,8 @@
final int numNotedItems = mNotedItems.size();
for (int i = 0; i < numNotedItems; i++) {
AppOpItem item = mNotedItems.get(i);
- if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
- && isUserVisible(item)) {
+ if ((userId == UserHandle.USER_ALL
+ || UserHandle.getUserId(item.getUid()) == userId)) {
list.add(item);
}
}
@@ -325,7 +272,21 @@
@Override
public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
- if (updateActives(code, uid, packageName, active)) {
+ if (DEBUG) {
+ Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s", code, uid, packageName,
+ Boolean.toString(active)));
+ }
+ boolean activeChanged = updateActives(code, uid, packageName, active);
+ if (!activeChanged) return; // early return
+ // Check if the item is also noted, in that case, there's no update.
+ boolean alsoNoted;
+ synchronized (mNotedItems) {
+ alsoNoted = getAppOpItemLocked(mNotedItems, code, uid, packageName) != null;
+ }
+ // If active is true, we only send the update if the op is not actively noted (already true)
+ // If active is false, we only send the update if the op is not actively noted (prevent
+ // early removal)
+ if (!alsoNoted) {
mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active));
}
}
@@ -333,17 +294,23 @@
@Override
public void onOpNoted(int code, int uid, String packageName, int result) {
if (DEBUG) {
- Log.w(TAG, "Op: " + code + " with result " + AppOpsManager.MODE_NAMES[result]);
+ Log.w(TAG, "Noted op: " + code + " with result "
+ + AppOpsManager.MODE_NAMES[result] + " for package " + packageName);
}
if (result != AppOpsManager.MODE_ALLOWED) return;
- if (addNoted(code, uid, packageName)) {
+ boolean notedAdded = addNoted(code, uid, packageName);
+ if (!notedAdded) return; // early return
+ boolean alsoActive;
+ synchronized (mActiveItems) {
+ alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
+ }
+ if (!alsoActive) {
mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true));
}
}
private void notifySuscribers(int code, int uid, String packageName, boolean active) {
- if (mCallbacksByCode.containsKey(code)
- && isUserVisible(code, uid, packageName)) {
+ if (mCallbacksByCode.containsKey(code)) {
if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName);
for (Callback cb: mCallbacksByCode.get(code)) {
cb.onActiveStateChanged(code, uid, packageName, active);
@@ -368,7 +335,7 @@
}
- protected final class H extends Handler {
+ protected class H extends Handler {
H(Looper looper) {
super(looper);
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
deleted file mode 100644
index f02c7af..0000000
--- a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.appops
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.UserHandle
-import android.util.ArrayMap
-import com.android.internal.annotations.VisibleForTesting
-
-private data class PermissionFlag(val flag: Int, val timestamp: Long)
-
-private data class PermissionFlagKey(
- val permission: String,
- val packageName: String,
- val user: UserHandle
-)
-
-internal const val CACHE_EXPIRATION = 10000L
-
-/**
- * Cache for PackageManager's PermissionFlags.
- *
- * Flags older than [CACHE_EXPIRATION] will be retrieved again.
- */
-internal open class PermissionFlagsCache(context: Context) {
- private val packageManager = context.packageManager
- private val permissionFlagsCache = ArrayMap<PermissionFlagKey, PermissionFlag>()
-
- /**
- * Retrieve permission flags from cache or PackageManager. There parameters will be passed
- * directly to [PackageManager].
- *
- * Calls to this method should be done from a background thread.
- */
- fun getPermissionFlags(permission: String, packageName: String, user: UserHandle): Int {
- val key = PermissionFlagKey(permission, packageName, user)
- val now = getCurrentTime()
- val value = permissionFlagsCache.getOrPut(key) {
- PermissionFlag(getFlags(key), now)
- }
- if (now - value.timestamp > CACHE_EXPIRATION) {
- val newValue = PermissionFlag(getFlags(key), now)
- permissionFlagsCache.put(key, newValue)
- return newValue.flag
- } else {
- return value.flag
- }
- }
-
- private fun getFlags(key: PermissionFlagKey) =
- packageManager.getPermissionFlags(key.permission, key.packageName, key.user)
-
- @VisibleForTesting
- protected open fun getCurrentTime() = System.currentTimeMillis()
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 97224f1..ccbbb24 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -111,7 +111,10 @@
}
private final Context mContext;
+ /** Bubbles that are actively in the stack. */
private final List<Bubble> mBubbles;
+ /** Bubbles that are being loaded but haven't been added to the stack just yet. */
+ private final List<Bubble> mPendingBubbles;
private Bubble mSelectedBubble;
private boolean mExpanded;
private final int mMaxBubbles;
@@ -143,6 +146,7 @@
public BubbleData(Context context) {
mContext = context;
mBubbles = new ArrayList<>();
+ mPendingBubbles = new ArrayList<>();
mStateChange = new Update(mBubbles);
mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
}
@@ -188,7 +192,15 @@
Bubble getOrCreateBubble(NotificationEntry entry) {
Bubble bubble = getBubbleWithKey(entry.getKey());
if (bubble == null) {
+ // Check for it in pending
+ for (int i = 0; i < mPendingBubbles.size(); i++) {
+ Bubble b = mPendingBubbles.get(i);
+ if (b.getKey().equals(entry.getKey())) {
+ return b;
+ }
+ }
bubble = new Bubble(entry);
+ mPendingBubbles.add(bubble);
} else {
bubble.setEntry(entry);
}
@@ -204,7 +216,7 @@
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "notificationEntryUpdated: " + bubble);
}
-
+ mPendingBubbles.remove(bubble); // No longer pending once we're here
Bubble prevBubble = getBubbleWithKey(bubble.getKey());
suppressFlyout |= !shouldShowFlyout(bubble.getEntry());
@@ -377,6 +389,12 @@
}
private void doRemove(String key, @DismissReason int reason) {
+ // If it was pending remove it
+ for (int i = 0; i < mPendingBubbles.size(); i++) {
+ if (mPendingBubbles.get(i).getKey().equals(key)) {
+ mPendingBubbles.remove(mPendingBubbles.get(i));
+ }
+ }
int indexToRemove = indexForKey(key);
if (indexToRemove == -1) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 018b631..d99607f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -10,6 +10,8 @@
import com.android.systemui.R;
+import javax.inject.Inject;
+
/**
* Activity for showing aged out bubbles.
* Must be public to be accessible to androidx...AppComponentFactory
@@ -17,6 +19,12 @@
public class BubbleOverflowActivity extends Activity {
private RecyclerView mRecyclerView;
private int mMaxBubbles;
+ private BubbleController mBubbleController;
+
+ @Inject
+ public BubbleOverflowActivity(BubbleController controller) {
+ mBubbleController = controller;
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -64,6 +72,6 @@
}
public void onDestroy() {
- super.onStop();
+ super.onDestroy();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index 563a0a7..31656a0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -961,6 +961,13 @@
if (view != null) {
final SpringAnimation animation =
(SpringAnimation) view.getTag(getTagIdForProperty(property));
+
+ // If the animation is null, the view was probably removed from the layout before
+ // the animation started.
+ if (animation == null) {
+ return;
+ }
+
if (afterCallbacks != null) {
animation.addEndListener(new OneTimeEndListener() {
@Override
diff --git a/media/java/android/media/RouteDiscoveryRequest.aidl b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
similarity index 70%
copy from media/java/android/media/RouteDiscoveryRequest.aidl
copy to packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
index 744f656..e6cdf50 100644
--- a/media/java/android/media/RouteDiscoveryRequest.aidl
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,8 @@
* limitations under the License.
*/
-package android.media;
+package com.android.systemui.controls
-parcelable RouteDiscoveryRequest;
+import android.service.controls.Control
+
+data class ControlStatus(val control: Control, val favorite: Boolean, val removed: Boolean = false)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
new file mode 100644
index 0000000..265ddd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls
+
+import android.content.Context
+import android.content.pm.ServiceInfo
+import com.android.settingslib.applications.DefaultAppInfo
+
+class ControlsServiceInfo(
+ context: Context,
+ serviceInfo: ServiceInfo
+) : DefaultAppInfo(
+ context,
+ context.packageManager,
+ context.userId,
+ serviceInfo.componentName
+)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
new file mode 100644
index 0000000..b6cca3f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.DeviceTypes
+import android.util.Log
+
+/**
+ * Stores basic information about a [Control] to persist and keep track of favorites.
+ */
+data class ControlInfo(
+ val component: ComponentName,
+ val controlId: String,
+ val controlTitle: CharSequence,
+ @DeviceTypes.DeviceType val deviceType: Int
+) {
+
+ companion object {
+ private const val TAG = "ControlInfo"
+ private const val SEPARATOR = ":"
+ fun createFromString(string: String): ControlInfo? {
+ val parts = string.split(SEPARATOR)
+ val component = ComponentName.unflattenFromString(parts[0])
+ if (parts.size != 4 || component == null) {
+ Log.e(TAG, "Cannot parse ControlInfo from $string")
+ return null
+ }
+ val type = try {
+ parts[3].toInt()
+ } catch (e: Exception) {
+ Log.e(TAG, "Cannot parse deviceType from ${parts[3]}")
+ return null
+ }
+ return ControlInfo(
+ component,
+ parts[1],
+ parts[2],
+ if (DeviceTypes.validDeviceType(type)) type else DeviceTypes.TYPE_UNKNOWN)
+ }
+ }
+ override fun toString(): String {
+ return component.flattenToString() +
+ "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
+ }
+
+ class Builder {
+ lateinit var componentName: ComponentName
+ lateinit var controlId: String
+ lateinit var controlTitle: CharSequence
+ var deviceType: Int = DeviceTypes.TYPE_UNKNOWN
+
+ fun build() = ControlInfo(componentName, controlId, controlTitle, deviceType)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
new file mode 100644
index 0000000..6b7fc4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+
+interface ControlsBindingController {
+ fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit)
+ fun bindServices(components: List<ComponentName>)
+ fun subscribe(controls: List<ControlInfo>)
+ fun action(controlInfo: ControlInfo, action: ControlAction)
+ fun unsubscribe()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
new file mode 100644
index 0000000..80e48b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.IBinder
+import android.service.controls.Control
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.util.ArrayMap
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.DelayableExecutor
+import dagger.Lazy
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+@VisibleForTesting
+open class ControlsBindingControllerImpl @Inject constructor(
+ private val context: Context,
+ @Background private val backgroundExecutor: DelayableExecutor,
+ private val lazyController: Lazy<ControlsController>
+) : ControlsBindingController {
+
+ companion object {
+ private const val TAG = "ControlsBindingControllerImpl"
+ }
+
+ private val refreshing = AtomicBoolean(false)
+
+ @GuardedBy("componentMap")
+ private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> =
+ ArrayMap<IBinder, ControlsProviderLifecycleManager>()
+ @GuardedBy("componentMap")
+ private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> =
+ ArrayMap<ComponentName, ControlsProviderLifecycleManager>()
+
+ private val serviceCallback = object : IControlsProviderCallback.Stub() {
+ override fun onLoad(token: IBinder, controls: MutableList<Control>) {
+ backgroundExecutor.execute(OnLoadRunnable(token, controls))
+ }
+
+ override fun onRefreshState(token: IBinder, controlStates: List<Control>) {
+ if (!refreshing.get()) {
+ Log.d(TAG, "Refresh outside of window for token:$token")
+ } else {
+ backgroundExecutor.execute(OnRefreshStateRunnable(token, controlStates))
+ }
+ }
+
+ override fun onControlActionResponse(
+ token: IBinder,
+ controlId: String,
+ @ControlAction.ResponseResult response: Int
+ ) {
+ backgroundExecutor.execute(OnActionResponseRunnable(token, controlId, response))
+ }
+ }
+
+ @VisibleForTesting
+ internal open fun createProviderManager(component: ComponentName):
+ ControlsProviderLifecycleManager {
+ return ControlsProviderLifecycleManager(
+ context,
+ backgroundExecutor,
+ serviceCallback,
+ component
+ )
+ }
+
+ private fun retrieveLifecycleManager(component: ComponentName):
+ ControlsProviderLifecycleManager {
+ synchronized(componentMap) {
+ val provider = componentMap.getOrPut(component) {
+ createProviderManager(component)
+ }
+ tokenMap.putIfAbsent(provider.token, provider)
+ return provider
+ }
+ }
+
+ override fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) {
+ val provider = retrieveLifecycleManager(component)
+ provider.maybeBindAndLoad(callback)
+ }
+
+ override fun subscribe(controls: List<ControlInfo>) {
+ val controlsByComponentName = controls.groupBy { it.component }
+ if (refreshing.compareAndSet(false, true)) {
+ controlsByComponentName.forEach {
+ val provider = retrieveLifecycleManager(it.key)
+ backgroundExecutor.execute {
+ provider.maybeBindAndSubscribe(it.value.map { it.controlId })
+ }
+ }
+ }
+ // Unbind unneeded providers
+ val providersWithFavorites = controlsByComponentName.keys
+ synchronized(componentMap) {
+ componentMap.forEach {
+ if (it.key !in providersWithFavorites) {
+ backgroundExecutor.execute { it.value.unbindService() }
+ }
+ }
+ }
+ }
+
+ override fun unsubscribe() {
+ if (refreshing.compareAndSet(true, false)) {
+ val providers = synchronized(componentMap) {
+ componentMap.values.toList()
+ }
+ providers.forEach {
+ backgroundExecutor.execute { it.unsubscribe() }
+ }
+ }
+ }
+
+ override fun action(controlInfo: ControlInfo, action: ControlAction) {
+ val provider = retrieveLifecycleManager(controlInfo.component)
+ provider.maybeBindAndSendAction(controlInfo.controlId, action)
+ }
+
+ override fun bindServices(components: List<ComponentName>) {
+ components.forEach {
+ val provider = retrieveLifecycleManager(it)
+ backgroundExecutor.execute { provider.bindPermanently() }
+ }
+ }
+
+ private abstract inner class CallbackRunnable(val token: IBinder) : Runnable {
+ protected val provider: ControlsProviderLifecycleManager? =
+ synchronized(componentMap) {
+ tokenMap.get(token)
+ }
+ }
+
+ private inner class OnLoadRunnable(
+ token: IBinder,
+ val list: List<Control>
+ ) : CallbackRunnable(token) {
+ override fun run() {
+ if (provider == null) {
+ Log.e(TAG, "No provider found for token:$token")
+ return
+ }
+ synchronized(componentMap) {
+ if (token !in tokenMap.keys) {
+ Log.e(TAG, "Provider for token:$token does not exist anymore")
+ return
+ }
+ }
+ provider.lastLoadCallback?.invoke(list) ?: run {
+ Log.w(TAG, "Null callback")
+ }
+ provider.maybeUnbindAndRemoveCallback()
+ }
+ }
+
+ private inner class OnRefreshStateRunnable(
+ token: IBinder,
+ val list: List<Control>
+ ) : CallbackRunnable(token) {
+ override fun run() {
+ if (!refreshing.get()) {
+ Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}")
+ }
+ provider?.let {
+ lazyController.get().refreshStatus(it.componentName, list)
+ }
+ }
+ }
+
+ private inner class OnActionResponseRunnable(
+ token: IBinder,
+ val controlId: String,
+ @ControlAction.ResponseResult val response: Int
+ ) : CallbackRunnable(token) {
+ override fun run() {
+ provider?.let {
+ lazyController.get().onActionResponse(it.componentName, controlId, response)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
new file mode 100644
index 0000000..4d95822
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+import com.android.systemui.controls.ControlStatus
+
+interface ControlsController {
+ val available: Boolean
+
+ fun getFavoriteControls(): List<ControlInfo>
+ fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit)
+ fun subscribeToFavorites()
+ fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
+ fun unsubscribe()
+ fun action(controlInfo: ControlInfo, action: ControlAction)
+ fun refreshStatus(componentName: ComponentName, controls: List<Control>)
+ fun onActionResponse(
+ componentName: ComponentName,
+ controlId: String,
+ @ControlAction.ResponseResult response: Int
+ )
+ fun clearFavorites()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
new file mode 100644
index 0000000..7e328e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Environment
+import android.provider.Settings
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+import android.util.ArrayMap
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.DumpController
+import com.android.systemui.Dumpable
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ControlsControllerImpl @Inject constructor (
+ private val context: Context,
+ @Background private val executor: DelayableExecutor,
+ private val uiController: ControlsUiController,
+ private val bindingController: ControlsBindingController,
+ private val optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+ dumpController: DumpController
+) : Dumpable, ControlsController {
+
+ companion object {
+ private const val TAG = "ControlsControllerImpl"
+ const val CONTROLS_AVAILABLE = "systemui.controls_available"
+ }
+
+ override val available = Settings.Secure.getInt(
+ context.contentResolver, CONTROLS_AVAILABLE, 0) != 0
+ val persistenceWrapper = optionalWrapper.orElseGet {
+ ControlsFavoritePersistenceWrapper(
+ Environment.buildPath(
+ context.filesDir,
+ ControlsFavoritePersistenceWrapper.FILE_NAME),
+ executor
+ )
+ }
+
+ // Map of map: ComponentName -> (String -> ControlInfo)
+ @GuardedBy("currentFavorites")
+ private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>()
+
+ init {
+ if (available) {
+ dumpController.registerDumpable(this)
+ loadFavorites()
+ }
+ }
+
+ private fun loadFavorites() {
+ val infos = persistenceWrapper.readFavorites()
+ synchronized(currentFavorites) {
+ infos.forEach {
+ currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() })
+ .put(it.controlId, it)
+ }
+ }
+ }
+
+ override fun loadForComponent(
+ componentName: ComponentName,
+ callback: (List<ControlStatus>) -> Unit
+ ) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ bindingController.bindAndLoad(componentName) {
+ synchronized(currentFavorites) {
+ val favoritesForComponentKeys: Set<String> =
+ currentFavorites.get(componentName)?.keys ?: emptySet()
+ val changed = updateFavoritesLocked(componentName, it)
+ if (changed) {
+ persistenceWrapper.storeFavorites(favoritesAsListLocked())
+ }
+ val removed = findRemovedLocked(favoritesForComponentKeys, it)
+ callback(removed.map { currentFavorites.getValue(componentName).getValue(it) }
+ .map(::createRemovedStatus) +
+ it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) })
+ }
+ }
+ }
+
+ private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus {
+ val intent = Intent(context, ControlsFavoritingActivity::class.java).apply {
+ putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ }
+ val pendingIntent = PendingIntent.getActivity(context,
+ controlInfo.component.hashCode(),
+ intent,
+ 0)
+ val control = Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
+ .setTitle(controlInfo.controlTitle)
+ .setDeviceType(controlInfo.deviceType)
+ .build()
+ return ControlStatus(control, true, true)
+ }
+
+ @GuardedBy("currentFavorites")
+ private fun findRemovedLocked(favoriteKeys: Set<String>, list: List<Control>): Set<String> {
+ val controlsKeys = list.map { it.controlId }
+ return favoriteKeys.minus(controlsKeys)
+ }
+
+ @GuardedBy("currentFavorites")
+ private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean {
+ val favorites = currentFavorites.get(componentName) ?: mutableMapOf()
+ val favoriteKeys = favorites.keys
+ if (favoriteKeys.isEmpty()) return false // early return
+ var changed = false
+ list.forEach {
+ if (it.controlId in favoriteKeys) {
+ val value = favorites.getValue(it.controlId)
+ if (value.controlTitle != it.title || value.deviceType != it.deviceType) {
+ favorites[it.controlId] = value.copy(controlTitle = it.title,
+ deviceType = it.deviceType)
+ changed = true
+ }
+ }
+ }
+ return changed
+ }
+
+ @GuardedBy("currentFavorites")
+ private fun favoritesAsListLocked(): List<ControlInfo> {
+ return currentFavorites.flatMap { it.value.values }
+ }
+
+ override fun subscribeToFavorites() {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ // Make a copy of the favorites list
+ val favorites = synchronized(currentFavorites) {
+ currentFavorites.flatMap { it.value.values.toList() }
+ }
+ bindingController.subscribe(favorites)
+ }
+
+ override fun unsubscribe() {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ bindingController.unsubscribe()
+ }
+
+ override fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ var changed = false
+ val listOfControls = synchronized(currentFavorites) {
+ if (state) {
+ if (controlInfo.component !in currentFavorites) {
+ currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>())
+ changed = true
+ }
+ val controlsForComponent = currentFavorites.getValue(controlInfo.component)
+ if (controlInfo.controlId !in controlsForComponent) {
+ controlsForComponent.put(controlInfo.controlId, controlInfo)
+ changed = true
+ } else {
+ if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) {
+ controlsForComponent.put(controlInfo.controlId, controlInfo)
+ changed = true
+ }
+ }
+ } else {
+ changed = currentFavorites.get(controlInfo.component)
+ ?.remove(controlInfo.controlId) != null
+ }
+ favoritesAsListLocked()
+ }
+ if (changed) {
+ persistenceWrapper.storeFavorites(listOfControls)
+ }
+ }
+
+ override fun refreshStatus(componentName: ComponentName, controls: List<Control>) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ executor.execute {
+ synchronized(currentFavorites) {
+ val changed = updateFavoritesLocked(componentName, controls)
+ if (changed) {
+ persistenceWrapper.storeFavorites(favoritesAsListLocked())
+ }
+ }
+ }
+ uiController.onRefreshState(componentName, controls)
+ }
+
+ override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ uiController.onActionResponse(componentName, controlId, response)
+ }
+
+ override fun getFavoriteControls(): List<ControlInfo> {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return emptyList()
+ }
+ synchronized(currentFavorites) {
+ return favoritesAsListLocked()
+ }
+ }
+
+ override fun action(controlInfo: ControlInfo, action: ControlAction) {
+ bindingController.action(controlInfo, action)
+ }
+
+ override fun clearFavorites() {
+ val changed = synchronized(currentFavorites) {
+ currentFavorites.isNotEmpty().also {
+ currentFavorites.clear()
+ }
+ }
+ if (changed) {
+ persistenceWrapper.storeFavorites(emptyList())
+ }
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println("ControlsController state:")
+ pw.println(" Favorites:")
+ synchronized(currentFavorites) {
+ currentFavorites.forEach {
+ it.value.forEach {
+ pw.println(" ${it.value}")
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
new file mode 100644
index 0000000..6f2d71f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.app.ActivityManager
+import android.content.ComponentName
+import android.util.AtomicFile
+import android.util.Log
+import android.util.Slog
+import android.util.Xml
+import com.android.systemui.util.concurrency.DelayableExecutor
+import libcore.io.IoUtils
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+import java.io.IOException
+
+class ControlsFavoritePersistenceWrapper(
+ val file: File,
+ val executor: DelayableExecutor
+) {
+
+ companion object {
+ private const val TAG = "ControlsFavoritePersistenceWrapper"
+ const val FILE_NAME = "controls_favorites.xml"
+ private const val TAG_CONTROLS = "controls"
+ private const val TAG_CONTROL = "control"
+ private const val TAG_COMPONENT = "component"
+ private const val TAG_ID = "id"
+ private const val TAG_TITLE = "title"
+ private const val TAG_TYPE = "type"
+ }
+
+ val currentUser: Int
+ get() = ActivityManager.getCurrentUser()
+
+ fun storeFavorites(list: List<ControlInfo>) {
+ executor.execute {
+ val atomicFile = AtomicFile(file)
+ val writer = try {
+ atomicFile.startWrite()
+ } catch (e: IOException) {
+ Log.e(TAG, "Failed to start write file", e)
+ return@execute
+ }
+ try {
+ Xml.newSerializer().apply {
+ setOutput(writer, "utf-8")
+ setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
+ startDocument(null, true)
+ startTag(null, TAG_CONTROLS)
+ list.forEach {
+ startTag(null, TAG_CONTROL)
+ attribute(null, TAG_COMPONENT, it.component.flattenToString())
+ attribute(null, TAG_ID, it.controlId)
+ attribute(null, TAG_TITLE, it.controlTitle.toString())
+ attribute(null, TAG_TYPE, it.deviceType.toString())
+ endTag(null, TAG_CONTROL)
+ }
+ endTag(null, TAG_CONTROLS)
+ endDocument()
+ atomicFile.finishWrite(writer)
+ }
+ } catch (t: Throwable) {
+ Log.e(TAG, "Failed to write file, reverting to previous version")
+ atomicFile.failWrite(writer)
+ } finally {
+ IoUtils.closeQuietly(writer)
+ }
+ }
+ }
+
+ fun readFavorites(): List<ControlInfo> {
+ if (!file.exists()) {
+ Log.d(TAG, "No favorites, returning empty list")
+ return emptyList()
+ }
+ val reader = try {
+ FileInputStream(file)
+ } catch (fnfe: FileNotFoundException) {
+ Slog.i(TAG, "No file found")
+ return emptyList()
+ }
+ try {
+ val parser = Xml.newPullParser()
+ parser.setInput(reader, null)
+ return parseXml(parser)
+ } catch (e: XmlPullParserException) {
+ throw IllegalStateException("Failed parsing favorites file: $file", e)
+ } catch (e: IOException) {
+ throw IllegalStateException("Failed parsing favorites file: $file", e)
+ } finally {
+ IoUtils.closeQuietly(reader)
+ }
+ }
+
+ private fun parseXml(parser: XmlPullParser): List<ControlInfo> {
+ var type: Int = 0
+ val infos = mutableListOf<ControlInfo>()
+ while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue
+ }
+ val tagName = parser.name
+ if (tagName == TAG_CONTROL) {
+ val component = ComponentName.unflattenFromString(
+ parser.getAttributeValue(null, TAG_COMPONENT))
+ val id = parser.getAttributeValue(null, TAG_ID)
+ val title = parser.getAttributeValue(null, TAG_TITLE)
+ val type = parser.getAttributeValue(null, TAG_TYPE)?.toInt()
+ if (component != null && id != null && title != null && type != null) {
+ infos.add(ControlInfo(component, id, title, type))
+ }
+ }
+ }
+ return infos
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
new file mode 100644
index 0000000..79057ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.os.RemoteException
+import android.service.controls.Control
+import android.service.controls.ControlsProviderService.CALLBACK_BINDER
+import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
+import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
+import android.service.controls.IControlsProvider
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.util.ArraySet
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.TimeUnit
+
+typealias LoadCallback = (List<Control>) -> Unit
+class ControlsProviderLifecycleManager(
+ private val context: Context,
+ private val executor: DelayableExecutor,
+ private val serviceCallback: IControlsProviderCallback.Stub,
+ val componentName: ComponentName
+) : IBinder.DeathRecipient {
+
+ var lastLoadCallback: LoadCallback? = null
+ private set
+ val token: IBinder = Binder()
+ private var unbindImmediate = false
+ private var requiresBound = false
+ private var isBound = false
+ @GuardedBy("queuedMessages")
+ private val queuedMessages: MutableSet<Message> = ArraySet()
+ private var wrapper: ControlsProviderServiceWrapper? = null
+ private var bindTryCount = 0
+ private val TAG = javaClass.simpleName
+ private var onLoadCanceller: Runnable? = null
+
+ companion object {
+ private const val MSG_LOAD = 0
+ private const val MSG_SUBSCRIBE = 1
+ private const val MSG_UNSUBSCRIBE = 2
+ private const val MSG_ON_ACTION = 3
+ private const val MSG_UNBIND = 4
+ private const val BIND_RETRY_DELAY = 1000L // ms
+ private const val LOAD_TIMEOUT = 5000L // ms
+ private const val MAX_BIND_RETRIES = 5
+ private const val DEBUG = true
+ private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
+ Context.BIND_WAIVE_PRIORITY
+ }
+
+ private val intent = Intent().apply {
+ component = componentName
+ putExtra(CALLBACK_BUNDLE, Bundle().apply {
+ putBinder(CALLBACK_BINDER, serviceCallback)
+ putBinder(CALLBACK_TOKEN, token)
+ })
+ }
+
+ private fun bindService(bind: Boolean) {
+ requiresBound = bind
+ if (bind) {
+ if (bindTryCount == MAX_BIND_RETRIES) {
+ return
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Binding service $intent")
+ }
+ bindTryCount++
+ try {
+ isBound = context.bindService(intent, serviceConnection, BIND_FLAGS)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "Failed to bind to service", e)
+ isBound = false
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Unbinding service $intent")
+ }
+ bindTryCount = 0
+ wrapper = null
+ if (isBound) {
+ context.unbindService(serviceConnection)
+ isBound = false
+ }
+ }
+ }
+
+ fun bindPermanently() {
+ unbindImmediate = false
+ unqueueMessage(Message.Unbind)
+ bindService(true)
+ }
+
+ private val serviceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ if (DEBUG) Log.d(TAG, "onServiceConnected $name")
+ bindTryCount = 0
+ wrapper = ControlsProviderServiceWrapper(IControlsProvider.Stub.asInterface(service))
+ try {
+ service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
+ } catch (_: RemoteException) {}
+ handlePendingMessages()
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ if (DEBUG) Log.d(TAG, "onServiceDisconnected $name")
+ isBound = false
+ bindService(false)
+ }
+ }
+
+ private fun handlePendingMessages() {
+ val queue = synchronized(queuedMessages) {
+ ArraySet(queuedMessages).also {
+ queuedMessages.clear()
+ }
+ }
+ if (Message.Unbind in queue) {
+ bindService(false)
+ return
+ }
+ if (Message.Load in queue) {
+ load()
+ }
+ queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run {
+ subscribe(this)
+ }
+ queue.filter { it is Message.Action }.forEach {
+ val msg = it as Message.Action
+ onAction(msg.id, msg.action)
+ }
+ }
+
+ override fun binderDied() {
+ if (wrapper == null) return
+ wrapper = null
+ if (requiresBound) {
+ if (DEBUG) {
+ Log.d(TAG, "binderDied")
+ }
+ // Try rebinding some time later
+ }
+ }
+
+ private fun queueMessage(message: Message) {
+ synchronized(queuedMessages) {
+ queuedMessages.add(message)
+ }
+ }
+
+ private fun unqueueMessage(message: Message) {
+ synchronized(queuedMessages) {
+ queuedMessages.removeIf { it.type == message.type }
+ }
+ }
+
+ private fun load() {
+ if (DEBUG) {
+ Log.d(TAG, "load $componentName")
+ }
+ if (!(wrapper?.load() ?: false)) {
+ queueMessage(Message.Load)
+ binderDied()
+ }
+ }
+
+ fun maybeBindAndLoad(callback: LoadCallback) {
+ unqueueMessage(Message.Unbind)
+ lastLoadCallback = callback
+ onLoadCanceller = executor.executeDelayed({
+ // Didn't receive a response in time, log and send back empty list
+ Log.d(TAG, "Timeout waiting onLoad for $componentName")
+ serviceCallback.onLoad(token, emptyList())
+ }, LOAD_TIMEOUT, TimeUnit.MILLISECONDS)
+ if (isBound) {
+ load()
+ } else {
+ queueMessage(Message.Load)
+ unbindImmediate = true
+ bindService(true)
+ }
+ }
+
+ fun maybeBindAndSubscribe(controlIds: List<String>) {
+ if (isBound) {
+ subscribe(controlIds)
+ } else {
+ queueMessage(Message.Subscribe(controlIds))
+ bindService(true)
+ }
+ }
+
+ private fun subscribe(controlIds: List<String>) {
+ if (DEBUG) {
+ Log.d(TAG, "subscribe $componentName - $controlIds")
+ }
+ if (!(wrapper?.subscribe(controlIds) ?: false)) {
+ queueMessage(Message.Subscribe(controlIds))
+ binderDied()
+ }
+ }
+
+ fun maybeBindAndSendAction(controlId: String, action: ControlAction) {
+ if (isBound) {
+ onAction(controlId, action)
+ } else {
+ queueMessage(Message.Action(controlId, action))
+ bindService(true)
+ }
+ }
+
+ private fun onAction(controlId: String, action: ControlAction) {
+ if (DEBUG) {
+ Log.d(TAG, "onAction $componentName - $controlId")
+ }
+ if (!(wrapper?.onAction(controlId, action) ?: false)) {
+ queueMessage(Message.Action(controlId, action))
+ binderDied()
+ }
+ }
+
+ fun unsubscribe() {
+ if (DEBUG) {
+ Log.d(TAG, "unsubscribe $componentName")
+ }
+ unqueueMessage(Message.Subscribe(emptyList())) // Removes all subscribe messages
+ wrapper?.unsubscribe()
+ }
+
+ fun maybeUnbindAndRemoveCallback() {
+ lastLoadCallback = null
+ onLoadCanceller?.run()
+ onLoadCanceller = null
+ if (unbindImmediate) {
+ bindService(false)
+ }
+ }
+
+ fun unbindService() {
+ unbindImmediate = true
+ maybeUnbindAndRemoveCallback()
+ }
+
+ sealed class Message {
+ abstract val type: Int
+ object Load : Message() {
+ override val type = MSG_LOAD
+ }
+ object Unbind : Message() {
+ override val type = MSG_UNBIND
+ }
+ class Subscribe(val list: List<String>) : Message() {
+ override val type = MSG_SUBSCRIBE
+ }
+ class Action(val id: String, val action: ControlAction) : Message() {
+ override val type = MSG_ON_ACTION
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt
new file mode 100644
index 0000000..882a10d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.service.controls.actions.ControlAction
+import android.service.controls.IControlsProvider
+import android.util.Log
+
+class ControlsProviderServiceWrapper(val service: IControlsProvider) {
+ companion object {
+ private const val TAG = "ControlsProviderServiceWrapper"
+ }
+
+ private fun callThroughService(block: () -> Unit): Boolean {
+ try {
+ block()
+ return true
+ } catch (ex: Exception) {
+ Log.d(TAG, "Caught exception from ControlsProviderService", ex)
+ return false
+ }
+ }
+
+ fun load(): Boolean {
+ return callThroughService {
+ service.load()
+ }
+ }
+
+ fun subscribe(controlIds: List<String>): Boolean {
+ return callThroughService {
+ service.subscribe(controlIds)
+ }
+ }
+
+ fun unsubscribe(): Boolean {
+ return callThroughService {
+ service.unsubscribe()
+ }
+ }
+
+ fun onAction(controlId: String, action: ControlAction): Boolean {
+ return callThroughService {
+ service.onAction(controlId, action)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
new file mode 100644
index 0000000..859311e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.dagger
+
+import android.app.Activity
+import com.android.systemui.controls.controller.ControlsBindingController
+import com.android.systemui.controls.controller.ControlsBindingControllerImpl
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.management.ControlsListingControllerImpl
+import com.android.systemui.controls.management.ControlsProviderSelectorActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.ControlsUiControllerImpl
+import dagger.Binds
+import dagger.BindsOptionalOf
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class ControlsModule {
+
+ @Binds
+ abstract fun provideControlsListingController(
+ controller: ControlsListingControllerImpl
+ ): ControlsListingController
+
+ @Binds
+ abstract fun provideControlsController(controller: ControlsControllerImpl): ControlsController
+
+ @Binds
+ abstract fun provideControlsBindingController(
+ controller: ControlsBindingControllerImpl
+ ): ControlsBindingController
+
+ @Binds
+ abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
+
+ @BindsOptionalOf
+ abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
+
+ @Binds
+ @IntoMap
+ @ClassKey(ControlsProviderSelectorActivity::class)
+ abstract fun provideControlsProviderActivity(
+ activity: ControlsProviderSelectorActivity
+ ): Activity
+
+ @Binds
+ @IntoMap
+ @ClassKey(ControlsFavoritingActivity::class)
+ abstract fun provideControlsFavoritingActivity(
+ activity: ControlsFavoritingActivity
+ ): Activity
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
new file mode 100644
index 0000000..d62bb4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.recyclerview.widget.RecyclerView
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.R
+import java.util.concurrent.Executor
+
+/**
+ * Adapter for binding [CandidateInfo] related to [ControlsProviderService].
+ *
+ * This class handles subscribing and keeping track of the list of valid applications for
+ * displaying.
+ *
+ * @param uiExecutor an executor on the view thread of the containing [RecyclerView]
+ * @param lifecycle the lifecycle of the containing [LifecycleOwner] to control listening status
+ * @param controlsListingController the controller to keep track of valid applications
+ * @param layoutInflater an inflater for the views in the containing [RecyclerView]
+ * @param onAppSelected a callback to indicate that an app has been selected in the list.
+ */
+class AppAdapter(
+ uiExecutor: Executor,
+ lifecycle: Lifecycle,
+ controlsListingController: ControlsListingController,
+ private val layoutInflater: LayoutInflater,
+ private val onAppSelected: (ComponentName?) -> Unit = {}
+) : RecyclerView.Adapter<AppAdapter.Holder>() {
+
+ private var listOfServices = emptyList<CandidateInfo>()
+
+ private val callback = object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(list: List<CandidateInfo>) {
+ uiExecutor.execute {
+ listOfServices = list
+ notifyDataSetChanged()
+ }
+ }
+ }
+
+ init {
+ controlsListingController.observe(lifecycle, callback)
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
+ return Holder(layoutInflater.inflate(R.layout.app_item, parent, false))
+ }
+
+ override fun getItemCount() = listOfServices.size
+
+ override fun onBindViewHolder(holder: Holder, index: Int) {
+ holder.bindData(listOfServices[index])
+ holder.itemView.setOnClickListener {
+ onAppSelected(ComponentName.unflattenFromString(listOfServices[index].key))
+ }
+ }
+
+ /**
+ * Holder for binding views in the [RecyclerView]-
+ */
+ class Holder(view: View) : RecyclerView.ViewHolder(view) {
+ private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon)
+ private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title)
+
+ /**
+ * Bind data to the view
+ * @param data Information about the [ControlsProviderService] to bind to the data
+ */
+ fun bindData(data: CandidateInfo) {
+ icon.setImageDrawable(data.loadIcon())
+ title.text = data.loadLabel()
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
new file mode 100644
index 0000000..e6d3c26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CheckBox
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.controller.ControlInfo
+
+/**
+ * Adapter for binding [Control] information to views.
+ *
+ * @param layoutInflater an inflater for the views in the containing [RecyclerView]
+ * @param favoriteCallback a callback to be called when the favorite status of a [Control] is
+ * changed. The callback will take a [ControlInfo.Builder] that's
+ * pre-populated with the [Control] information and the new favorite
+ * status.
+ */
+class ControlAdapter(
+ private val layoutInflater: LayoutInflater,
+ private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit
+) : RecyclerView.Adapter<ControlAdapter.Holder>() {
+
+ var listOfControls = emptyList<ControlStatus>()
+
+ override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
+ return Holder(layoutInflater.inflate(R.layout.control_item, parent, false))
+ }
+
+ override fun getItemCount() = listOfControls.size
+
+ override fun onBindViewHolder(holder: Holder, index: Int) {
+ holder.bindData(listOfControls[index], favoriteCallback)
+ }
+
+ /**
+ * Holder for binding views in the [RecyclerView]-
+ */
+ class Holder(view: View) : RecyclerView.ViewHolder(view) {
+ private val title: TextView = itemView.requireViewById(R.id.title)
+ private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
+ private val favorite: CheckBox = itemView.requireViewById(R.id.favorite)
+
+ /**
+ * Bind data to the view
+ * @param data information about the [Control]
+ * @param callback a callback to be called when the favorite status of the [Control] is
+ * changed. The callback will take a [ControlInfo.Builder] that's
+ * pre-populated with the [Control] information and the new favorite status.
+ */
+ fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) {
+ title.text = data.control.title
+ subtitle.text = data.control.subtitle
+ favorite.isChecked = data.favorite
+ favorite.setOnClickListener {
+ val infoBuilder = ControlInfo.Builder().apply {
+ controlId = data.control.controlId
+ controlTitle = data.control.title
+ deviceType = data.control.deviceType
+ }
+ callback(infoBuilder, favorite.isChecked)
+ }
+ }
+ }
+
+ fun setItems(list: List<ControlStatus>) {
+ listOfControls = list
+ notifyDataSetChanged()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
new file mode 100644
index 0000000..01c4fef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.app.Activity
+import android.content.ComponentName
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.controls.controller.ControlInfo
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+class ControlsFavoritingActivity @Inject constructor(
+ @Main private val executor: Executor,
+ private val controller: ControlsControllerImpl
+) : Activity() {
+
+ companion object {
+ private const val TAG = "ControlsFavoritingActivity"
+ const val EXTRA_APP = "extra_app_label"
+ const val EXTRA_COMPONENT = "extra_component"
+ }
+
+ private lateinit var recyclerView: RecyclerView
+ private lateinit var adapter: ControlAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val app = intent.getCharSequenceExtra(EXTRA_APP)
+ val component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT)
+
+ // If we have no component name, there's not much we can do.
+ val callback = component?.let {
+ { infoBuilder: ControlInfo.Builder, status: Boolean ->
+ infoBuilder.componentName = it
+ controller.changeFavoriteStatus(infoBuilder.build(), status)
+ }
+ } ?: { _, _ -> Unit }
+
+ recyclerView = RecyclerView(applicationContext)
+ adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback)
+ recyclerView.adapter = adapter
+ recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+ if (app != null) {
+ setTitle("Controls for $app")
+ } else {
+ setTitle("Controls")
+ }
+ setContentView(recyclerView)
+
+ component?.let {
+ controller.loadForComponent(it) {
+ executor.execute {
+ adapter.setItems(it)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
new file mode 100644
index 0000000..09e0ce9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.statusbar.policy.CallbackController
+
+interface ControlsListingController :
+ CallbackController<ControlsListingController.ControlsListingCallback> {
+
+ fun getCurrentServices(): List<CandidateInfo>
+ fun getAppLabel(name: ComponentName): CharSequence? = ""
+
+ interface ControlsListingCallback {
+ fun onServicesUpdated(list: List<CandidateInfo>)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
new file mode 100644
index 0000000..9372162
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ServiceInfo
+import android.service.controls.ControlsProviderService
+import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
+import com.android.settingslib.applications.DefaultAppInfo
+import com.android.settingslib.applications.ServiceListing
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Provides a listing of components to be used as ControlsServiceProvider.
+ *
+ * This controller keeps track of components that satisfy:
+ *
+ * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
+ * * Has the bind permission `android.permission.BIND_CONTROLS`
+ */
+@Singleton
+class ControlsListingControllerImpl @VisibleForTesting constructor(
+ private val context: Context,
+ @Background private val backgroundExecutor: Executor,
+ private val serviceListing: ServiceListing
+) : ControlsListingController {
+
+ @Inject
+ constructor(context: Context, executor: Executor): this(
+ context,
+ executor,
+ ServiceListing.Builder(context)
+ .setIntentAction(ControlsProviderService.CONTROLS_ACTION)
+ .setPermission("android.permission.BIND_CONTROLS")
+ .setNoun("Controls Provider")
+ .setSetting("controls_providers")
+ .setTag("controls_providers")
+ .build()
+ )
+
+ companion object {
+ private const val TAG = "ControlsListingControllerImpl"
+ }
+
+ private var availableServices = emptyList<ServiceInfo>()
+
+ init {
+ serviceListing.addCallback {
+ Log.d(TAG, "ServiceConfig reloaded")
+ availableServices = it.toList()
+
+ backgroundExecutor.execute {
+ callbacks.forEach {
+ it.onServicesUpdated(getCurrentServices())
+ }
+ }
+ }
+ }
+
+ // All operations in background thread
+ private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
+
+ /**
+ * Adds a callback to this controller.
+ *
+ * The callback will be notified after it is added as well as any time that the valid
+ * components change.
+ *
+ * @param listener a callback to be notified
+ */
+ override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
+ backgroundExecutor.execute {
+ callbacks.add(listener)
+ if (callbacks.size == 1) {
+ serviceListing.setListening(true)
+ serviceListing.reload()
+ } else {
+ listener.onServicesUpdated(getCurrentServices())
+ }
+ }
+ }
+
+ /**
+ * Removes a callback from this controller.
+ *
+ * @param listener the callback to be removed.
+ */
+ override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) {
+ backgroundExecutor.execute {
+ callbacks.remove(listener)
+ if (callbacks.size == 0) {
+ serviceListing.setListening(false)
+ }
+ }
+ }
+
+ /**
+ * @return a list of components that satisfy the requirements to be a
+ * [ControlsProviderService]
+ */
+ override fun getCurrentServices(): List<CandidateInfo> =
+ availableServices.map { ControlsServiceInfo(context, it) }
+
+ /**
+ * Get the localized label for the component.
+ *
+ * @param name the name of the component
+ * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
+ */
+ override fun getAppLabel(name: ComponentName): CharSequence? {
+ return getCurrentServices().firstOrNull { (it as? DefaultAppInfo)?.componentName == name }
+ ?.loadLabel()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
new file mode 100644
index 0000000..69af516
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.LifecycleActivity
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Activity to select an application to favorite the [Control] provided by them.
+ */
+class ControlsProviderSelectorActivity @Inject constructor(
+ @Main private val executor: Executor,
+ private val listingController: ControlsListingController
+) : LifecycleActivity() {
+
+ companion object {
+ private const val TAG = "ControlsProviderSelectorActivity"
+ }
+
+ private lateinit var recyclerView: RecyclerView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ recyclerView = RecyclerView(applicationContext)
+ recyclerView.adapter = AppAdapter(executor, lifecycle, listingController,
+ LayoutInflater.from(this), ::launchFavoritingActivity)
+ recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+ setContentView(recyclerView)
+ }
+
+ /**
+ * Launch the [ControlsFavoritingActivity] for the specified component.
+ * @param component a component name for a [ControlsProviderService]
+ */
+ fun launchFavoritingActivity(component: ComponentName?) {
+ component?.let {
+ val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java).apply {
+ putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it))
+ putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ }
+ startActivity(intent)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
new file mode 100644
index 0000000..0270c2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+
+interface ControlsUiController {
+ fun onRefreshState(componentName: ComponentName, controls: List<Control>)
+ fun onActionResponse(
+ componentName: ComponentName,
+ controlId: String,
+ @ControlAction.ResponseResult response: Int
+ )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
new file mode 100644
index 0000000..0ace126
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.content.ComponentName
+import android.service.controls.Control
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ControlsUiControllerImpl @Inject constructor() : ControlsUiController {
+
+ override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
+ TODO("not implemented")
+ }
+
+ override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
+ TODO("not implemented")
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index df79310..91f032d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -19,7 +19,9 @@
import android.app.Activity;
import com.android.systemui.ForegroundServicesDialog;
+import com.android.systemui.bubbles.BubbleOverflowActivity;
import com.android.systemui.keyguard.WorkLockActivity;
+import com.android.systemui.screenrecord.ScreenRecordDialog;
import com.android.systemui.settings.BrightnessDialog;
import com.android.systemui.tuner.TunerActivity;
@@ -29,7 +31,7 @@
import dagger.multibindings.IntoMap;
/**
- * Services and Activities that are injectable should go here.
+ * Activities that are injectable should go here.
*/
@Module
public abstract class DefaultActivityBinder {
@@ -56,4 +58,16 @@
@IntoMap
@ClassKey(BrightnessDialog.class)
public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
+
+ /** Inject into ScreenRecordDialog */
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenRecordDialog.class)
+ public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity);
+
+ /** Inject into BubbleOverflowActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(BubbleOverflowActivity.class)
+ public abstract Activity bindBubbleOverflowActivity(BubbleOverflowActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index f790d99..f006acf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -22,6 +22,7 @@
import com.android.systemui.SystemUIService;
import com.android.systemui.doze.DozeService;
import com.android.systemui.keyguard.KeyguardService;
+import com.android.systemui.screenrecord.RecordingService;
import com.android.systemui.screenshot.TakeScreenshotService;
import dagger.Binds;
@@ -63,4 +64,10 @@
@IntoMap
@ClassKey(TakeScreenshotService.class)
public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
+
+ /** Inject into RecordingService */
+ @Binds
+ @IntoMap
+ @ClassKey(RecordingService.class)
+ public abstract Service bindRecordingService(RecordingService service);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 6744d74..2877ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -20,10 +20,14 @@
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.appops.AppOpsControllerImpl;
import com.android.systemui.classifier.FalsingManagerProxy;
+import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.doze.DozeHost;
+import com.android.systemui.globalactions.GlobalActionsComponent;
+import com.android.systemui.globalactions.GlobalActionsImpl;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.PowerNotificationWarnings;
@@ -82,7 +86,7 @@
/**
* Maps interfaces to implementations for use with Dagger.
*/
-@Module
+@Module(includes = {ControlsModule.class})
public abstract class DependencyBinder {
/**
@@ -99,6 +103,17 @@
/**
*/
@Binds
+ public abstract GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl);
+
+ /**
+ */
+ @Binds
+ public abstract GlobalActions.GlobalActionsManager provideGlobalActionsManager(
+ GlobalActionsComponent controllerImpl);
+
+ /**
+ */
+ @Binds
public abstract LocationController provideLocationController(
LocationControllerImpl controllerImpl);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 26337b1..3aa14a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -26,17 +26,24 @@
import android.app.NotificationManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
+import android.app.trust.TrustManager;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.Vibrator;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
import android.view.IWindowManager;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -66,6 +73,12 @@
return context.getSystemService(AccessibilityManager.class);
}
+ @Provides
+ @Singleton
+ static ActivityManager provideActivityManager(Context context) {
+ return context.getSystemService(ActivityManager.class);
+ }
+
@Singleton
@Provides
static AlarmManager provideAlarmManager(Context context) {
@@ -74,10 +87,21 @@
@Provides
@Singleton
- static ActivityManager provideActivityManager(Context context) {
- return context.getSystemService(ActivityManager.class);
+ static AudioManager provideAudioManager(Context context) {
+ return context.getSystemService(AudioManager.class);
}
+ @Provides
+ @Singleton
+ static ConnectivityManager provideConnectivityManagager(Context context) {
+ return context.getSystemService(ConnectivityManager.class);
+ }
+
+ @Provides
+ @Singleton
+ static ContentResolver provideContentResolver(Context context) {
+ return context.getContentResolver();
+ }
@Provides
@DisplayId
@@ -185,6 +209,31 @@
@Provides
@Singleton
+ static TelecomManager provideTelecomManager(Context context) {
+ return context.getSystemService(TelecomManager.class);
+ }
+
+ @Provides
+ @Singleton
+ static TelephonyManager provideTelephonyManager(Context context) {
+ return context.getSystemService(TelephonyManager.class);
+ }
+
+ @Provides
+ @Singleton
+ static TrustManager provideTrustManager(Context context) {
+ return context.getSystemService(TrustManager.class);
+ }
+
+ @Provides
+ @Singleton
+ @Nullable
+ static Vibrator provideVibrator(Context context) {
+ return context.getSystemService(Vibrator.class);
+ }
+
+ @Provides
+ @Singleton
static UserManager provideUserManager(Context context) {
return context.getSystemService(UserManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 58ddda9..a6fa414 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -30,8 +30,8 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.people.PeopleHubModule;
import com.android.systemui.statusbar.phone.KeyguardLiftController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -82,6 +82,11 @@
keyguardUpdateMonitor, dumpController);
}
+ /** */
+ @Binds
+ public abstract NotificationRowBinder bindNotificationRowBinder(
+ NotificationRowBinderImpl notificationRowBinder);
+
@Singleton
@Provides
static SysUiState provideSysUiState() {
@@ -106,9 +111,4 @@
@Singleton
@Binds
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
-
- @Singleton
- @Binds
- abstract NotifListBuilder bindNotifListBuilder(NotifListBuilderImpl impl);
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index 19b6f82..e949007 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -19,7 +19,6 @@
import android.os.ServiceManager;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
@@ -29,6 +28,7 @@
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import javax.inject.Inject;
+import javax.inject.Provider;
import javax.inject.Singleton;
/**
@@ -38,23 +38,29 @@
public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
private final CommandQueue mCommandQueue;
+ private final ExtensionController mExtensionController;
+ private final Provider<GlobalActions> mGlobalActionsProvider;
private GlobalActions mPlugin;
private Extension<GlobalActions> mExtension;
private IStatusBarService mBarService;
@Inject
- public GlobalActionsComponent(Context context, CommandQueue commandQueue) {
+ public GlobalActionsComponent(Context context, CommandQueue commandQueue,
+ ExtensionController extensionController,
+ Provider<GlobalActions> globalActionsProvider) {
super(context);
mCommandQueue = commandQueue;
+ mExtensionController = extensionController;
+ mGlobalActionsProvider = globalActionsProvider;
}
@Override
public void start() {
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
- mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class)
+ mExtension = mExtensionController.newExtension(GlobalActions.class)
.withPlugin(GlobalActions.class)
- .withDefault(() -> new GlobalActionsImpl(mContext, mCommandQueue))
+ .withDefault(mGlobalActionsProvider::get)
.withCallback(this::onExtensionCallback)
.build();
mPlugin = mExtension.get();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3ccad64..c138462 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -21,8 +21,10 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Dialog;
+import android.app.IActivityManager;
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
@@ -30,11 +32,13 @@
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
@@ -44,20 +48,17 @@
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
-import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.sysprop.TelephonyProperties;
import android.telecom.TelecomManager;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import android.util.ArraySet;
import android.util.FeatureFlagUtils;
import android.util.Log;
@@ -92,6 +93,7 @@
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
@@ -106,6 +108,8 @@
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+
/**
* Helper to show the global actions dialog. Each item is an {@link Action} that
* may show depending on whether the keyguard is showing, and whether the device
@@ -146,6 +150,13 @@
private final LockPatternUtils mLockPatternUtils;
private final KeyguardManager mKeyguardManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ContentResolver mContentResolver;
+ private final Resources mResources;
+ private final UserManager mUserManager;
+ private final TrustManager mTrustManager;
+ private final IActivityManager mIActivityManager;
+ private final TelecomManager mTelecomManager;
+ private final MetricsLogger mMetricsLogger;
private ArrayList<Action> mItems;
private ActionsDialog mDialog;
@@ -161,8 +172,6 @@
private boolean mIsWaitingForEcmExit = false;
private boolean mHasTelephony;
private boolean mHasVibrator;
- private boolean mHasLogoutButton;
- private boolean mHasLockdownButton;
private final boolean mShowSilentToggle;
private final EmergencyAffordanceManager mEmergencyAffordanceManager;
private final ScreenshotHelper mScreenshotHelper;
@@ -173,17 +182,32 @@
/**
* @param context everything needs a context :(
*/
- public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs) {
+ @Inject
+ public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs,
+ AudioManager audioManager, IDreamManager iDreamManager,
+ DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils,
+ KeyguardManager keyguardManager, BroadcastDispatcher broadcastDispatcher,
+ ConnectivityManager connectivityManager, TelephonyManager telephonyManager,
+ ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources,
+ ConfigurationController configurationController, ActivityStarter activityStarter,
+ KeyguardStateController keyguardStateController, UserManager userManager,
+ TrustManager trustManager, IActivityManager iActivityManager,
+ TelecomManager telecomManager, MetricsLogger metricsLogger) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
mWindowManagerFuncs = windowManagerFuncs;
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- mDreamManager = IDreamManager.Stub.asInterface(
- ServiceManager.getService(DreamService.DREAM_SERVICE));
- mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- mLockPatternUtils = new LockPatternUtils(mContext);
- mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
+ mAudioManager = audioManager;
+ mDreamManager = iDreamManager;
+ mDevicePolicyManager = devicePolicyManager;
+ mLockPatternUtils = lockPatternUtils;
+ mKeyguardManager = keyguardManager;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mContentResolver = contentResolver;
+ mResources = resources;
+ mUserManager = userManager;
+ mTrustManager = trustManager;
+ mIActivityManager = iActivityManager;
+ mTelecomManager = telecomManager;
+ mMetricsLogger = metricsLogger;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -192,32 +216,25 @@
filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
- ConnectivityManager cm = (ConnectivityManager)
- context.getSystemService(Context.CONNECTIVITY_SERVICE);
- mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ mHasTelephony = connectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
// get notified of phone state changes
- TelephonyManager telephonyManager =
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
- mContext.getContentResolver().registerContentObserver(
+ contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
mAirplaneModeObserver);
- Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = vibrator != null && vibrator.hasVibrator();
- mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
+ mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
R.bool.config_useFixedVolume);
mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
mScreenshotHelper = new ScreenshotHelper(context);
mScreenRecordHelper = new ScreenRecordHelper(context);
- Dependency.get(ConfigurationController.class).addCallback(this);
+ configurationController.addCallback(this);
- mActivityStarter = Dependency.get(ActivityStarter.class);
- KeyguardStateController keyguardStateController =
- Dependency.get(KeyguardStateController.class);
+ mActivityStarter = activityStarter;
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
@@ -343,12 +360,9 @@
onAirplaneModeChanged();
mItems = new ArrayList<Action>();
- String[] defaultActions = mContext.getResources().getStringArray(
- R.array.config_globalActionsList);
+ String[] defaultActions = mResources.getStringArray(R.array.config_globalActionsList);
ArraySet<String> addedKeys = new ArraySet<String>();
- mHasLogoutButton = false;
- mHasLockdownButton = false;
for (int i = 0; i < defaultActions.length; i++) {
String actionKey = defaultActions[i];
if (addedKeys.contains(actionKey)) {
@@ -360,7 +374,7 @@
} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
mItems.add(mAirplaneModeOn);
} else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
- if (Settings.Global.getInt(mContext.getContentResolver(),
+ if (Settings.Global.getInt(mContentResolver,
Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
mItems.add(new BugReportAction());
}
@@ -375,11 +389,10 @@
} else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
mItems.add(getSettingsAction());
} else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
- if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ if (Settings.Secure.getIntForUser(mContentResolver,
Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
&& shouldDisplayLockdown()) {
mItems.add(getLockdownAction());
- mHasLockdownButton = true;
}
} else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
mItems.add(getVoiceAssistAction());
@@ -393,7 +406,6 @@
if (mDevicePolicyManager.isLogoutEnabled()
&& getCurrentUser().id != UserHandle.USER_SYSTEM) {
mItems.add(new LogoutAction());
- mHasLogoutButton = true;
}
} else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) {
@@ -431,7 +443,8 @@
mKeyguardManager.isDeviceLocked())
: null;
- ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController);
+ ActionsDialog dialog = new ActionsDialog(
+ mContext, mAdapter, panelViewController, isControlsEnabled(mContext));
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
dialog.setKeyguardShowing(mKeyguardShowing);
@@ -474,8 +487,7 @@
@Override
public boolean onLongPress() {
- UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
mWindowManagerFuncs.reboot(true);
return true;
}
@@ -506,7 +518,7 @@
@Override
public boolean shouldBeSeparated() {
- return shouldUseSeparatedView();
+ return !isControlsEnabled(mContext);
}
@Override
@@ -560,9 +572,8 @@
@Override
public void onPress() {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
- Intent intent = mContext.getSystemService(TelecomManager.class)
- .createLaunchEmergencyDialerIntent(null /* number */);
+ mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
+ Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(null /* number */);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -579,8 +590,7 @@
@Override
public boolean onLongPress() {
- UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
mWindowManagerFuncs.reboot(true);
return true;
}
@@ -618,8 +628,7 @@
@Override
public void run() {
mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null);
- MetricsLogger.action(mContext,
- MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
+ mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
}
}, 500);
}
@@ -666,11 +675,11 @@
public void run() {
try {
// Take an "interactive" bugreport.
- MetricsLogger.action(mContext,
+ mMetricsLogger.action(
MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
- if (!ActivityManager.getService().launchBugReportHandlerApp()) {
+ if (!mIActivityManager.launchBugReportHandlerApp()) {
Log.w(TAG, "Bugreport handler could not be launched");
- ActivityManager.getService().requestInteractiveBugReport();
+ mIActivityManager.requestInteractiveBugReport();
}
} catch (RemoteException e) {
}
@@ -687,8 +696,8 @@
}
try {
// Take a "full" bugreport.
- MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
- ActivityManager.getService().requestFullBugReport();
+ mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
+ mIActivityManager.requestFullBugReport();
} catch (RemoteException e) {
}
return false;
@@ -726,8 +735,8 @@
mHandler.postDelayed(() -> {
try {
int currentUserId = getCurrentUser().id;
- ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM);
- ActivityManager.getService().stopUser(currentUserId, true /*force*/, null);
+ mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
+ mIActivityManager.stopUser(currentUserId, true /*force*/, null);
} catch (RemoteException re) {
Log.e(TAG, "Couldn't logout user " + re);
}
@@ -834,20 +843,18 @@
}
private void lockProfiles() {
- final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- final TrustManager tm = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
final int currentUserId = getCurrentUser().id;
- final int[] profileIds = um.getEnabledProfileIds(currentUserId);
+ final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId);
for (final int id : profileIds) {
if (id != currentUserId) {
- tm.setDeviceLockedForUser(id, true);
+ mTrustManager.setDeviceLockedForUser(id, true);
}
}
}
private UserInfo getCurrentUser() {
try {
- return ActivityManager.getService().getCurrentUser();
+ return mIActivityManager.getCurrentUser();
} catch (RemoteException re) {
return null;
}
@@ -859,9 +866,8 @@
}
private void addUsersToMenu(ArrayList<Action> items) {
- UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (um.isUserSwitcherEnabled()) {
- List<UserInfo> users = um.getUsers();
+ if (mUserManager.isUserSwitcherEnabled()) {
+ List<UserInfo> users = mUserManager.getUsers();
UserInfo currentUser = getCurrentUser();
for (final UserInfo user : users) {
if (user.supportsSwitchToByUser()) {
@@ -875,7 +881,7 @@
+ (isCurrentUser ? " \u2714" : "")) {
public void onPress() {
try {
- ActivityManager.getService().switchUser(user.id);
+ mIActivityManager.switchUser(user.id);
} catch (RemoteException re) {
Log.e(TAG, "Couldn't switch user " + re);
}
@@ -932,7 +938,7 @@
/** {@inheritDoc} */
public void onShow(DialogInterface dialog) {
- MetricsLogger.visible(mContext, MetricsEvent.POWER_MENU);
+ mMetricsLogger.visible(MetricsEvent.POWER_MENU);
}
/**
@@ -1148,6 +1154,9 @@
}
protected int getActionLayoutId(Context context) {
+ if (isControlsEnabled(context)) {
+ return com.android.systemui.R.layout.global_actions_grid_item_v2;
+ }
return com.android.systemui.R.layout.global_actions_grid_item;
}
@@ -1160,13 +1169,6 @@
TextView messageView = (TextView) v.findViewById(R.id.message);
messageView.setSelected(true); // necessary for marquee to work
- TextView statusView = (TextView) v.findViewById(R.id.status);
- final String status = getStatus();
- if (!TextUtils.isEmpty(status)) {
- statusView.setText(status);
- } else {
- statusView.setVisibility(View.GONE);
- }
if (mIcon != null) {
icon.setImageDrawable(mIcon);
icon.setScaleType(ScaleType.CENTER_CROP);
@@ -1251,32 +1253,26 @@
LayoutInflater inflater) {
willCreate();
- View v = inflater.inflate(R
- .layout.global_actions_item, parent, false);
+ View v = inflater.inflate(com.android.systemui.R
+ .layout.global_actions_grid_item, parent, false);
ImageView icon = (ImageView) v.findViewById(R.id.icon);
TextView messageView = (TextView) v.findViewById(R.id.message);
- TextView statusView = (TextView) v.findViewById(R.id.status);
final boolean enabled = isEnabled();
+ boolean on = ((mState == State.On) || (mState == State.TurningOn));
if (messageView != null) {
- messageView.setText(mMessageResId);
+ messageView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
messageView.setEnabled(enabled);
messageView.setSelected(true); // necessary for marquee to work
}
- boolean on = ((mState == State.On) || (mState == State.TurningOn));
if (icon != null) {
icon.setImageDrawable(context.getDrawable(
(on ? mEnabledIconResId : mDisabledIconResid)));
icon.setEnabled(enabled);
}
- if (statusView != null) {
- statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
- statusView.setVisibility(View.VISIBLE);
- statusView.setEnabled(enabled);
- }
v.setEnabled(enabled);
return v;
@@ -1422,8 +1418,8 @@
} else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
// Airplane mode can be changed after ECM exits if airplane toggle button
// is pressed during ECM mode
- if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
- mIsWaitingForEcmExit) {
+ if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false))
+ && mIsWaitingForEcmExit) {
mIsWaitingForEcmExit = false;
changeAirplaneModeSystemSetting(true);
}
@@ -1492,7 +1488,7 @@
if (mHasTelephony) return;
boolean airplaneModeOn = Settings.Global.getInt(
- mContext.getContentResolver(),
+ mContentResolver,
Settings.Global.AIRPLANE_MODE_ON,
0) == 1;
mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
@@ -1504,7 +1500,7 @@
*/
private void changeAirplaneModeSystemSetting(boolean on) {
Settings.Global.putInt(
- mContext.getContentResolver(),
+ mContentResolver,
Settings.Global.AIRPLANE_MODE_ON,
on ? 1 : 0);
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
@@ -1533,15 +1529,18 @@
private ResetOrientationData mResetOrientationData;
private boolean mHadTopUi;
private final StatusBarWindowController mStatusBarWindowController;
+ private boolean mControlsEnabled;
ActionsDialog(Context context, MyAdapter adapter,
- GlobalActionsPanelPlugin.PanelViewController plugin) {
+ GlobalActionsPanelPlugin.PanelViewController plugin,
+ boolean controlsEnabled) {
super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
mContext = context;
mAdapter = adapter;
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mStatusBarService = Dependency.get(IStatusBarService.class);
mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
+ mControlsEnabled = controlsEnabled;
// Window initialization
Window window = getWindow();
@@ -1658,6 +1657,10 @@
}
private int getGlobalActionsLayoutId(Context context) {
+ if (mControlsEnabled) {
+ return com.android.systemui.R.layout.global_actions_grid_v2;
+ }
+
int rotation = RotationUtils.getRotation(context);
boolean useGridLayout = isForceGridEnabled(context)
|| (shouldUsePanel() && rotation == RotationUtils.ROTATION_NONE);
@@ -1861,4 +1864,9 @@
private static boolean shouldUseSeparatedView() {
return true;
}
+
+ private static boolean isControlsEnabled(Context context) {
+ return Settings.Secure.getInt(
+ context.getContentResolver(), "systemui.controls_available", 0) == 1;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
new file mode 100644
index 0000000..6749f1d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.globalactions;
+
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Single row implementation of the button layout created by the global actions dialog.
+ */
+public class GlobalActionsFlatLayout extends GlobalActionsLayout {
+ public GlobalActionsFlatLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mBackgroundsSet = true;
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ // backgrounds set only once, the first time onMeasure is called after inflation
+ // if (getListView() != null && !mBackgroundsSet) {
+ // setBackgrounds();
+ // mBackgroundsSet = true;
+ // }
+ }
+
+ @VisibleForTesting
+ protected void setupListView() {
+ ListGridLayout listView = getListView();
+ listView.setExpectedCount(Math.min(2, mAdapter.countListItems()));
+ listView.setReverseSublists(shouldReverseSublists());
+ listView.setReverseItems(shouldReverseListItems());
+ listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns());
+ }
+
+ @Override
+ public void onUpdateList() {
+ setupListView();
+ super.onUpdateList();
+ updateSeparatedItemSize();
+ }
+
+ /**
+ * If the separated view contains only one item, expand the bounds of that item to take up the
+ * entire view, so that the whole thing is touch-able.
+ */
+ @VisibleForTesting
+ protected void updateSeparatedItemSize() {
+ ViewGroup separated = getSeparatedView();
+ if (separated.getChildCount() == 0) {
+ return;
+ }
+ View firstChild = separated.getChildAt(0);
+ ViewGroup.LayoutParams childParams = firstChild.getLayoutParams();
+
+ if (separated.getChildCount() == 1) {
+ childParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ childParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ } else {
+ childParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ childParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ }
+ }
+
+ @Override
+ protected ListGridLayout getListView() {
+ return (ListGridLayout) super.getListView();
+ }
+
+ @Override
+ protected void removeAllListViews() {
+ ListGridLayout list = getListView();
+ if (list != null) {
+ list.removeAllItems();
+ }
+ }
+
+ @Override
+ protected void addToListView(View v, boolean reverse) {
+ ListGridLayout list = getListView();
+ if (list != null) {
+ list.addItem(v);
+ }
+ }
+
+ @Override
+ public void removeAllItems() {
+ ViewGroup separatedList = getSeparatedView();
+ ListGridLayout list = getListView();
+ if (separatedList != null) {
+ separatedList.removeAllViews();
+ }
+ if (list != null) {
+ list.removeAllItems();
+ }
+ }
+
+ /**
+ * Determines whether the ListGridLayout should fill sublists in the reverse order.
+ * Used to account for sublist ordering changing between landscape and seascape views.
+ */
+ @VisibleForTesting
+ protected boolean shouldReverseSublists() {
+ if (getCurrentRotation() == ROTATION_SEASCAPE) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether the ListGridLayout should fill rows first instead of columns.
+ * Used to account for vertical/horizontal changes due to landscape or seascape rotations.
+ */
+ @VisibleForTesting
+ protected boolean shouldSwapRowsAndColumns() {
+ if (getCurrentRotation() == ROTATION_NONE) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean shouldReverseListItems() {
+ int rotation = getCurrentRotation();
+ boolean reverse = false; // should we add items to parents in the reverse order?
+ if (rotation == ROTATION_NONE
+ || rotation == ROTATION_SEASCAPE) {
+ reverse = !reverse; // if we're in portrait or seascape, reverse items
+ }
+ if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ reverse = !reverse; // if we're in an RTL language, reverse items (again)
+ }
+ return reverse;
+ }
+
+ @VisibleForTesting
+ protected float getAnimationDistance() {
+ int rows = getListView().getRowCount();
+ float gridItemSize = getContext().getResources().getDimension(
+ com.android.systemui.R.dimen.global_actions_grid_item_height);
+ return rows * gridItemSize / 2;
+ }
+
+ @Override
+ public float getAnimationOffsetX() {
+ switch (getCurrentRotation()) {
+ case ROTATION_LANDSCAPE:
+ return getAnimationDistance();
+ case ROTATION_SEASCAPE:
+ return -getAnimationDistance();
+ default: // Portrait
+ return 0;
+ }
+ }
+
+ @Override
+ public float getAnimationOffsetY() {
+ if (getCurrentRotation() == ROTATION_NONE) {
+ return getAnimationDistance();
+ }
+ return 0;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index d385123..c911bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -45,24 +45,32 @@
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks,
PluginListener<GlobalActionsPanelPlugin> {
private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f;
private final Context mContext;
+ private final Lazy<GlobalActionsDialog> mGlobalActionsDialogLazy;
private final KeyguardStateController mKeyguardStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension;
private GlobalActionsPanelPlugin mPlugin;
private final CommandQueue mCommandQueue;
- private GlobalActionsDialog mGlobalActions;
+ private GlobalActionsDialog mGlobalActionsDialog;
private boolean mDisabled;
private final PluginManager mPluginManager;
private final String mPluginPackageName;
- public GlobalActionsImpl(Context context, CommandQueue commandQueue) {
+ @Inject
+ public GlobalActionsImpl(Context context, CommandQueue commandQueue,
+ Lazy<GlobalActionsDialog> globalActionsDialogLazy) {
mContext = context;
+ mGlobalActionsDialogLazy = globalActionsDialogLazy;
mKeyguardStateController = Dependency.get(KeyguardStateController.class);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
mPluginManager = Dependency.get(PluginManager.class);
@@ -83,19 +91,17 @@
mCommandQueue.removeCallback(this);
mPluginManager.removePluginListener(this);
if (mPlugin != null) mPlugin.onDestroy();
- if (mGlobalActions != null) {
- mGlobalActions.destroy();
- mGlobalActions = null;
+ if (mGlobalActionsDialog != null) {
+ mGlobalActionsDialog.destroy();
+ mGlobalActionsDialog = null;
}
}
@Override
public void showGlobalActions(GlobalActionsManager manager) {
if (mDisabled) return;
- if (mGlobalActions == null) {
- mGlobalActions = new GlobalActionsDialog(mContext, manager);
- }
- mGlobalActions.showDialog(mKeyguardStateController.isShowing(),
+ mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
+ mGlobalActionsDialog.showDialog(mKeyguardStateController.isShowing(),
mDeviceProvisionedController.isDeviceProvisioned(),
mPlugin != null ? mPlugin : mPanelExtension.get());
Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
@@ -189,8 +195,8 @@
final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
if (displayId != mContext.getDisplayId() || disabled == mDisabled) return;
mDisabled = disabled;
- if (disabled && mGlobalActions != null) {
- mGlobalActions.dismissDialog();
+ if (disabled && mGlobalActionsDialog != null) {
+ mGlobalActionsDialog.dismissDialog();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index f06c849..2b53727 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -42,6 +42,7 @@
import com.android.systemui.qs.tiles.NfcTile;
import com.android.systemui.qs.tiles.NightDisplayTile;
import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.ScreenRecordTile;
import com.android.systemui.qs.tiles.UiModeNightTile;
import com.android.systemui.qs.tiles.UserTile;
import com.android.systemui.qs.tiles.WifiTile;
@@ -77,6 +78,7 @@
private final Provider<NfcTile> mNfcTileProvider;
private final Provider<GarbageMonitor.MemoryTile> mMemoryTileProvider;
private final Provider<UiModeNightTile> mUiModeNightTileProvider;
+ private final Provider<ScreenRecordTile> mScreenRecordTileProvider;
private QSTileHost mHost;
@@ -100,7 +102,8 @@
Provider<NightDisplayTile> nightDisplayTileProvider,
Provider<NfcTile> nfcTileProvider,
Provider<GarbageMonitor.MemoryTile> memoryTileProvider,
- Provider<UiModeNightTile> uiModeNightTileProvider) {
+ Provider<UiModeNightTile> uiModeNightTileProvider,
+ Provider<ScreenRecordTile> screenRecordTileProvider) {
mWifiTileProvider = wifiTileProvider;
mBluetoothTileProvider = bluetoothTileProvider;
mControlsTileProvider = controlsTileProvider;
@@ -121,6 +124,7 @@
mNfcTileProvider = nfcTileProvider;
mMemoryTileProvider = memoryTileProvider;
mUiModeNightTileProvider = uiModeNightTileProvider;
+ mScreenRecordTileProvider = screenRecordTileProvider;
}
public void setHost(QSTileHost host) {
@@ -179,6 +183,8 @@
return mNfcTileProvider.get();
case "dark":
return mUiModeNightTileProvider.get();
+ case "screenrecord":
+ return mScreenRecordTileProvider.get();
}
// Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
new file mode 100644
index 0000000..596c3b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.Intent;
+import android.service.quicksettings.Tile;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.screenrecord.RecordingController;
+
+import javax.inject.Inject;
+
+/**
+ * Quick settings tile for screen recording
+ */
+public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> {
+ private static final String TAG = "ScreenRecordTile";
+ private RecordingController mController;
+ private long mMillisUntilFinished = 0;
+
+ @Inject
+ public ScreenRecordTile(QSHost host, RecordingController controller) {
+ super(host);
+ mController = controller;
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+ if (mController.isStarting()) {
+ cancelCountdown();
+ } else if (mController.isRecording()) {
+ stopRecording();
+ } else {
+ startCountdown();
+ }
+ refreshState();
+ }
+
+ /**
+ * Refresh tile state
+ * @param millisUntilFinished Time until countdown completes, or 0 if not counting down
+ */
+ public void refreshState(long millisUntilFinished) {
+ mMillisUntilFinished = millisUntilFinished;
+ refreshState();
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ boolean isStarting = mController.isStarting();
+ boolean isRecording = mController.isRecording();
+
+ state.label = mContext.getString(R.string.quick_settings_screen_record_label);
+ state.value = isRecording || isStarting;
+ state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.handlesLongClick = false;
+
+ if (isRecording) {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+ state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_stop);
+ } else if (isStarting) {
+ // round, since the timer isn't exact
+ int countdown = (int) Math.floorDiv(mMillisUntilFinished + 500, 1000);
+ // TODO update icon
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+ state.secondaryLabel = String.format("%d...", countdown);
+ } else {
+ // TODO update icon
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+ state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return 0;
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return null;
+ }
+
+ @Override
+ protected void handleSetListening(boolean listening) {
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mContext.getString(R.string.quick_settings_screen_record_label);
+ }
+
+ private void startCountdown() {
+ Log.d(TAG, "Starting countdown");
+ // Close QS, otherwise the permission dialog appears beneath it
+ getHost().collapsePanels();
+ mController.launchRecordPrompt(this);
+ }
+
+ private void cancelCountdown() {
+ Log.d(TAG, "Cancelling countdown");
+ mController.cancelCountdown();
+ }
+
+ private void stopRecording() {
+ Log.d(TAG, "Stopping recording from tile");
+ mController.stopRecording();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
new file mode 100644
index 0000000..188501e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.CountDownTimer;
+import android.util.Log;
+
+import com.android.systemui.qs.tiles.ScreenRecordTile;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Helper class to initiate a screen recording
+ */
+@Singleton
+public class RecordingController {
+ private static final String TAG = "RecordingController";
+ private static final String SYSUI_PACKAGE = "com.android.systemui";
+ private static final String SYSUI_SCREENRECORD_LAUNCHER =
+ "com.android.systemui.screenrecord.ScreenRecordDialog";
+
+ private final Context mContext;
+ private boolean mIsStarting;
+ private boolean mIsRecording;
+ private ScreenRecordTile mTileToUpdate;
+ private PendingIntent mStopIntent;
+ private CountDownTimer mCountDownTimer = null;
+
+ /**
+ * Create a new RecordingController
+ * @param context Context for the controller
+ */
+ @Inject
+ public RecordingController(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Show dialog of screen recording options to user.
+ */
+ public void launchRecordPrompt(ScreenRecordTile tileToUpdate) {
+ final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
+ SYSUI_SCREENRECORD_LAUNCHER);
+ final Intent intent = new Intent();
+ intent.setComponent(launcherComponent);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra("com.android.systemui.screenrecord.EXTRA_SETTINGS_ONLY", true);
+ mContext.startActivity(intent);
+
+ mTileToUpdate = tileToUpdate;
+ }
+
+ /**
+ * Start counting down in preparation to start a recording
+ * @param ms Time in ms to count down
+ * @param startIntent Intent to start a recording
+ * @param stopIntent Intent to stop a recording
+ */
+ public void startCountdown(long ms, PendingIntent startIntent, PendingIntent stopIntent) {
+ mIsStarting = true;
+ mStopIntent = stopIntent;
+
+ mCountDownTimer = new CountDownTimer(ms, 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ refreshTile(millisUntilFinished);
+ }
+
+ @Override
+ public void onFinish() {
+ mIsStarting = false;
+ mIsRecording = true;
+ refreshTile();
+ try {
+ startIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
+ }
+ }
+ };
+
+ mCountDownTimer.start();
+ }
+
+ private void refreshTile() {
+ refreshTile(0);
+ }
+
+ private void refreshTile(long millisUntilFinished) {
+ if (mTileToUpdate != null) {
+ mTileToUpdate.refreshState(millisUntilFinished);
+ } else {
+ Log.e(TAG, "No tile to refresh");
+ }
+ }
+
+ /**
+ * Cancel a countdown in progress. This will not stop the recording if it already started.
+ */
+ public void cancelCountdown() {
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ } else {
+ Log.e(TAG, "Timer was null");
+ }
+ mIsStarting = false;
+ refreshTile();
+ }
+
+ /**
+ * Check if the recording is currently counting down to begin
+ * @return
+ */
+ public boolean isStarting() {
+ return mIsStarting;
+ }
+
+ /**
+ * Check if the recording is ongoing
+ * @return
+ */
+ public boolean isRecording() {
+ return mIsRecording;
+ }
+
+ /**
+ * Stop the recording
+ */
+ public void stopRecording() {
+ updateState(false);
+ try {
+ mStopIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Error stopping: " + e.getMessage());
+ }
+ refreshTile();
+ }
+
+ /**
+ * Update the current status
+ * @param isRecording
+ */
+ public void updateState(boolean isRecording) {
+ mIsRecording = isRecording;
+ refreshTile();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 77c3ad9..1b32168 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -53,10 +53,14 @@
import java.text.SimpleDateFormat;
import java.util.Date;
+import javax.inject.Inject;
+
/**
* A service which records the device screen and optionally microphone input.
*/
public class RecordingService extends Service {
+ public static final int REQUEST_CODE = 2;
+
private static final int NOTIFICATION_ID = 1;
private static final String TAG = "RecordingService";
private static final String CHANNEL_ID = "screen_record";
@@ -65,7 +69,6 @@
private static final String EXTRA_PATH = "extra_path";
private static final String EXTRA_USE_AUDIO = "extra_useAudio";
private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
- private static final int REQUEST_CODE = 2;
private static final String ACTION_START = "com.android.systemui.screenrecord.START";
private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
@@ -81,6 +84,7 @@
private static final int AUDIO_BIT_RATE = 16;
private static final int AUDIO_SAMPLE_RATE = 44100;
+ private final RecordingController mController;
private MediaProjectionManager mMediaProjectionManager;
private MediaProjection mMediaProjection;
private Surface mInputSurface;
@@ -92,6 +96,11 @@
private boolean mShowTaps;
private File mTempFile;
+ @Inject
+ public RecordingService(RecordingController controller) {
+ mController = controller;
+ }
+
/**
* Get an intent to start the recording service.
*
@@ -272,6 +281,7 @@
null);
mMediaRecorder.start();
+ mController.updateState(true);
} catch (IOException e) {
Log.e(TAG, "Error starting screen recording: " + e.getMessage());
e.printStackTrace();
@@ -285,7 +295,7 @@
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
getString(R.string.screenrecord_name),
- NotificationManager.IMPORTANCE_HIGH);
+ NotificationManager.IMPORTANCE_LOW);
channel.setDescription(getString(R.string.screenrecord_channel_description));
channel.enableVibration(true);
NotificationManager notificationManager =
@@ -399,6 +409,7 @@
mInputSurface.release();
mVirtualDisplay.release();
stopSelf();
+ mController.updateState(false);
}
private void saveRecording(NotificationManager notificationManager) {
@@ -439,7 +450,12 @@
Settings.System.SHOW_TOUCHES, value);
}
- private static Intent getStopIntent(Context context) {
+ /**
+ * Get an intent to stop the recording service.
+ * @param context Context from the requesting activity
+ * @return
+ */
+ public static Intent getStopIntent(Context context) {
return new Intent(context, RecordingService.class).setAction(ACTION_STOP);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index 27e9fba..8324986 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -18,55 +18,41 @@
import android.Manifest;
import android.app.Activity;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
-import android.util.Log;
-import android.widget.Button;
-import android.widget.CheckBox;
import android.widget.Toast;
import com.android.systemui.R;
+import javax.inject.Inject;
+
/**
* Activity to select screen recording options
*/
public class ScreenRecordDialog extends Activity {
- private static final String TAG = "ScreenRecord";
private static final int REQUEST_CODE_VIDEO_ONLY = 200;
private static final int REQUEST_CODE_VIDEO_TAPS = 201;
private static final int REQUEST_CODE_PERMISSIONS = 299;
private static final int REQUEST_CODE_VIDEO_AUDIO = 300;
private static final int REQUEST_CODE_VIDEO_AUDIO_TAPS = 301;
private static final int REQUEST_CODE_PERMISSIONS_AUDIO = 399;
- private boolean mUseAudio;
- private boolean mShowTaps;
+ private static final long DELAY_MS = 3000;
+
+ private final RecordingController mController;
+
+ @Inject
+ public ScreenRecordDialog(RecordingController controller) {
+ mController = controller;
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.screen_record_dialog);
-
- final CheckBox micCheckBox = findViewById(R.id.checkbox_mic);
- final CheckBox tapsCheckBox = findViewById(R.id.checkbox_taps);
-
- final Button recordButton = findViewById(R.id.record_button);
- recordButton.setOnClickListener(v -> {
- mUseAudio = micCheckBox.isChecked();
- mShowTaps = tapsCheckBox.isChecked();
- Log.d(TAG, "Record button clicked: audio " + mUseAudio + ", taps " + mShowTaps);
-
- if (mUseAudio && checkSelfPermission(Manifest.permission.RECORD_AUDIO)
- != PackageManager.PERMISSION_GRANTED) {
- Log.d(TAG, "Requesting permission for audio");
- requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
- REQUEST_CODE_PERMISSIONS_AUDIO);
- } else {
- requestScreenCapture();
- }
- });
+ requestScreenCapture();
}
private void requestScreenCapture() {
@@ -74,18 +60,23 @@
Context.MEDIA_PROJECTION_SERVICE);
Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();
- if (mUseAudio) {
+ // TODO get saved settings
+ boolean useAudio = false;
+ boolean showTaps = false;
+ if (useAudio) {
startActivityForResult(permissionIntent,
- mShowTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
+ showTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
} else {
startActivityForResult(permissionIntent,
- mShowTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
+ showTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- mShowTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
+ boolean showTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
+ || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
+ boolean useAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
|| requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
switch (requestCode) {
case REQUEST_CODE_VIDEO_TAPS:
@@ -93,11 +84,17 @@
case REQUEST_CODE_VIDEO_ONLY:
case REQUEST_CODE_VIDEO_AUDIO:
if (resultCode == RESULT_OK) {
- mUseAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
- || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
- startForegroundService(
- RecordingService.getStartIntent(this, resultCode, data, mUseAudio,
- mShowTaps));
+ PendingIntent startIntent = PendingIntent.getForegroundService(
+ this, RecordingService.REQUEST_CODE, RecordingService.getStartIntent(
+ ScreenRecordDialog.this, resultCode, data, useAudio,
+ showTaps),
+ PendingIntent.FLAG_UPDATE_CURRENT
+ );
+ PendingIntent stopIntent = PendingIntent.getService(
+ this, RecordingService.REQUEST_CODE,
+ RecordingService.getStopIntent(this),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mController.startCountdown(DELAY_MS, startIntent, stopIntent);
} else {
Toast.makeText(this,
getResources().getString(R.string.screenrecord_permission_error),
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 02c4beb..27c9555 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -20,7 +20,7 @@
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED;
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
import android.animation.Animator;
@@ -64,7 +64,6 @@
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
import android.widget.Toast;
import com.android.systemui.R;
@@ -246,20 +245,13 @@
mSaveInBgTask.cancel(false);
}
- if (!DeviceConfig.getBoolean(
- NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false)) {
- mNotificationsController.reset();
- mNotificationsController.setImage(mScreenBitmap);
- mNotificationsController.showSavingScreenshotNotification();
- }
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
}
/**
* Takes a screenshot of the current display and shows an animation.
*/
- private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
- boolean navBarVisible, Rect crop) {
+ private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
int rot = mDisplay.getRotation();
int width = crop.width();
int height = crop.height();
@@ -278,21 +270,20 @@
mScreenBitmap.prepareToDraw();
// Start the post-screenshot animation
- startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
- statusBarVisible, navBarVisible);
+ startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
}
- void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
+ void takeScreenshot(Consumer<Uri> finisher) {
mDisplay.getRealMetrics(mDisplayMetrics);
- takeScreenshot(finisher, statusBarVisible, navBarVisible,
+ takeScreenshot(
+ finisher,
new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
}
/**
* Displays a screenshot selector
*/
- void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
- final boolean navBarVisible) {
+ void takeScreenshotPartial(final Consumer<Uri> finisher) {
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
@Override
@@ -312,8 +303,7 @@
if (rect != null) {
if (rect.width() != 0 && rect.height() != 0) {
// Need mScreenshotLayout to handle it after the view disappears
- mScreenshotLayout.post(() -> takeScreenshot(
- finisher, statusBarVisible, navBarVisible, rect));
+ mScreenshotLayout.post(() -> takeScreenshot(finisher, rect));
}
}
@@ -364,8 +354,7 @@
/**
* Starts the animation after taking the screenshot
*/
- private void startAnimation(final Consumer<Uri> finisher, int w, int h,
- boolean statusBarVisible, boolean navBarVisible) {
+ private void startAnimation(final Consumer<Uri> finisher, int w, int h) {
// If power save is on, show a toast so there is some visual indication that a screenshot
// has been taken.
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -385,50 +374,30 @@
mScreenshotAnimation.removeAllListeners();
}
- boolean useCornerFlow =
- DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
- ValueAnimator screenshotFadeOutAnim = useCornerFlow
- ? createScreenshotToCornerAnimation(w, h)
- : createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible);
+ ValueAnimator screenshotFadeOutAnim = createScreenshotToCornerAnimation(w, h);
mScreenshotAnimation = new AnimatorSet();
mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Save the screenshot once we have a bit of time now
- if (!useCornerFlow) {
- saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
- @Override
- void onActionsReady(Uri uri, List<Notification.Action> actions) {
- if (uri == null) {
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- } else {
- mNotificationsController
- .showScreenshotActionsNotification(uri, actions);
- }
+ saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+ @Override
+ void onActionsReady(Uri uri, List<Notification.Action> actions) {
+ if (uri == null) {
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ } else {
+ mScreenshotHandler.post(() ->
+ createScreenshotActionsShadeAnimation(actions).start());
}
- });
- clearScreenshot();
- } else {
- saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
- @Override
- void onActionsReady(Uri uri, List<Notification.Action> actions) {
- if (uri == null) {
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- } else {
- mScreenshotHandler.post(() ->
- createScreenshotActionsShadeAnimation(actions).start());
- }
- }
- });
- mScreenshotHandler.sendMessageDelayed(
- mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
- SCREENSHOT_CORNER_TIMEOUT_MILLIS);
- }
+ }
+ });
+ mScreenshotHandler.sendMessageDelayed(
+ mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
+ SCREENSHOT_CORNER_TIMEOUT_MILLIS);
}
});
mScreenshotHandler.post(() -> {
@@ -492,7 +461,7 @@
}
@Override
- public void onAnimationEnd(android.animation.Animator animation) {
+ public void onAnimationEnd(Animator animation) {
mScreenshotFlash.setVisibility(View.GONE);
}
});
@@ -513,81 +482,6 @@
return anim;
}
- private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
- boolean navBarVisible) {
- ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
- anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mBackgroundView.setVisibility(View.GONE);
- mScreenshotView.setVisibility(View.GONE);
- mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- });
-
- if (!statusBarVisible || !navBarVisible) {
- // There is no status bar/nav bar, so just fade the screenshot away in place
- anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
- anim.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float t = (Float) animation.getAnimatedValue();
- float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
- - t * (SCREENSHOT_DROP_IN_MIN_SCALE
- - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
- mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
- mScreenshotView.setAlpha(1f - t);
- mScreenshotView.setScaleX(scaleT);
- mScreenshotView.setScaleY(scaleT);
- }
- });
- } else {
- // In the case where there is a status bar, animate to the origin of the bar (top-left)
- final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
- / SCREENSHOT_DROP_OUT_DURATION;
- final Interpolator scaleInterpolator = new Interpolator() {
- @Override
- public float getInterpolation(float x) {
- if (x < scaleDurationPct) {
- // Decelerate, and scale the input accordingly
- return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
- }
- return 1f;
- }
- };
-
- // Determine the bounds of how to scale
- float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
- float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
- final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
- final PointF finalPos = new PointF(
- -halfScreenWidth
- + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
- -halfScreenHeight
- + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
-
- // Animate the screenshot to the status bar
- anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
- anim.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float t = (Float) animation.getAnimatedValue();
- float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
- - scaleInterpolator.getInterpolation(t)
- * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
- mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
- mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
- mScreenshotView.setScaleX(scaleT);
- mScreenshotView.setScaleY(scaleT);
- mScreenshotView.setTranslationX(t * finalPos.x);
- mScreenshotView.setTranslationY(t * finalPos.y);
- }
- });
- }
- return anim;
- }
-
private ValueAnimator createScreenshotToCornerAnimation(int w, int h) {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
@@ -634,9 +528,10 @@
mActionsView.removeAllViews();
for (Notification.Action action : actions) {
- TextView actionChip = (TextView) inflater.inflate(
+ ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
R.layout.global_screenshot_action_chip, mActionsView, false);
actionChip.setText(action.title);
+ actionChip.setIcon(action.getIcon(), true);
actionChip.setOnClickListener(v -> {
try {
action.actionIntent.send();
@@ -648,13 +543,16 @@
});
mActionsView.addView(actionChip);
}
- TextView scrollChip = (TextView) inflater.inflate(
- R.layout.global_screenshot_action_chip, mActionsView, false);
- Toast scrollNotImplemented = Toast.makeText(
- mContext, "Not implemented", Toast.LENGTH_SHORT);
- scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate
- scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
- mActionsView.addView(scrollChip);
+
+ if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) {
+ ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate(
+ R.layout.global_screenshot_action_chip, mActionsView, false);
+ Toast scrollNotImplemented = Toast.makeText(
+ mContext, "Not implemented", Toast.LENGTH_SHORT);
+ scrollChip.setText("Extend"); // TODO (mkephart): add resource and translate
+ scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
+ mActionsView.addView(scrollChip);
+ }
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
mActionsView.setY(mDisplayMetrics.heightPixels);
@@ -776,8 +674,7 @@
String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
Slog.d(TAG, "Executing smart action [" + actionType + "]:" + actionIntent);
ActivityOptions opts = ActivityOptions.makeBasic();
- context.startActivityAsUser(actionIntent, opts.toBundle(),
- UserHandle.CURRENT);
+ context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
ScreenshotSmartActions.notifyScreenshotAction(
context, intent.getStringExtra(EXTRA_ID), actionType, true);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
new file mode 100644
index 0000000..11aa80b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.media.MediaActionSound;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.PowerManager;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Class for handling device screen shots
+ *
+ * @deprecated will be removed when corner flow is complete and tested
+ */
+@Singleton
+@Deprecated
+public class GlobalScreenshotLegacy {
+
+ // These strings are used for communicating the action invoked to
+ // ScreenshotNotificationSmartActionsProvider.
+ static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
+ static final String EXTRA_ID = "android:screenshot_id";
+ static final String ACTION_TYPE_DELETE = "Delete";
+ static final String ACTION_TYPE_SHARE = "Share";
+ static final String ACTION_TYPE_EDIT = "Edit";
+ static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+ static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+
+ static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
+ static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
+ static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
+
+ private static final String TAG = "GlobalScreenshot";
+
+ private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
+ private static final int SCREENSHOT_DROP_IN_DURATION = 430;
+ private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
+ private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
+ private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
+ private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
+ private static final float BACKGROUND_ALPHA = 0.5f;
+ private static final float SCREENSHOT_SCALE = 1f;
+ private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
+ private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
+ private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
+ private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
+
+ private final ScreenshotNotificationsController mNotificationsController;
+
+ private Context mContext;
+ private WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowLayoutParams;
+ private Display mDisplay;
+ private DisplayMetrics mDisplayMetrics;
+
+ private Bitmap mScreenBitmap;
+ private View mScreenshotLayout;
+ private ScreenshotSelectorView mScreenshotSelectorView;
+ private ImageView mBackgroundView;
+ private ImageView mScreenshotView;
+ private ImageView mScreenshotFlash;
+
+ private AnimatorSet mScreenshotAnimation;
+
+ private float mBgPadding;
+ private float mBgPaddingScale;
+
+ private AsyncTask<Void, Void, Void> mSaveInBgTask;
+
+ private MediaActionSound mCameraSound;
+
+ /**
+ * @param context everything needs a context :(
+ */
+ @Inject
+ public GlobalScreenshotLegacy(
+ Context context, @Main Resources resources, LayoutInflater layoutInflater,
+ ScreenshotNotificationsController screenshotNotificationsController) {
+ mContext = context;
+ mNotificationsController = screenshotNotificationsController;
+
+ // Inflate the screenshot layout
+ mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot_legacy, null);
+ mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_background);
+ mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy);
+
+ mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_flash);
+ mScreenshotSelectorView = mScreenshotLayout.findViewById(
+ R.id.global_screenshot_legacy_selector);
+ mScreenshotLayout.setFocusable(true);
+ mScreenshotSelectorView.setFocusable(true);
+ mScreenshotSelectorView.setFocusableInTouchMode(true);
+ mScreenshotLayout.setOnTouchListener((v, event) -> {
+ // Intercept and ignore all touch events
+ return true;
+ });
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
+ WindowManager.LayoutParams.TYPE_SCREENSHOT,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
+ PixelFormat.TRANSLUCENT);
+ mWindowLayoutParams.setTitle("ScreenshotAnimation");
+ mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */);
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mDisplay = mWindowManager.getDefaultDisplay();
+ mDisplayMetrics = new DisplayMetrics();
+ mDisplay.getRealMetrics(mDisplayMetrics);
+
+ // Scale has to account for both sides of the bg
+ mBgPadding = (float) resources.getDimensionPixelSize(
+ R.dimen.global_screenshot_legacy_bg_padding);
+ mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
+
+
+ // Setup the Camera shutter sound
+ mCameraSound = new MediaActionSound();
+ mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
+ }
+
+ /**
+ * Creates a new worker thread and saves the screenshot to the media store.
+ */
+ private void saveScreenshotInWorkerThread(
+ Consumer<Uri> finisher,
+ @Nullable GlobalScreenshot.ActionsReadyListener actionsReadyListener) {
+ GlobalScreenshot.SaveImageInBackgroundData data =
+ new GlobalScreenshot.SaveImageInBackgroundData();
+ data.image = mScreenBitmap;
+ data.finisher = finisher;
+ data.mActionsReadyListener = actionsReadyListener;
+ if (mSaveInBgTask != null) {
+ mSaveInBgTask.cancel(false);
+ }
+
+ mNotificationsController.reset();
+ mNotificationsController.setImage(mScreenBitmap);
+ mNotificationsController.showSavingScreenshotNotification();
+
+ mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
+ }
+
+ /**
+ * Takes a screenshot of the current display and shows an animation.
+ */
+ private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
+ boolean navBarVisible, Rect crop) {
+ int rot = mDisplay.getRotation();
+ int width = crop.width();
+ int height = crop.height();
+
+ // Take the screenshot
+ mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
+ if (mScreenBitmap == null) {
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ finisher.accept(null);
+ return;
+ }
+
+ // Optimizations
+ mScreenBitmap.setHasAlpha(false);
+ mScreenBitmap.prepareToDraw();
+
+ // Start the post-screenshot animation
+ startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+ statusBarVisible, navBarVisible);
+ }
+
+ void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
+ mDisplay.getRealMetrics(mDisplayMetrics);
+ takeScreenshot(finisher, statusBarVisible, navBarVisible,
+ new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
+ }
+
+ /**
+ * Displays a screenshot selector
+ */
+ void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
+ final boolean navBarVisible) {
+ mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+ mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ ScreenshotSelectorView view = (ScreenshotSelectorView) v;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ view.startSelection((int) event.getX(), (int) event.getY());
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ view.updateSelection((int) event.getX(), (int) event.getY());
+ return true;
+ case MotionEvent.ACTION_UP:
+ view.setVisibility(View.GONE);
+ mWindowManager.removeView(mScreenshotLayout);
+ final Rect rect = view.getSelectionRect();
+ if (rect != null) {
+ if (rect.width() != 0 && rect.height() != 0) {
+ // Need mScreenshotLayout to handle it after the view disappears
+ mScreenshotLayout.post(() -> takeScreenshot(
+ finisher, statusBarVisible, navBarVisible, rect));
+ }
+ }
+
+ view.stopSelection();
+ return true;
+ }
+
+ return false;
+ }
+ });
+ mScreenshotLayout.post(new Runnable() {
+ @Override
+ public void run() {
+ mScreenshotSelectorView.setVisibility(View.VISIBLE);
+ mScreenshotSelectorView.requestFocus();
+ }
+ });
+ }
+
+ /**
+ * Cancels screenshot request
+ */
+ void stopScreenshot() {
+ // If the selector layer still presents on screen, we remove it and resets its state.
+ if (mScreenshotSelectorView.getSelectionRect() != null) {
+ mWindowManager.removeView(mScreenshotLayout);
+ mScreenshotSelectorView.stopSelection();
+ }
+ }
+
+ /**
+ * Clears current screenshot
+ */
+ private void clearScreenshot() {
+ if (mScreenshotLayout.isAttachedToWindow()) {
+ mWindowManager.removeView(mScreenshotLayout);
+ }
+
+ // Clear any references to the bitmap
+ mScreenBitmap = null;
+ mScreenshotView.setImageBitmap(null);
+ mBackgroundView.setVisibility(View.GONE);
+ mScreenshotView.setVisibility(View.GONE);
+ mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+
+ /**
+ * Starts the animation after taking the screenshot
+ */
+ private void startAnimation(final Consumer<Uri> finisher, int w, int h,
+ boolean statusBarVisible, boolean navBarVisible) {
+ // If power save is on, show a toast so there is some visual indication that a screenshot
+ // has been taken.
+ PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ if (powerManager.isPowerSaveMode()) {
+ Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
+ }
+
+ // Add the view for the animation
+ mScreenshotView.setImageBitmap(mScreenBitmap);
+ mScreenshotLayout.requestFocus();
+
+ // Setup the animation with the screenshot just taken
+ if (mScreenshotAnimation != null) {
+ if (mScreenshotAnimation.isStarted()) {
+ mScreenshotAnimation.end();
+ }
+ mScreenshotAnimation.removeAllListeners();
+ }
+
+ mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+ ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
+ ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
+ statusBarVisible, navBarVisible);
+ mScreenshotAnimation = new AnimatorSet();
+ mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
+ mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Save the screenshot once we have a bit of time now
+ saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
+ @Override
+ void onActionsReady(Uri uri, List<Notification.Action> actions) {
+ if (uri == null) {
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ } else {
+ mNotificationsController.showScreenshotActionsNotification(
+ uri, actions);
+ }
+ }
+ });
+ clearScreenshot();
+ }
+ });
+ mScreenshotLayout.post(() -> {
+ // Play the shutter sound to notify that we've taken a screenshot
+ mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+
+ mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mScreenshotView.buildLayer();
+ mScreenshotAnimation.start();
+ });
+ }
+
+ private ValueAnimator createScreenshotDropInAnimation() {
+ final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
+ / SCREENSHOT_DROP_IN_DURATION);
+ final float flashDurationPct = 2f * flashPeakDurationPct;
+ final Interpolator flashAlphaInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float x) {
+ // Flash the flash view in and out quickly
+ if (x <= flashDurationPct) {
+ return (float) Math.sin(Math.PI * (x / flashDurationPct));
+ }
+ return 0;
+ }
+ };
+ final Interpolator scaleInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float x) {
+ // We start scaling when the flash is at it's peak
+ if (x < flashPeakDurationPct) {
+ return 0;
+ }
+ return (x - flashDurationPct) / (1f - flashDurationPct);
+ }
+ };
+
+ Resources r = mContext.getResources();
+ if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES) {
+ mScreenshotView.getBackground().setTint(Color.BLACK);
+ } else {
+ mScreenshotView.getBackground().setTintList(null);
+ }
+
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mBackgroundView.setAlpha(0f);
+ mBackgroundView.setVisibility(View.VISIBLE);
+ mScreenshotView.setAlpha(0f);
+ mScreenshotView.setTranslationX(0f);
+ mScreenshotView.setTranslationY(0f);
+ mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
+ mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
+ mScreenshotView.setVisibility(View.VISIBLE);
+ mScreenshotFlash.setAlpha(0f);
+ mScreenshotFlash.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(android.animation.Animator animation) {
+ mScreenshotFlash.setVisibility(View.GONE);
+ }
+ });
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (Float) animation.getAnimatedValue();
+ float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
+ - scaleInterpolator.getInterpolation(t)
+ * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
+ mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
+ mScreenshotView.setAlpha(t);
+ mScreenshotView.setScaleX(scaleT);
+ mScreenshotView.setScaleY(scaleT);
+ mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
+ }
+ });
+ return anim;
+ }
+
+ private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
+ boolean navBarVisible) {
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBackgroundView.setVisibility(View.GONE);
+ mScreenshotView.setVisibility(View.GONE);
+ mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ });
+
+ if (!statusBarVisible || !navBarVisible) {
+ // There is no status bar/nav bar, so just fade the screenshot away in place
+ anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (Float) animation.getAnimatedValue();
+ float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
+ - t * (SCREENSHOT_DROP_IN_MIN_SCALE
+ - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
+ mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+ mScreenshotView.setAlpha(1f - t);
+ mScreenshotView.setScaleX(scaleT);
+ mScreenshotView.setScaleY(scaleT);
+ }
+ });
+ } else {
+ // In the case where there is a status bar, animate to the origin of the bar (top-left)
+ final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
+ / SCREENSHOT_DROP_OUT_DURATION;
+ final Interpolator scaleInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float x) {
+ if (x < scaleDurationPct) {
+ // Decelerate, and scale the input accordingly
+ return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
+ }
+ return 1f;
+ }
+ };
+
+ // Determine the bounds of how to scale
+ float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
+ float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
+ final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
+ final PointF finalPos = new PointF(
+ -halfScreenWidth
+ + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
+ -halfScreenHeight
+ + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
+
+ // Animate the screenshot to the status bar
+ anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (Float) animation.getAnimatedValue();
+ float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
+ - scaleInterpolator.getInterpolation(t)
+ * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
+ mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+ mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
+ mScreenshotView.setScaleX(scaleT);
+ mScreenshotView.setScaleY(scaleT);
+ mScreenshotView.setTranslationX(t * finalPos.x);
+ mScreenshotView.setTranslationY(t * finalPos.y);
+ }
+ });
+ }
+ return anim;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
new file mode 100644
index 0000000..6edacd1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+/**
+ * View for a chip with an icon and text.
+ */
+public class ScreenshotActionChip extends LinearLayout {
+
+ private ImageView mIcon;
+ private TextView mText;
+ private @ColorInt int mIconColor;
+
+ public ScreenshotActionChip(Context context) {
+ this(context, null);
+ }
+
+ public ScreenshotActionChip(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ScreenshotActionChip(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ScreenshotActionChip(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mIconColor = context.getColor(R.color.global_screenshot_button_text);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mIcon = findViewById(R.id.screenshot_action_chip_icon);
+ mText = findViewById(R.id.screenshot_action_chip_text);
+ }
+
+ void setIcon(Icon icon, boolean tint) {
+ if (tint) {
+ icon.setTint(mIconColor);
+ }
+ mIcon.setImageIcon(icon);
+ }
+
+ void setText(CharSequence text) {
+ mText.setText(text);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 29b96a9..4f045d5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -16,15 +16,21 @@
package com.android.systemui.screenshot;
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
+
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserManager;
+import android.provider.DeviceConfig;
import android.util.Log;
import android.view.WindowManager;
@@ -36,9 +42,10 @@
private static final String TAG = "TakeScreenshotService";
private final GlobalScreenshot mScreenshot;
+ private final GlobalScreenshotLegacy mScreenshotLegacy;
private final UserManager mUserManager;
- private Handler mHandler = new Handler() {
+ private Handler mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
final Messenger callback = msg.replyTo;
@@ -59,12 +66,24 @@
return;
}
+ // TODO (mkephart): clean up once notifications flow is fully deprecated
+ boolean useCornerFlow = DeviceConfig.getBoolean(
+ NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
- mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+ if (useCornerFlow) {
+ mScreenshot.takeScreenshot(finisher);
+ } else {
+ mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+ }
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
- mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
+ if (useCornerFlow) {
+ mScreenshot.takeScreenshotPartial(finisher);
+ } else {
+ mScreenshotLegacy.takeScreenshotPartial(
+ finisher, msg.arg1 > 0, msg.arg2 > 0);
+ }
break;
default:
Log.d(TAG, "Invalid screenshot option: " + msg.what);
@@ -73,8 +92,10 @@
};
@Inject
- public TakeScreenshotService(GlobalScreenshot globalScreenshot, UserManager userManager) {
+ public TakeScreenshotService(GlobalScreenshot globalScreenshot,
+ GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) {
mScreenshot = globalScreenshot;
+ mScreenshotLegacy = globalScreenshotLegacy;
mUserManager = userManager;
}
@@ -86,6 +107,7 @@
@Override
public boolean onUnbind(Intent intent) {
if (mScreenshot != null) mScreenshot.stopScreenshot();
+ if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot();
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 43d0399..667e721 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -584,7 +584,15 @@
public void bindRow(ExpandableNotificationRow row) {
row.setRemoteInputController(mRemoteInputController);
- row.setRemoteViewClickHandler(mOnClickHandler);
+ }
+
+ /**
+ * Return on-click handler for notification remote views
+ *
+ * @return on-click handler
+ */
+ public RemoteViews.OnClickHandler getRemoteViewsOnClickHandler() {
+ return mOnClickHandler;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
index d1f6ebf..ec8dbea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
@@ -18,6 +18,8 @@
import android.content.Context;
+import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+
import javax.inject.Singleton;
import dagger.Module;
@@ -26,7 +28,7 @@
/**
* Dagger Module providing common dependencies of StatusBar.
*/
-@Module
+@Module(includes = {NotificationRowModule.class})
public class StatusBarDependenciesModule {
/**
* Provides our instance of CommandQueue which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index dc84b57..9c626f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -307,6 +307,7 @@
case Icon.TYPE_RESOURCE:
return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId();
case Icon.TYPE_URI:
+ case Icon.TYPE_URI_ADAPTIVE_BITMAP:
return a.getUriString().equals(b.getUriString());
default:
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index a2578ab..4a22831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -32,7 +32,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
@@ -44,7 +43,7 @@
import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.logging.NotifEvent;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -68,6 +67,8 @@
import javax.inject.Inject;
import javax.inject.Singleton;
+import dagger.Lazy;
+
/**
* NotificationEntryManager is responsible for the adding, removing, and updating of
* {@link NotificationEntry}s. It also handles tasks such as their inflation and their interaction
@@ -126,8 +127,9 @@
new ArrayMap<>();
// Lazily retrieved dependencies
- private NotificationRemoteInputManager mRemoteInputManager;
- private NotificationRowBinder mNotificationRowBinder;
+ private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
+ private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
+ private final LeakDetector mLeakDetector;
private final KeyguardEnvironment mKeyguardEnvironment;
private final NotificationGroupManager mGroupManager;
@@ -173,12 +175,18 @@
NotificationGroupManager groupManager,
NotificationRankingManager rankingManager,
KeyguardEnvironment keyguardEnvironment,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ Lazy<NotificationRowBinder> notificationRowBinderLazy,
+ Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+ LeakDetector leakDetector) {
mNotifLog = notifLog;
mGroupManager = groupManager;
mRankingManager = rankingManager;
mKeyguardEnvironment = keyguardEnvironment;
mFeatureFlags = featureFlags;
+ mNotificationRowBinderLazy = notificationRowBinderLazy;
+ mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
+ mLeakDetector = leakDetector;
}
/** Once called, the NEM will start processing notification events from system server. */
@@ -204,20 +212,6 @@
mRemoveInterceptor = interceptor;
}
- /**
- * Our dependencies can have cyclic references, so some need to be lazy
- */
- private NotificationRemoteInputManager getRemoteInputManager() {
- if (mRemoteInputManager == null) {
- mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
- }
- return mRemoteInputManager;
- }
-
- public void setRowBinder(NotificationRowBinder notificationRowBinder) {
- mNotificationRowBinder = notificationRowBinder;
- }
-
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationListContainer listContainer,
HeadsUpManager headsUpManager) {
@@ -468,7 +462,7 @@
handleGroupSummaryRemoved(key);
removeVisibleNotification(key);
updateNotifications("removeNotificationInternal");
- Dependency.get(LeakDetector.class).trackGarbage(entry);
+ mLeakDetector.trackGarbage(entry);
removedByUser |= entryDismissed;
mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.getSbn(),
@@ -507,8 +501,8 @@
boolean isForeground = (entry.getSbn().getNotification().flags
& Notification.FLAG_FOREGROUND_SERVICE) != 0;
boolean keepForReply =
- getRemoteInputManager().shouldKeepForRemoteInputHistory(childEntry)
- || getRemoteInputManager().shouldKeepForSmartReplyHistory(childEntry);
+ mRemoteInputManagerLazy.get().shouldKeepForRemoteInputHistory(childEntry)
+ || mRemoteInputManagerLazy.get().shouldKeepForSmartReplyHistory(childEntry);
if (isForeground || keepForReply) {
// the child is a foreground service notification which we can't remove or it's
// a child we're keeping around for reply!
@@ -536,12 +530,13 @@
NotificationEntry entry = new NotificationEntry(notification, ranking);
- Dependency.get(LeakDetector.class).trackInstance(entry);
+ mLeakDetector.trackInstance(entry);
// Construct the expanded view.
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
- requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
- REASON_CANCEL));
+ mNotificationRowBinderLazy.get()
+ .inflateViews(entry, () -> performRemoveNotification(notification,
+ REASON_CANCEL));
}
abortExistingInflation(key, "addNotification");
@@ -586,15 +581,16 @@
}
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
- requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
- REASON_CANCEL));
+ mNotificationRowBinderLazy.get()
+ .inflateViews(entry, () -> performRemoveNotification(notification,
+ REASON_CANCEL));
}
updateNotifications("updateNotificationInternal");
if (DEBUG) {
// Is this for you?
- boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class)
+ boolean isForCurrentUser = mKeyguardEnvironment
.isNotificationForCurrentProfiles(notification);
Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
}
@@ -644,11 +640,12 @@
// By comparing the old and new UI adjustments, reinflate the view accordingly.
for (NotificationEntry entry : entries) {
- requireBinder().onNotificationRankingUpdated(
- entry,
- oldImportances.get(entry.getKey()),
- oldAdjustments.get(entry.getKey()),
- NotificationUiAdjustment.extractFromNotificationEntry(entry));
+ mNotificationRowBinderLazy.get()
+ .onNotificationRankingUpdated(
+ entry,
+ oldImportances.get(entry.getKey()),
+ oldAdjustments.get(entry.getKey()),
+ NotificationUiAdjustment.extractFromNotificationEntry(entry));
}
updateNotifications("updateNotificationRanking");
@@ -728,14 +725,6 @@
}
}
- private NotificationRowBinder requireBinder() {
- if (mNotificationRowBinder == null) {
- throw new RuntimeException("You must initialize NotificationEntryManager by calling"
- + "setRowBinder() before using.");
- }
- return mNotificationRowBinder;
- }
-
/*
* -----
* Annexed from NotificationData below:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index e1268f6..eaa9d78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -16,44 +16,135 @@
package com.android.systemui.statusbar.notification.collection;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
-
+import java.util.Arrays;
import java.util.List;
-
/**
- * Utility class for dumping the results of a {@link NotifListBuilder} to a debug string.
+ * Utility class for dumping the results of a {@link ShadeListBuilder} to a debug string.
*/
public class ListDumper {
- /** See class description */
- public static String dumpList(List<ListEntry> entries) {
+ /**
+ * Creates a debug string for a list of grouped notifications that will be printed
+ * in the order given in a tiered/tree structure.
+ * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+ * entry to be in its current state (ie: filter, lifeExtender)
+ */
+ public static String dumpTree(
+ List<ListEntry> entries,
+ boolean includeRecordKeeping,
+ String indent) {
StringBuilder sb = new StringBuilder();
- for (int i = 0; i < entries.size(); i++) {
- ListEntry entry = entries.get(i);
- dumpEntry(entry, Integer.toString(i), "", sb);
+ final String childEntryIndent = indent + INDENT;
+ for (int topEntryIndex = 0; topEntryIndex < entries.size(); topEntryIndex++) {
+ ListEntry entry = entries.get(topEntryIndex);
+ dumpEntry(entry,
+ Integer.toString(topEntryIndex),
+ indent,
+ sb,
+ true,
+ includeRecordKeeping);
if (entry instanceof GroupEntry) {
GroupEntry ge = (GroupEntry) entry;
- for (int j = 0; j < ge.getChildren().size(); j++) {
- dumpEntry(
- ge.getChildren().get(j),
- Integer.toString(j),
- INDENT,
- sb);
+ List<NotificationEntry> children = ge.getChildren();
+ for (int childIndex = 0; childIndex < children.size(); childIndex++) {
+ dumpEntry(children.get(childIndex),
+ Integer.toString(topEntryIndex) + "." + Integer.toString(childIndex),
+ childEntryIndent,
+ sb,
+ true,
+ includeRecordKeeping);
}
}
}
return sb.toString();
}
+ /**
+ * Creates a debug string for a flat list of notifications
+ * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+ * entry to be in its current state (ie: filter, lifeExtender)
+ */
+ public static String dumpList(
+ List<NotificationEntry> entries,
+ boolean includeRecordKeeping,
+ String indent) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < entries.size(); j++) {
+ dumpEntry(
+ entries.get(j),
+ Integer.toString(j),
+ indent,
+ sb,
+ false,
+ includeRecordKeeping);
+ }
+ return sb.toString();
+ }
+
private static void dumpEntry(
- ListEntry entry, String index, String indent, StringBuilder sb) {
+ ListEntry entry,
+ String index,
+ String indent,
+ StringBuilder sb,
+ boolean includeParent,
+ boolean includeRecordKeeping) {
sb.append(indent)
.append("[").append(index).append("] ")
- .append(entry.getKey())
- .append(" (parent=")
- .append(entry.getParent() != null ? entry.getParent().getKey() : null)
- .append(")\n");
+ .append(entry.getKey());
+
+ if (includeParent) {
+ sb.append(" (parent=")
+ .append(entry.getParent() != null ? entry.getParent().getKey() : null)
+ .append(")");
+ }
+
+ if (entry.mNotifSection != null) {
+ sb.append(" sectionIndex=")
+ .append(entry.getSection())
+ .append(" sectionName=")
+ .append(entry.mNotifSection.getName());
+ }
+
+ if (includeRecordKeeping) {
+ NotificationEntry notifEntry = entry.getRepresentativeEntry();
+ StringBuilder rksb = new StringBuilder();
+
+ if (!notifEntry.mLifetimeExtenders.isEmpty()) {
+ String[] lifetimeExtenderNames = new String[notifEntry.mLifetimeExtenders.size()];
+ for (int i = 0; i < lifetimeExtenderNames.length; i++) {
+ lifetimeExtenderNames[i] = notifEntry.mLifetimeExtenders.get(i).getName();
+ }
+ rksb.append("lifetimeExtenders=")
+ .append(Arrays.toString(lifetimeExtenderNames))
+ .append(" ");
+ }
+
+ if (notifEntry.mExcludingFilter != null) {
+ rksb.append("filter=")
+ .append(notifEntry.mExcludingFilter)
+ .append(" ");
+ }
+
+ if (notifEntry.mNotifPromoter != null) {
+ rksb.append("promoter=")
+ .append(notifEntry.mNotifPromoter)
+ .append(" ");
+ }
+
+ if (notifEntry.hasInflationError()) {
+ rksb.append("hasInflationError ");
+ }
+
+ String rkString = rksb.toString();
+ if (!rkString.isEmpty()) {
+ sb.append("\n\t")
+ .append(indent)
+ .append(rkString);
+ }
+ }
+
+ sb.append("\n");
}
private static final String INDENT = " ";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index dc68c4b..56ad0e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -18,6 +18,8 @@
import android.annotation.Nullable;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+
/**
* Abstract superclass for top-level entries, i.e. things that can appear in the final notification
* list shown to users. In practice, this means either GroupEntries or NotificationEntries.
@@ -27,7 +29,9 @@
@Nullable private GroupEntry mParent;
@Nullable private GroupEntry mPreviousParent;
- private int mSection;
+ @Nullable NotifSection mNotifSection;
+
+ private int mSection = -1;
int mFirstAddedIteration = -1;
ListEntry(String key) {
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 856b75b..c488c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -47,11 +47,19 @@
import android.util.Log;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CoalescedEvent;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.util.Assert;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -88,7 +96,7 @@
*/
@MainThread
@Singleton
-public class NotifCollection {
+public class NotifCollection implements Dumpable {
private final IStatusBarService mStatusBarService;
private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
@@ -103,9 +111,10 @@
private boolean mAmDispatchingToOtherCode;
@Inject
- public NotifCollection(IStatusBarService statusBarService) {
+ public NotifCollection(IStatusBarService statusBarService, DumpController dumpController) {
Assert.isMainThread();
mStatusBarService = statusBarService;
+ dumpController.registerDumpable(TAG, this);
}
/** Initializes the NotifCollection and registers it to receive notification events. */
@@ -123,36 +132,25 @@
* Sets the class responsible for converting the collection into the list of currently-visible
* notifications.
*/
- public void setBuildListener(CollectionReadyForBuildListener buildListener) {
+ void setBuildListener(CollectionReadyForBuildListener buildListener) {
Assert.isMainThread();
mBuildListener = buildListener;
}
- /**
- * Returns the list of "active" notifications, i.e. the notifications that are currently posted
- * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
- * but it can diverge slightly due to lifetime extenders.
- *
- * The returned list is read-only, unsorted, unfiltered, and ungrouped.
- */
- public Collection<NotificationEntry> getNotifs() {
+ /** @see NotifPipeline#getActiveNotifs() */
+ Collection<NotificationEntry> getActiveNotifs() {
Assert.isMainThread();
return mReadOnlyNotificationSet;
}
- /**
- * Registers a listener to be informed when notifications are added, removed or updated.
- */
- public void addCollectionListener(NotifCollectionListener listener) {
+ /** @see NotifPipeline#addCollectionListener(NotifCollectionListener) */
+ void addCollectionListener(NotifCollectionListener listener) {
Assert.isMainThread();
mNotifCollectionListeners.add(listener);
}
- /**
- * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
- * dismissed or retracted to be temporarily retained in the collection.
- */
- public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
+ /** @see NotifPipeline#addNotificationLifetimeExtender(NotifLifetimeExtender) */
+ void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
Assert.isMainThread();
checkForReentrantCall();
if (mLifetimeExtenders.contains(extender)) {
@@ -165,7 +163,7 @@
/**
* Dismiss a notification on behalf of the user.
*/
- public void dismissNotification(
+ void dismissNotification(
NotificationEntry entry,
@CancellationReason int reason,
@NonNull DismissedByUserStats stats) {
@@ -446,7 +444,22 @@
REASON_TIMEOUT,
})
@Retention(RetentionPolicy.SOURCE)
- @interface CancellationReason {}
+ public @interface CancellationReason {}
public static final int REASON_UNKNOWN = 0;
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
+
+ pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
+ if (entries.size() == 0) {
+ pw.println("\t\t None");
+ }
+ pw.println(
+ ListDumper.dumpList(
+ entries,
+ true,
+ "\t\t"));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 0d17557..e7b772f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -25,6 +25,9 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
@@ -107,7 +110,7 @@
DISMISS_SENTIMENT_NEUTRAL,
NotificationVisibility.obtain(entry.getKey(),
entry.getRanking().getRank(),
- mNotifCollection.getNotifs().size(),
+ mNotifCollection.getActiveNotifs().size(),
true,
NotificationLogger.getNotificationLocation(entry))
));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
new file mode 100644
index 0000000..0377f57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * The system that constructs the "shade list", the filtered, grouped, and sorted list of
+ * notifications that are currently being displayed to the user in the notification shade.
+ *
+ * The pipeline proceeds through a series of stages in order to produce the final list (see below).
+ * Each stage exposes hooks and listeners to allow other code to participate.
+ *
+ * This list differs from the canonical one we receive from system server in a few ways:
+ * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose
+ * views haven't been inflated yet. We also filter out some notifications if we're on the lock
+ * screen and notifications for other users. So participate, see
+ * {@link #addPreGroupFilter} and similar methods.
+ * - Grouped: Notifications that are part of the same group are clustered together into a single
+ * GroupEntry. These groups are then transformed in order to remove children or completely split
+ * them apart. To participate, see {@link #addPromoter}.
+ * - Sorted: All top-level notifications are sorted. To participate, see
+ * {@link #setSections} and {@link #setComparators}
+ *
+ * The exact order of all hooks is as follows:
+ * 0. Collection listeners are fired ({@link #addCollectionListener}).
+ * 1. Pre-group filters are fired on each notification ({@link #addPreGroupFilter}).
+ * 2. Initial grouping is performed (NotificationEntries will have their parents set
+ * appropriately).
+ * 3. OnBeforeTransformGroupListeners are fired ({@link #addOnBeforeTransformGroupsListener})
+ * 4. NotifPromoters are called on each notification with a parent ({@link #addPromoter})
+ * 5. OnBeforeSortListeners are fired ({@link #addOnBeforeSortListener})
+ * 6. Top-level entries are assigned sections by NotifSections ({@link #setSections})
+ * 7. Top-level entries within the same section are sorted by NotifComparators
+ * ({@link #setComparators})
+ * 8. Pre-render filters are fired on each notification ({@link #addPreRenderFilter})
+ * 9. OnBeforeRenderListListeners are fired ({@link #addOnBeforeRenderListListener})
+ * 9. The list is handed off to the view layer to be rendered
+ */
+@Singleton
+public class NotifPipeline {
+ private final NotifCollection mNotifCollection;
+ private final ShadeListBuilder mShadeListBuilder;
+
+ @Inject
+ public NotifPipeline(
+ NotifCollection notifCollection,
+ ShadeListBuilder shadeListBuilder) {
+ mNotifCollection = notifCollection;
+ mShadeListBuilder = shadeListBuilder;
+ }
+
+ /**
+ * Returns the list of "active" notifications, i.e. the notifications that are currently posted
+ * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
+ * but it can diverge slightly due to lifetime extenders.
+ *
+ * The returned collection is read-only, unsorted, unfiltered, and ungrouped.
+ */
+ public Collection<NotificationEntry> getActiveNotifs() {
+ return mNotifCollection.getActiveNotifs();
+ }
+
+ /**
+ * Registers a listener to be informed when notifications are added, removed or updated.
+ */
+ public void addCollectionListener(NotifCollectionListener listener) {
+ mNotifCollection.addCollectionListener(listener);
+ }
+
+ /**
+ * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
+ * dismissed or retracted to be temporarily retained in the collection.
+ */
+ public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
+ mNotifCollection.addNotificationLifetimeExtender(extender);
+ }
+
+ /**
+ * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
+ * are called on each notification in the order that they were registered. If any filter
+ * returns true, the notification is removed from the pipeline (and no other filters are
+ * called on that notif).
+ */
+ public void addPreGroupFilter(NotifFilter filter) {
+ mShadeListBuilder.addPreGroupFilter(filter);
+ }
+
+ /**
+ * Called after notifications have been filtered and after the initial grouping has been
+ * performed but before NotifPromoters have had a chance to promote children out of groups.
+ */
+ public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
+ mShadeListBuilder.addOnBeforeTransformGroupsListener(listener);
+ }
+
+ /**
+ * Registers a promoter with the pipeline. Promoters are able to promote child notifications to
+ * top-level, i.e. move a notification that would be a child of a group and make it appear
+ * ungrouped. Promoters are called on each child notification in the order that they are
+ * registered. If any promoter returns true, the notification is removed from the group (and no
+ * other promoters are called on it).
+ */
+ public void addPromoter(NotifPromoter promoter) {
+ mShadeListBuilder.addPromoter(promoter);
+ }
+
+ /**
+ * Called after notifs have been filtered and groups have been determined but before sections
+ * have been determined or the notifs have been sorted.
+ */
+ public void addOnBeforeSortListener(OnBeforeSortListener listener) {
+ mShadeListBuilder.addOnBeforeSortListener(listener);
+ }
+
+ /**
+ * Sections that are used to sort top-level entries. If two entries have the same section,
+ * NotifComparators are consulted. Sections from this list are called in order for each
+ * notification passed through the pipeline. The first NotifSection to return true for
+ * {@link NotifSection#isInSection(ListEntry)} sets the entry as part of its Section.
+ */
+ public void setSections(List<NotifSection> sections) {
+ mShadeListBuilder.setSections(sections);
+ }
+
+ /**
+ * Comparators that are used to sort top-level entries that share the same section. The
+ * comparators are executed in order until one of them returns a non-zero result. If all return
+ * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when).
+ */
+ public void setComparators(List<NotifComparator> comparators) {
+ mShadeListBuilder.setComparators(comparators);
+ }
+
+ /**
+ * Registers a filter with the pipeline to filter right before rendering the list (after
+ * pre-group filtering, grouping, promoting and sorting occurs). Filters are
+ * called on each notification in the order that they were registered. If any filter returns
+ * true, the notification is removed from the pipeline (and no other filters are called on that
+ * notif).
+ */
+ public void addPreRenderFilter(NotifFilter filter) {
+ mShadeListBuilder.addPreRenderFilter(filter);
+ }
+
+ /**
+ * Called at the end of the pipeline after the notif list has been finalized but before it has
+ * been handed off to the view layer.
+ */
+ public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
+ mShadeListBuilder.addOnBeforeRenderListListener(listener);
+ }
+
+ /**
+ * Returns a read-only view in to the current shade list, i.e. the list of notifications that
+ * are currently present in the shade. If this method is called during pipeline execution it
+ * will return the current state of the list, which will likely be only partially-generated.
+ */
+ public List<ListEntry> getShadeList() {
+ return mShadeListBuilder.getShadeList();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 7301fe1..2fcfb8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -59,6 +59,7 @@
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
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 3bbd722..820c042 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
@@ -176,12 +176,14 @@
}
entry.ranking = newRanking
- val oldSbn = entry.sbn.cloneLight()
val newOverrideGroupKey = newRanking.overrideGroupKey
- if (!Objects.equals(oldSbn.overrideGroupKey, newOverrideGroupKey)) {
+ if (!Objects.equals(entry.sbn.overrideGroupKey, newOverrideGroupKey)) {
+ val oldGroupKey = entry.sbn.groupKey
+ val oldIsGroup = entry.sbn.isGroup
+ val oldIsGroupSummary = entry.sbn.notification.isGroupSummary
entry.sbn.overrideGroupKey = newOverrideGroupKey
- // TODO: notify group manager here?
- groupManager.onEntryUpdated(entry, oldSbn)
+ groupManager.onEntryUpdated(entry, oldGroupKey, oldIsGroup,
+ oldIsGroupSummary)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 19d90f0..97f8ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.collection;
import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
@@ -31,8 +30,10 @@
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.util.ArrayMap;
+import android.util.Pair;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
@@ -40,12 +41,15 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.logging.NotifEvent;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.util.Assert;
import com.android.systemui.util.time.SystemClock;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -57,11 +61,13 @@
import javax.inject.Singleton;
/**
- * The implementation of {@link NotifListBuilder}.
+ * The second half of {@link NotifPipeline}. Sits downstream of the NotifCollection and transforms
+ * its "notification set" into the "shade list", the filtered, grouped, and sorted list of
+ * notifications that are currently present in the notification shade.
*/
@MainThread
@Singleton
-public class NotifListBuilderImpl implements NotifListBuilder {
+public class ShadeListBuilder implements Dumpable {
private final SystemClock mSystemClock;
private final NotifLog mNotifLog;
@@ -77,7 +83,7 @@
private final List<NotifPromoter> mNotifPromoters = new ArrayList<>();
private final List<NotifFilter> mNotifPreRenderFilters = new ArrayList<>();
private final List<NotifComparator> mNotifComparators = new ArrayList<>();
- private SectionsProvider mSectionsProvider = new DefaultSectionsProvider();
+ private final List<NotifSection> mNotifSections = new ArrayList<>();
private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
new ArrayList<>();
@@ -90,10 +96,14 @@
private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
@Inject
- public NotifListBuilderImpl(SystemClock systemClock, NotifLog notifLog) {
+ public ShadeListBuilder(
+ SystemClock systemClock,
+ NotifLog notifLog,
+ DumpController dumpController) {
Assert.isMainThread();
mSystemClock = systemClock;
mNotifLog = notifLog;
+ dumpController.registerDumpable(TAG, this);
}
/**
@@ -116,32 +126,28 @@
mOnRenderListListener = onRenderListListener;
}
- @Override
- public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
+ void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
mOnBeforeTransformGroupsListeners.add(listener);
}
- @Override
- public void addOnBeforeSortListener(OnBeforeSortListener listener) {
+ void addOnBeforeSortListener(OnBeforeSortListener listener) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
mOnBeforeSortListeners.add(listener);
}
- @Override
- public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
+ void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
mOnBeforeRenderListListeners.add(listener);
}
- @Override
- public void addPreGroupFilter(NotifFilter filter) {
+ void addPreGroupFilter(NotifFilter filter) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -149,8 +155,7 @@
filter.setInvalidationListener(this::onPreGroupFilterInvalidated);
}
- @Override
- public void addPreRenderFilter(NotifFilter filter) {
+ void addPreRenderFilter(NotifFilter filter) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -158,8 +163,7 @@
filter.setInvalidationListener(this::onPreRenderFilterInvalidated);
}
- @Override
- public void addPromoter(NotifPromoter promoter) {
+ void addPromoter(NotifPromoter promoter) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -167,17 +171,18 @@
promoter.setInvalidationListener(this::onPromoterInvalidated);
}
- @Override
- public void setSectionsProvider(SectionsProvider provider) {
+ void setSections(List<NotifSection> sections) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
- mSectionsProvider = provider;
- provider.setInvalidationListener(this::onSectionsProviderInvalidated);
+ mNotifSections.clear();
+ for (NotifSection section : sections) {
+ mNotifSections.add(section);
+ section.setInvalidationListener(this::onNotifSectionInvalidated);
+ }
}
- @Override
- public void setComparators(List<NotifComparator> comparators) {
+ void setComparators(List<NotifComparator> comparators) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -188,8 +193,7 @@
}
}
- @Override
- public List<ListEntry> getActiveNotifs() {
+ List<ListEntry> getShadeList() {
Assert.isMainThread();
return mReadOnlyNotifList;
}
@@ -230,12 +234,12 @@
rebuildListIfBefore(STATE_TRANSFORMING);
}
- private void onSectionsProviderInvalidated(SectionsProvider provider) {
+ private void onNotifSectionInvalidated(NotifSection section) {
Assert.isMainThread();
- mNotifLog.log(NotifEvent.SECTIONS_PROVIDER_INVALIDATED, String.format(
- "Sections provider \"%s\" invalidated; pipeline state is %d",
- provider.getName(),
+ mNotifLog.log(NotifEvent.SECTION_INVALIDATED, String.format(
+ "Section \"%s\" invalidated; pipeline state is %d",
+ section.getName(),
mPipelineState.getState()));
rebuildListIfBefore(STATE_SORTING);
@@ -275,7 +279,7 @@
}
/**
- * The core algorithm of the pipeline. See the top comment in {@link NotifListBuilder} for
+ * The core algorithm of the pipeline. See the top comment in {@link NotifPipeline} for
* details on our contracts with other code.
*
* Once the build starts we are very careful to protect against reentrant code. Anything that
@@ -318,7 +322,7 @@
sortList();
// Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting
- // Now filters can see grouping information to determine whether to filter or not
+ // Now filters can see grouping information to determine whether to filter or not.
mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING);
filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters);
applyNewNotifList();
@@ -331,7 +335,7 @@
// Step 6: Dispatch the new list, first to any listeners and then to the view layer
mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n"
- + dumpList(mNotifList));
+ + ListDumper.dumpTree(mNotifList, false, "\t\t"));
dispatchOnBeforeRenderList(mReadOnlyNotifList);
if (mOnRenderListListener != null) {
mOnRenderListListener.onRenderList(mReadOnlyNotifList);
@@ -580,6 +584,8 @@
* filtered out during any of the filtering steps.
*/
private void annulAddition(ListEntry entry) {
+ entry.setSection(-1);
+ entry.mNotifSection = null;
entry.setParent(null);
if (entry.mFirstAddedIteration == mIterationCount) {
entry.mFirstAddedIteration = -1;
@@ -589,11 +595,12 @@
private void sortList() {
// Assign sections to top-level elements and sort their children
for (ListEntry entry : mNotifList) {
- entry.setSection(mSectionsProvider.getSection(entry));
+ Pair<NotifSection, Integer> sectionWithIndex = applySections(entry);
if (entry instanceof GroupEntry) {
GroupEntry parent = (GroupEntry) entry;
for (NotificationEntry child : parent.getChildren()) {
- child.setSection(0);
+ child.mNotifSection = sectionWithIndex.first;
+ child.setSection(sectionWithIndex.second);
}
parent.sortChildren(sChildComparator);
}
@@ -754,6 +761,45 @@
return null;
}
+ private Pair<NotifSection, Integer> applySections(ListEntry entry) {
+ final Pair<NotifSection, Integer> sectionWithIndex = findSection(entry);
+ final NotifSection section = sectionWithIndex.first;
+ final Integer sectionIndex = sectionWithIndex.second;
+
+ if (section != entry.mNotifSection) {
+ if (entry.mNotifSection == null) {
+ mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
+ "%s: sectioned by '%s' [index=%d].",
+ entry.getKey(),
+ section.getName(),
+ sectionIndex));
+ } else {
+ mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
+ "%s: section changed: '%s' [index=%d] -> '%s [index=%d]'.",
+ entry.getKey(),
+ entry.mNotifSection,
+ entry.getSection(),
+ section,
+ sectionIndex));
+ }
+
+ entry.mNotifSection = section;
+ entry.setSection(sectionIndex);
+ }
+
+ return sectionWithIndex;
+ }
+
+ private Pair<NotifSection, Integer> findSection(ListEntry entry) {
+ for (int i = 0; i < mNotifSections.size(); i++) {
+ NotifSection sectioner = mNotifSections.get(i);
+ if (sectioner.isInSection(entry)) {
+ return new Pair<>(sectioner, i);
+ }
+ }
+ return new Pair<>(sDefaultSection, mNotifSections.size());
+ }
+
private void rebuildListIfBefore(@PipelineState.StateName int state) {
mPipelineState.requireIsBefore(state);
if (mPipelineState.is(STATE_IDLE)) {
@@ -779,6 +825,19 @@
}
}
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("\t" + TAG + " shade notifications:");
+ if (getShadeList().size() == 0) {
+ pw.println("\t\t None");
+ }
+
+ pw.println(ListDumper.dumpTree(
+ getShadeList(),
+ true,
+ "\t\t"));
+ }
+
/** See {@link #setOnRenderListListener(OnRenderListListener)} */
public interface OnRenderListListener {
/**
@@ -790,16 +849,13 @@
void onRenderList(List<ListEntry> entries);
}
- private static class DefaultSectionsProvider extends SectionsProvider {
- DefaultSectionsProvider() {
- super("DefaultSectionsProvider");
- }
-
- @Override
- public int getSection(ListEntry entry) {
- return 0;
- }
- }
+ private static final NotifSection sDefaultSection =
+ new NotifSection("DefaultSection") {
+ @Override
+ public boolean isInSection(ListEntry entry) {
+ return true;
+ }
+ };
private static final String TAG = "NotifListBuilderImpl";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt
index b6218b4..143de8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection.notifcollection
+package com.android.systemui.statusbar.notification.collection.coalescer
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.StatusBarNotification
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java
index ac51178..2c6a165 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection.notifcollection;
+package com.android.systemui.statusbar.notification.collection.coalescer;
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
index 069c15f..8076616 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection.notifcollection;
+package com.android.systemui.statusbar.notification.collection.coalescer;
import static com.android.systemui.statusbar.notification.logging.NotifEvent.COALESCED_EVENT;
import static com.android.systemui.statusbar.notification.logging.NotifEvent.EARLY_BATCH_EMIT;
@@ -259,7 +259,7 @@
pw.println("Coalesced notifications:");
for (EventBatch batch : mBatches.values()) {
pw.println(" Batch " + batch.mGroupKey + ":");
- pw.println(" Created" + (now - batch.mCreatedTimestamp) + "ms ago");
+ pw.println(" Created " + (now - batch.mCreatedTimestamp) + "ms ago");
for (CoalescedEvent event : batch.mMembers) {
pw.println(" " + event.getKey());
eventCount++;
@@ -299,5 +299,5 @@
void onNotificationBatchPosted(List<CoalescedEvent> events);
}
- private static final int GROUP_LINGER_DURATION = 40;
+ private static final int GROUP_LINGER_DURATION = 500;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
index 898918e..d8b2e40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
@@ -16,27 +16,21 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
/**
- * Interface for registering callbacks to the {@link NewNotifPipeline}.
- *
- * This includes registering:
- * {@link Pluggable}s to the {@link NotifListBuilder}
- * {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s to {@link NotifCollection}
+ * Interface for registering callbacks to the {@link NotifPipeline}.
*/
public interface Coordinator {
-
/**
* Called after the NewNotifPipeline is initialized.
- * Coordinators should register their {@link Pluggable}s to the notifListBuilder
- * and their {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s
- * to the notifCollection in this method.
+ * Coordinators should register their listeners and {@link Pluggable}s to the pipeline.
*/
- void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder);
+ void attach(NotifPipeline pipeline);
+
+ default NotifSection getSection() {
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 5e7dd98..625d1b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -23,9 +23,8 @@
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -52,10 +51,10 @@
}
@Override
- public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+ public void attach(NotifPipeline pipeline) {
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
- notifListBuilder.addPreGroupFilter(mNotifFilter);
+ pipeline.addPreGroupFilter(mNotifFilter);
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
index 62342b1..da119c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
@@ -25,12 +25,11 @@
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import java.util.HashMap;
import java.util.Map;
@@ -57,7 +56,7 @@
private final AppOpsController mAppOpsController;
private final Handler mMainHandler;
- private NotifCollection mNotifCollection;
+ private NotifPipeline mNotifPipeline;
@Inject
public ForegroundCoordinator(
@@ -70,20 +69,20 @@
}
@Override
- public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
- mNotifCollection = notifCollection;
+ public void attach(NotifPipeline pipeline) {
+ mNotifPipeline = pipeline;
// extend the lifetime of foreground notification services to show for at least 5 seconds
- mNotifCollection.addNotificationLifetimeExtender(mForegroundLifetimeExtender);
+ mNotifPipeline.addNotificationLifetimeExtender(mForegroundLifetimeExtender);
// listen for new notifications to add appOps
- mNotifCollection.addCollectionListener(mNotifCollectionListener);
+ mNotifPipeline.addCollectionListener(mNotifCollectionListener);
// when appOps change, update any relevant notifications to update appOps for
mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged);
// filter out foreground service notifications that aren't necessary anymore
- notifListBuilder.addPreGroupFilter(mNotifFilter);
+ mNotifPipeline.addPreGroupFilter(mNotifFilter);
}
/**
@@ -230,7 +229,7 @@
}
private NotificationEntry findNotificationEntryWithKey(String key) {
- for (NotificationEntry entry : mNotifCollection.getNotifs()) {
+ for (NotificationEntry entry : mNotifPipeline.getActiveNotifs()) {
if (entry.getKey().equals(key)) {
return entry;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index db107f5..a26ee545 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -39,9 +39,8 @@
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -86,9 +85,9 @@
}
@Override
- public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+ public void attach(NotifPipeline pipeline) {
setupInvalidateNotifListCallbacks();
- notifListBuilder.addPreRenderFilter(mNotifFilter);
+ pipeline.addPreRenderFilter(mNotifFilter);
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index eeb54ab..8d0dd5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -16,13 +16,14 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -33,25 +34,28 @@
import javax.inject.Singleton;
/**
- * Handles the attachment of the {@link NotifListBuilder} and {@link NotifCollection} to the
- * {@link Coordinator}s, so that the Coordinators can register their respective callbacks.
+ * Handles the attachment of {@link Coordinator}s to the {@link NotifPipeline} so that the
+ * Coordinators can register their respective callbacks.
*/
@Singleton
public class NotifCoordinators implements Dumpable {
private static final String TAG = "NotifCoordinators";
private final List<Coordinator> mCoordinators = new ArrayList<>();
-
+ private final List<NotifSection> mOrderedSections = new ArrayList<>();
/**
* Creates all the coordinators.
*/
@Inject
public NotifCoordinators(
+ DumpController dumpController,
FeatureFlags featureFlags,
KeyguardCoordinator keyguardCoordinator,
RankingCoordinator rankingCoordinator,
ForegroundCoordinator foregroundCoordinator,
DeviceProvisionedCoordinator deviceProvisionedCoordinator,
PreparationCoordinator preparationCoordinator) {
+ dumpController.registerDumpable(TAG, this);
+
mCoordinators.add(keyguardCoordinator);
mCoordinators.add(rankingCoordinator);
mCoordinators.add(foregroundCoordinator);
@@ -60,25 +64,37 @@
mCoordinators.add(preparationCoordinator);
}
// TODO: add new Coordinators here! (b/145134683, b/112656837)
+
+ // TODO: add the sections in a particular ORDER (HeadsUp < People < Alerting)
+ for (Coordinator c : mCoordinators) {
+ if (c.getSection() != null) {
+ mOrderedSections.add(c.getSection());
+ }
+ }
}
/**
- * Sends the initialized notifListBuilder and notifCollection to each
- * coordinator to indicate the notifListBuilder is ready to accept {@link Pluggable}s
- * and the notifCollection is ready to accept {@link NotifCollectionListener}s and
- * {@link NotifLifetimeExtender}s.
+ * Sends the pipeline to each coordinator when the pipeline is ready to accept
+ * {@link Pluggable}s, {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s.
*/
- public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+ public void attach(NotifPipeline pipeline) {
for (Coordinator c : mCoordinators) {
- c.attach(notifCollection, notifListBuilder);
+ c.attach(pipeline);
}
+
+ pipeline.setSections(mOrderedSections);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println();
pw.println(TAG + ":");
for (Coordinator c : mCoordinators) {
pw.println("\t" + c.getClass());
}
+
+ for (NotifSection s : mOrderedSections) {
+ pw.println("\t" + s.getName());
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index a14f0e1..20c9cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -16,13 +16,12 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.NotifInflater;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.logging.NotifEvent;
import com.android.systemui.statusbar.notification.logging.NotifLog;
@@ -55,10 +54,10 @@
}
@Override
- public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
- notifCollection.addCollectionListener(mNotifCollectionListener);
- notifListBuilder.addPreRenderFilter(mNotifInflationErrorFilter);
- notifListBuilder.addPreRenderFilter(mNotifInflatingFilter);
+ public void attach(NotifPipeline pipeline) {
+ pipeline.addCollectionListener(mNotifCollectionListener);
+ pipeline.addPreRenderFilter(mNotifInflationErrorFilter);
+ pipeline.addPreRenderFilter(mNotifInflatingFilter);
}
private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 0751aa8..7e9e760 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -17,9 +17,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import javax.inject.Inject;
@@ -43,10 +42,10 @@
}
@Override
- public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+ public void attach(NotifPipeline pipeline) {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
- notifListBuilder.addPreGroupFilter(mNotifFilter);
+ pipeline.addPreGroupFilter(mNotifFilter);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java
index fc04827..ea0ece4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.inflation;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
index 7504e86..3f500644 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.inflation;
import android.annotation.Nullable;
import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Used by the {@link NotificationEntryManager}. When notifications are added or updated, the binder
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 6dc647d..5cd3e94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.inflation;
+import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
@@ -29,7 +30,6 @@
import android.view.ViewGroup;
import com.android.internal.util.NotificationMessagingUtil;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -39,70 +39,84 @@
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationClicker;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.people.NotificationPersonExtractorPluginBoundary;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifierImpl;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.Objects;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
/** Handles inflating and updating views for notifications. */
+@Singleton
public class NotificationRowBinderImpl implements NotificationRowBinder {
private static final String TAG = "NotificationViewManager";
- private final NotificationGroupManager mGroupManager =
- Dependency.get(NotificationGroupManager.class);
- private final NotificationGutsManager mGutsManager =
- Dependency.get(NotificationGutsManager.class);
- private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
- Dependency.get(NotificationInterruptionStateProvider.class);
-
+ private final NotificationGroupManager mGroupManager;
+ private final NotificationGutsManager mGutsManager;
+ private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private final Context mContext;
+ private final NotificationRowContentBinder mRowContentBinder;
private final NotificationMessagingUtil mMessagingUtil;
private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
this::logNotificationExpansion;
+ private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+ private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
private final boolean mAllowLongPress;
private final KeyguardBypassController mKeyguardBypassController;
private final StatusBarStateController mStatusBarStateController;
- private NotificationRemoteInputManager mRemoteInputManager;
private NotificationPresenter mPresenter;
private NotificationListContainer mListContainer;
private HeadsUpManager mHeadsUpManager;
- private NotificationContentInflater.InflationCallback mInflationCallback;
+ private NotificationRowContentBinder.InflationCallback mInflationCallback;
private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
private BindRowCallback mBindRowCallback;
private NotificationClicker mNotificationClicker;
private final NotificationLogger mNotificationLogger;
+ @Inject
public NotificationRowBinderImpl(
Context context,
- boolean allowLongPress,
+ NotificationRemoteInputManager notificationRemoteInputManager,
+ NotificationLockscreenUserManager notificationLockscreenUserManager,
+ NotificationRowContentBinder rowContentBinder,
+ @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
KeyguardBypassController keyguardBypassController,
StatusBarStateController statusBarStateController,
+ NotificationGroupManager notificationGroupManager,
+ NotificationGutsManager notificationGutsManager,
+ NotificationInterruptionStateProvider notificationInterruptionStateProvider,
NotificationLogger logger) {
mContext = context;
+ mRowContentBinder = rowContentBinder;
mMessagingUtil = new NotificationMessagingUtil(context);
+ mNotificationRemoteInputManager = notificationRemoteInputManager;
+ mNotificationLockscreenUserManager = notificationLockscreenUserManager;
mAllowLongPress = allowLongPress;
mKeyguardBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
+ mGroupManager = notificationGroupManager;
+ mGutsManager = notificationGutsManager;
+ mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
mNotificationLogger = logger;
}
- private NotificationRemoteInputManager getRemoteInputManager() {
- if (mRemoteInputManager == null) {
- mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
- }
- return mRemoteInputManager;
- }
-
/**
* Sets up late-bound dependencies for this component.
*/
@@ -117,7 +131,7 @@
mOnAppOpsClickListener = mGutsManager::openGuts;
}
- public void setInflationCallback(NotificationContentInflater.InflationCallback callback) {
+ public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
mInflationCallback = callback;
}
@@ -156,19 +170,6 @@
private void bindRow(NotificationEntry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row,
Runnable onDismissRunnable) {
- row.setExpansionLogger(mExpansionLogger, entry.getSbn().getKey());
- row.setBypassController(mKeyguardBypassController);
- row.setStatusBarStateController(mStatusBarStateController);
- row.setGroupManager(mGroupManager);
- row.setHeadsUpManager(mHeadsUpManager);
- row.setOnExpandClickListener(mPresenter);
- row.setInflationCallback(mInflationCallback);
- if (mAllowLongPress) {
- row.setLongPressListener(mGutsManager::openGuts);
- }
- mListContainer.bindRow(row);
- getRemoteInputManager().bindRow(row);
-
// Get the app name.
// Note that Notification.Builder#bindHeaderAppName has similar logic
// but since this field is used in the guts, it must be accurate.
@@ -186,15 +187,33 @@
} catch (PackageManager.NameNotFoundException e) {
// Do nothing
}
- row.setAppName(appname);
+
+ row.initialize(
+ appname,
+ sbn.getKey(),
+ mExpansionLogger,
+ mKeyguardBypassController,
+ mGroupManager,
+ mHeadsUpManager,
+ mRowContentBinder,
+ mPresenter);
+
+ // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely
+ row.setStatusBarStateController(mStatusBarStateController);
+ row.setInflationCallback(mInflationCallback);
+ row.setAppOpsOnClickListener(mOnAppOpsClickListener);
+ if (mAllowLongPress) {
+ row.setLongPressListener(mGutsManager::openGuts);
+ }
+ mListContainer.bindRow(row);
+ mNotificationRemoteInputManager.bindRow(row);
+
row.setOnDismissRunnable(onDismissRunnable);
row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (ENABLE_REMOTE_INPUT) {
row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
}
- row.setAppOpsOnClickListener(mOnAppOpsClickListener);
-
mBindRowCallback.onBindRow(entry, pmUser, sbn, row);
}
@@ -263,8 +282,7 @@
if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP);
}
- row.setNeedsRedaction(
- Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));
+ row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry));
row.inflateViews();
// bind the click event to the content area
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java
index 986ee17..15f312d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java
@@ -19,8 +19,8 @@
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -36,7 +36,7 @@
private List<ListEntry> mEntries = Collections.emptyList();
/** Attach the consumer to the pipeline. */
- public void attach(NotifListBuilderImpl listBuilder) {
+ public void attach(ShadeListBuilder listBuilder) {
listBuilder.setOnRenderListListener(this::onBuildComplete);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index 8d3d0ff..959b002 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -24,10 +24,11 @@
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -39,10 +40,11 @@
* Initialization code for the new notification pipeline.
*/
@Singleton
-public class NewNotifPipeline implements Dumpable {
+public class NotifPipelineInitializer implements Dumpable {
+ private final NotifPipeline mPipelineWrapper;
private final GroupCoalescer mGroupCoalescer;
private final NotifCollection mNotifCollection;
- private final NotifListBuilderImpl mNotifPipeline;
+ private final ShadeListBuilder mListBuilder;
private final NotifCoordinators mNotifPluggableCoordinators;
private final NotifInflaterImpl mNotifInflater;
private final DumpController mDumpController;
@@ -51,17 +53,19 @@
private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer();
@Inject
- public NewNotifPipeline(
+ public NotifPipelineInitializer(
+ NotifPipeline pipelineWrapper,
GroupCoalescer groupCoalescer,
NotifCollection notifCollection,
- NotifListBuilderImpl notifPipeline,
+ ShadeListBuilder listBuilder,
NotifCoordinators notifCoordinators,
NotifInflaterImpl notifInflater,
DumpController dumpController,
FeatureFlags featureFlags) {
+ mPipelineWrapper = pipelineWrapper;
mGroupCoalescer = groupCoalescer;
mNotifCollection = notifCollection;
- mNotifPipeline = notifPipeline;
+ mListBuilder = listBuilder;
mNotifPluggableCoordinators = notifCoordinators;
mDumpController = dumpController;
mNotifInflater = notifInflater;
@@ -81,11 +85,11 @@
}
// Wire up coordinators
- mFakePipelineConsumer.attach(mNotifPipeline);
- mNotifPluggableCoordinators.attach(mNotifCollection, mNotifPipeline);
+ mNotifPluggableCoordinators.attach(mPipelineWrapper);
// Wire up pipeline
- mNotifPipeline.attach(mNotifCollection);
+ mFakePipelineConsumer.attach(mListBuilder);
+ mListBuilder.attach(mNotifCollection);
mNotifCollection.attach(mGroupCoalescer);
mGroupCoalescer.attach(notificationService);
@@ -99,5 +103,5 @@
mGroupCoalescer.dump(fd, pw, args);
}
- private static final String TAG = "NewNotifPipeline";
+ private static final String TAG = "NotifPipeline";
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
deleted file mode 100644
index 7580924..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.listbuilder;
-
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
-
-import java.util.List;
-
-/**
- * The system that constructs the current "notification list", the list of notifications that are
- * currently being displayed to the user.
- *
- * The pipeline proceeds through a series of stages in order to produce the final list (see below).
- * Each stage exposes hooks and listeners for other code to participate.
- *
- * This list differs from the canonical one we receive from system server in a few ways:
- * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose
- * views haven't been inflated yet. We also filter out some notifications if we're on the lock
- * screen. To participate, see {@link #addFilter(NotifFilter)}.
- * - Grouped: Notifications that are part of the same group are clustered together into a single
- * GroupEntry. These groups are then transformed in order to remove children or completely split
- * them apart. To participate, see {@link #addPromoter(NotifPromoter)}.
- * - Sorted: All top-level notifications are sorted. To participate, see
- * {@link #setSectionsProvider(SectionsProvider)} and {@link #setComparators(List)}
- *
- * The exact order of all hooks is as follows:
- * 0. Collection listeners are fired (see {@link NotifCollection}).
- * 1. NotifFilters are called on each notification currently in NotifCollection.
- * 2. Initial grouping is performed (NotificationEntries will have their parents set
- * appropriately).
- * 3. OnBeforeTransformGroupListeners are fired
- * 4. NotifPromoters are called on each notification with a parent
- * 5. OnBeforeSortListeners are fired
- * 6. SectionsProvider is called on each top-level entry in the list
- * 7. The top-level entries are sorted using the provided NotifComparators (plus some additional
- * built-in logic).
- * 8. OnBeforeRenderListListeners are fired
- * 9. The list is handed off to the view layer to be rendered.
- */
-public interface NotifListBuilder {
-
- /**
- * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
- * are called on each notification in the order that they were registered. If any filter
- * returns true, the notification is removed from the pipeline (and no other filters are
- * called on that notif).
- */
- void addPreGroupFilter(NotifFilter filter);
-
- /**
- * Registers a promoter with the pipeline. Promoters are able to promote child notifications to
- * top-level, i.e. move a notification that would be a child of a group and make it appear
- * ungrouped. Promoters are called on each child notification in the order that they are
- * registered. If any promoter returns true, the notification is removed from the group (and no
- * other promoters are called on it).
- */
- void addPromoter(NotifPromoter promoter);
-
- /**
- * Assigns sections to each top-level entry, where a section is simply an integer. Sections are
- * the primary metric by which top-level entries are sorted; NotifComparators are only consulted
- * when two entries are in the same section. The pipeline doesn't assign any particular meaning
- * to section IDs -- from it's perspective they're just numbers and it sorts them by a simple
- * numerical comparison.
- */
- void setSectionsProvider(SectionsProvider provider);
-
- /**
- * Comparators that are used to sort top-level entries that share the same section. The
- * comparators are executed in order until one of them returns a non-zero result. If all return
- * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when).
- */
- void setComparators(List<NotifComparator> comparators);
-
- /**
- * Registers a filter with the pipeline to filter right before rendering the list (after
- * pre-group filtering, grouping, promoting and sorting occurs). Filters are
- * called on each notification in the order that they were registered. If any filter returns
- * true, the notification is removed from the pipeline (and no other filters are called on that
- * notif).
- */
- void addPreRenderFilter(NotifFilter filter);
-
- /**
- * Called after notifications have been filtered and after the initial grouping has been
- * performed but before NotifPromoters have had a chance to promote children out of groups.
- */
- void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener);
-
- /**
- * Called after notifs have been filtered and groups have been determined but before sections
- * have been determined or the notifs have been sorted.
- */
- void addOnBeforeSortListener(OnBeforeSortListener listener);
-
- /**
- * Called at the end of the pipeline after the notif list has been finalized but before it has
- * been handed off to the view layer.
- */
- void addOnBeforeRenderListListener(OnBeforeRenderListListener listener);
-
- /**
- * Returns a read-only view in to the current notification list. If this method is called
- * during pipeline execution it will return the current state of the list, which will likely
- * be only partially-generated.
- */
- List<ListEntry> getActiveNotifs();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
index f6ca12d..44a27a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
@@ -17,10 +17,11 @@
package com.android.systemui.statusbar.notification.collection.listbuilder;
import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import java.util.List;
-/** See {@link NotifListBuilder#addOnBeforeRenderListListener(OnBeforeRenderListListener)} */
+/** See {@link NotifPipeline#addOnBeforeRenderListListener(OnBeforeRenderListListener)} */
public interface OnBeforeRenderListListener {
/**
* Called at the end of the pipeline after the notif list has been finalized but before it has
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
index 7be7ac0..56cfe5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
@@ -17,10 +17,11 @@
package com.android.systemui.statusbar.notification.collection.listbuilder;
import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import java.util.List;
-/** See {@link NotifListBuilder#addOnBeforeSortListener(OnBeforeSortListener)} */
+/** See {@link NotifPipeline#addOnBeforeSortListener(OnBeforeSortListener)} */
public interface OnBeforeSortListener {
/**
* Called after the notif list has been filtered and grouped but before sections have been
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
index d7a0815..0dc4df0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
@@ -17,13 +17,14 @@
package com.android.systemui.statusbar.notification.collection.listbuilder;
import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import java.util.List;
/**
* See
- * {@link NotifListBuilder#addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener)}
+ * {@link NotifPipeline#addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener)}
*/
public interface OnBeforeTransformGroupsListener {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
index 084d038..1897ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
@@ -18,13 +18,13 @@
import android.annotation.IntDef;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * Used by {@link NotifListBuilderImpl} to track its internal state machine.
+ * Used by {@link ShadeListBuilder} to track its internal state machine.
*/
public class PipelineState {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
index a191c83..0d150ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
@@ -17,13 +17,13 @@
package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import java.util.Comparator;
import java.util.List;
/**
- * Pluggable for participating in notif sorting. See {@link NotifListBuilder#setComparators(List)}.
+ * Pluggable for participating in notif sorting. See {@link NotifPipeline#setComparators(List)}.
*/
public abstract class NotifComparator
extends Pluggable<NotifComparator>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
index e6189ed..8f575cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
@@ -16,12 +16,12 @@
package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
/**
* Pluggable for participating in notif filtering.
- * See {@link NotifListBuilder#addPreGroupFilter} and {@link NotifListBuilder#addPreRenderFilter}.
+ * See {@link NotifPipeline#addPreGroupFilter} and {@link NotifPipeline#addPreRenderFilter}.
*/
public abstract class NotifFilter extends Pluggable<NotifFilter> {
protected NotifFilter(String name) {
@@ -35,9 +35,9 @@
* however. If another filter returns true before yours, we'll skip straight to the next notif.
*
* @param entry The entry in question.
- * If this filter is registered via {@link NotifListBuilder#addPreGroupFilter},
+ * If this filter is registered via {@link NotifPipeline#addPreGroupFilter},
* this entry will not have any grouping nor sorting information.
- * If this filter is registered via {@link NotifListBuilder#addPreRenderFilter},
+ * If this filter is registered via {@link NotifPipeline#addPreRenderFilter},
* this entry will have grouping and sorting information.
* @param now A timestamp in SystemClock.uptimeMillis that represents "now" for the purposes of
* pipeline execution. This value will be the same for all pluggable calls made
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java
index 84e16f4..5fce446 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java
@@ -16,13 +16,13 @@
package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
/**
* Pluggable for participating in notif promotion. Notif promoters can upgrade notifications
* from being children of a group to top-level notifications. See
- * {@link NotifListBuilder#addPromoter(NotifPromoter)}.
+ * {@link NotifPipeline#addPromoter}.
*/
public abstract class NotifPromoter extends Pluggable<NotifPromoter> {
protected NotifPromoter(String name) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
new file mode 100644
index 0000000..fe5ba3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+
+/**
+ * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}.
+ */
+public abstract class NotifSection extends Pluggable<NotifSection> {
+ protected NotifSection(String name) {
+ super(name);
+ }
+
+ /**
+ * If returns true, this notification is considered within this section.
+ * Sectioning is performed on each top level notification each time the pipeline is run.
+ * However, this doesn't necessarily mean that your section will get called on each top-level
+ * notification. The first section to return true determines the section of the notification.
+ */
+ public abstract boolean isInSection(ListEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
index f9ce197..4270408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
@@ -18,10 +18,10 @@
import android.annotation.Nullable;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
/**
- * Generic superclass for chunks of code that can plug into the {@link NotifListBuilder}.
+ * Generic superclass for chunks of code that can plug into the {@link NotifPipeline}.
*
* A pluggable is fundamentally three things:
* 1. A name (for debugging purposes)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java
deleted file mode 100644
index 11ea850..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
-
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-
-/**
- * Interface for sorting notifications into "sections", such as a heads-upping section, people
- * section, alerting section, silent section, etc.
- */
-public abstract class SectionsProvider extends Pluggable<SectionsProvider> {
-
- protected SectionsProvider(String name) {
- super(name);
- }
-
- /**
- * Returns the section that this entry belongs to. A section can be any non-negative integer.
- * When entries are sorted, they are first sorted by section and then by any remainining
- * comparators.
- */
- public abstract int getSection(ListEntry entry);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
index 87aaea0..4023474 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.notifcollection;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.Collection;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java
index ecce6ea..b268686 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.notifcollection;
import android.service.notification.NotificationStats.DismissalSentiment;
import android.service.notification.NotificationStats.DismissalSurface;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index 032620e..9cbc7d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.notifcollection;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Listener interface for {@link NotifCollection}.
@@ -36,7 +38,9 @@
}
/**
- * Called immediately after a notification has been removed from the collection.
+ * Called whenever a notification is retracted by system server. This method is not called
+ * immediately after a user dismisses a notification: we wait until we receive confirmation from
+ * system server before considering the notification removed.
*/
default void onEntryRemoved(
NotificationEntry entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
index 2c7b138..05f5ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.notifcollection;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* A way for other code to temporarily extend the lifetime of a notification after it has been
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index c6c36ee..2374cde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -22,8 +22,8 @@
import com.android.systemui.log.RichEvent;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -67,7 +67,7 @@
}
/**
- * @return if this event occurred in {@link NotifListBuilder}
+ * @return if this event occurred in {@link ShadeListBuilder}
*/
static boolean isListBuilderEvent(@EventType int type) {
return isBetweenInclusive(type, 0, TOTAL_LIST_BUILDER_EVENT_TYPES);
@@ -94,7 +94,7 @@
LIST_BUILD_COMPLETE,
PRE_GROUP_FILTER_INVALIDATED,
PROMOTER_INVALIDATED,
- SECTIONS_PROVIDER_INVALIDATED,
+ SECTION_INVALIDATED,
COMPARATOR_INVALIDATED,
PARENT_CHANGED,
FILTER_CHANGED,
@@ -132,12 +132,13 @@
"ListBuildComplete",
"FilterInvalidated",
"PromoterInvalidated",
- "SectionsProviderInvalidated",
+ "SectionInvalidated",
"ComparatorInvalidated",
"ParentChanged",
"FilterChanged",
"PromoterChanged",
"FinalFilterInvalidated",
+ "SectionerChanged",
// NEM event labels:
"NotifAdded",
@@ -161,7 +162,7 @@
private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length;
/**
- * Events related to {@link NotifListBuilder}
+ * Events related to {@link ShadeListBuilder}
*/
public static final int WARN = 0;
public static final int ON_BUILD_LIST = 1;
@@ -170,13 +171,14 @@
public static final int LIST_BUILD_COMPLETE = 4;
public static final int PRE_GROUP_FILTER_INVALIDATED = 5;
public static final int PROMOTER_INVALIDATED = 6;
- public static final int SECTIONS_PROVIDER_INVALIDATED = 7;
+ public static final int SECTION_INVALIDATED = 7;
public static final int COMPARATOR_INVALIDATED = 8;
public static final int PARENT_CHANGED = 9;
public static final int FILTER_CHANGED = 10;
public static final int PROMOTER_CHANGED = 11;
public static final int PRE_RENDER_FILTER_INVALIDATED = 12;
- private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 13;
+ public static final int SECTION_CHANGED = 13;
+ private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14;
/**
* Events related to {@link NotificationEntryManager}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index 78eaf3e..452d1eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -17,7 +17,9 @@
package com.android.systemui.statusbar.notification.people
import android.app.Notification
+import android.content.Context
import android.service.notification.StatusBarNotification
+import android.util.FeatureFlagUtils
import javax.inject.Inject
import javax.inject.Singleton
@@ -27,10 +29,16 @@
@Singleton
class PeopleNotificationIdentifierImpl @Inject constructor(
- private val personExtractor: NotificationPersonExtractor
+ private val personExtractor: NotificationPersonExtractor,
+ private val context: Context
) : PeopleNotificationIdentifier {
override fun isPeopleNotification(sbn: StatusBarNotification) =
- sbn.notification.notificationStyle == Notification.MessagingStyle::class.java ||
+ (sbn.notification.notificationStyle == Notification.MessagingStyle::class.java &&
+ (sbn.notification.shortcutId != null ||
+ FeatureFlagUtils.isEnabled(
+ context,
+ FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ
+ ))) ||
personExtractor.isPersonNotification(sbn)
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 3c247df..b71beda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -65,7 +65,6 @@
import android.widget.Chronometer;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
@@ -150,7 +149,7 @@
private StatusBarStateController mStatusbarStateController;
private KeyguardBypassController mBypassController;
private LayoutListener mLayoutListener;
- private final NotificationContentInflater mNotificationInflater;
+ private NotificationRowContentBinder mNotificationContentBinder;
private int mIconTransformContentShift;
private int mIconTransformContentShiftNoIcon;
private int mMaxHeadsUpHeightBeforeN;
@@ -464,7 +463,7 @@
* Inflate views based off the inflation flags set. Inflation happens asynchronously.
*/
public void inflateViews() {
- mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+ mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
false /* forceInflate */, mInflationCallback);
}
@@ -478,7 +477,7 @@
// View should not be reinflated in the future
clearInflationFlags(inflationFlag);
Runnable freeViewRunnable =
- () -> mNotificationInflater.unbindContent(mEntry, this, inflationFlag);
+ () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag);
switch (inflationFlag) {
case FLAG_CONTENT_VIEW_HEADS_UP:
getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
@@ -742,23 +741,10 @@
return mIsHeadsUp || mHeadsupDisappearRunning;
}
-
- public void setGroupManager(NotificationGroupManager groupManager) {
- mGroupManager = groupManager;
- mPrivateLayout.setGroupManager(groupManager);
- }
-
public void setRemoteInputController(RemoteInputController r) {
mPrivateLayout.setRemoteInputController(r);
}
- public void setAppName(String appName) {
- mAppName = appName;
- if (mMenuRow != null && mMenuRow.getMenuView() != null) {
- mMenuRow.setAppName(mAppName);
- }
- }
-
public void addChildNotification(ExpandableNotificationRow row) {
addChildNotification(row, -1);
}
@@ -852,7 +838,7 @@
mIsChildInGroup = isChildInGroup;
if (mIsLowPriority) {
int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
- mNotificationInflater.bindContent(mEntry, this, flags, mBindParams,
+ mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams,
false /* forceInflate */, mInflationCallback);
}
}
@@ -1105,10 +1091,6 @@
return mPrivateLayout.getContractedNotificationHeader();
}
- public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
- mOnExpandClickListener = onExpandClickListener;
- }
-
public void setLongPressListener(LongPressListener longPressListener) {
mLongPressListener = longPressListener;
}
@@ -1131,10 +1113,6 @@
}
}
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
- mHeadsUpManager = headsUpManager;
- }
-
public HeadsUpManager getHeadsUpManager() {
return mHeadsUpManager;
}
@@ -1172,6 +1150,7 @@
mMenuRow = plugin;
if (mMenuRow.shouldUseDefaultMenuItems()) {
ArrayList<MenuItem> items = new ArrayList<>();
+ items.add(NotificationMenuRow.createConversationItem(mContext));
items.add(NotificationMenuRow.createInfoItem(mContext));
items.add(NotificationMenuRow.createSnoozeItem(mContext));
items.add(NotificationMenuRow.createAppOpsItem(mContext));
@@ -1185,7 +1164,7 @@
@Override
public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
boolean existed = mMenuRow.getMenuView() != null;
- mMenuRow = new NotificationMenuRow(mContext); // Back to default
+ mMenuRow = new NotificationMenuRow(mContext);
if (existed) {
createMenu();
}
@@ -1259,7 +1238,7 @@
l.reInflateViews();
}
mEntry.getSbn().clearPackageContext();
- mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+ mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
true /* forceInflate */, mInflationCallback);
}
@@ -1634,10 +1613,6 @@
mBindParams.usesIncreasedHeadsUpHeight = use;
}
- public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
- mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
- }
-
/**
* Set callback for notification content inflation
*
@@ -1652,7 +1627,7 @@
mNeedsRedaction = needsRedaction;
if (needsRedaction) {
setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
- mNotificationInflater.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
+ mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
mBindParams, false /* forceInflate */, mInflationCallback);
} else {
clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
@@ -1661,18 +1636,12 @@
}
}
- @VisibleForTesting
- public NotificationContentInflater getNotificationInflater() {
- return mNotificationInflater;
- }
-
public interface ExpansionLogger {
void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
- mNotificationInflater = new NotificationContentInflater();
mMenuRow = new NotificationMenuRow(mContext);
mImageResolver = new NotificationInlineImageResolver(context,
new NotificationInlineImageCache());
@@ -1680,8 +1649,30 @@
initDimens();
}
- public void setBypassController(KeyguardBypassController bypassController) {
+ /**
+ * Initialize row.
+ */
+ public void initialize(
+ String appName,
+ String notificationKey,
+ ExpansionLogger logger,
+ KeyguardBypassController bypassController,
+ NotificationGroupManager groupManager,
+ HeadsUpManager headsUpManager,
+ NotificationRowContentBinder rowContentBinder,
+ OnExpandClickListener onExpandClickListener) {
+ mAppName = appName;
+ if (mMenuRow != null && mMenuRow.getMenuView() != null) {
+ mMenuRow.setAppName(mAppName);
+ }
+ mLogger = logger;
+ mLoggingKey = notificationKey;
mBypassController = bypassController;
+ mGroupManager = groupManager;
+ mPrivateLayout.setGroupManager(groupManager);
+ mHeadsUpManager = headsUpManager;
+ mNotificationContentBinder = rowContentBinder;
+ mOnExpandClickListener = onExpandClickListener;
}
public void setStatusBarStateController(StatusBarStateController statusBarStateController) {
@@ -1730,6 +1721,8 @@
*/
public void reset() {
mShowingPublicInitialized = false;
+ unDismiss();
+ resetTranslation();
onHeightReset();
requestLayout();
}
@@ -2920,11 +2913,6 @@
return 0;
}
- public void setExpansionLogger(ExpansionLogger logger, String key) {
- mLogger = logger;
- mLoggingKey = key;
- }
-
public void onExpandedByGesture(boolean userExpanded) {
int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
new file mode 100644
index 0000000..c11c60f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import android.widget.RemoteViews;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+/**
+ * Caches {@link RemoteViews} for a notification's content views.
+ */
+public interface NotifRemoteViewCache {
+
+ /**
+ * Whether the notification has the remote view cached
+ *
+ * @param entry notification
+ * @param flag inflation flag for content view
+ * @return true if the remote view is cached
+ */
+ boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+ /**
+ * Get the remote view for the content flag specified.
+ *
+ * @param entry notification
+ * @param flag inflation flag for the content view
+ * @return the remote view if it is cached, null otherwise
+ */
+ @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+ /**
+ * Cache a remote view for a given content flag on a notification.
+ *
+ * @param entry notification
+ * @param flag inflation flag for the content view
+ * @param remoteView remote view to store
+ */
+ void putCachedView(
+ NotificationEntry entry,
+ @InflationFlag int flag,
+ RemoteViews remoteView);
+
+ /**
+ * Remove a cached remote view for a given content flag on a notification.
+ *
+ * @param entry notification
+ * @param flag inflation flag for the content view
+ */
+ void removeCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+ /**
+ * Clear a notification's remote view cache.
+ *
+ * @param entry notification
+ */
+ void clearCache(NotificationEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
new file mode 100644
index 0000000..a6e5c2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Implementation of remote view cache that keeps remote views cached for all active notifications.
+ */
+public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache {
+ private final Map<NotificationEntry, SparseArray<RemoteViews>> mNotifCachedContentViews =
+ new ArrayMap<>();
+
+ @Inject
+ NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) {
+ entryManager.addNotificationEntryListener(mEntryListener);
+ }
+
+ @Override
+ public boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag) {
+ return getCachedView(entry, flag) != null;
+ }
+
+ @Override
+ public @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag) {
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return null;
+ }
+ return contentViews.get(flag);
+ }
+
+ @Override
+ public void putCachedView(
+ NotificationEntry entry,
+ @InflationFlag int flag,
+ RemoteViews remoteView) {
+ /**
+ * TODO: We should be more strict here in the future (i.e. throw an exception) if the
+ * content views aren't created. We don't do that right now because we have edge cases
+ * where we may bind/unbind content after a notification is removed.
+ */
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return;
+ }
+ contentViews.put(flag, remoteView);
+ }
+
+ @Override
+ public void removeCachedView(NotificationEntry entry, @InflationFlag int flag) {
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return;
+ }
+ contentViews.remove(flag);
+ }
+
+ @Override
+ public void clearCache(NotificationEntry entry) {
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return;
+ }
+ contentViews.clear();
+ }
+
+ private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+ @Override
+ public void onPendingEntryAdded(NotificationEntry entry) {
+ mNotifCachedContentViews.put(entry, new SparseArray<>());
+ }
+
+ @Override
+ public void onEntryRemoved(
+ NotificationEntry entry,
+ @Nullable NotificationVisibility visibility,
+ boolean removedByUser) {
+ mNotifCachedContentViews.remove(entry);
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 30f22ac..e1a6747 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
@@ -26,7 +27,6 @@
import android.os.AsyncTask;
import android.os.CancellationSignal;
import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
@@ -35,6 +35,7 @@
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
@@ -49,17 +50,30 @@
import java.util.HashMap;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
* asynchronously building the content's {@link RemoteViews} and applying it to the row.
*/
+@Singleton
+@VisibleForTesting(visibility = PACKAGE)
public class NotificationContentInflater implements NotificationRowContentBinder {
public static final String TAG = "NotifContentInflater";
- private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private boolean mInflateSynchronously = false;
- private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
+ private final NotificationRemoteInputManager mRemoteInputManager;
+ private final NotifRemoteViewCache mRemoteViewCache;
+
+ @Inject
+ public NotificationContentInflater(
+ NotifRemoteViewCache remoteViewCache,
+ NotificationRemoteInputManager remoteInputManager) {
+ mRemoteViewCache = remoteViewCache;
+ mRemoteInputManager = remoteInputManager;
+ }
@Override
public void bindContent(
@@ -76,27 +90,27 @@
return;
}
- StatusBarNotification sbn = row.getEntry().getSbn();
+ StatusBarNotification sbn = entry.getSbn();
// To check if the notification has inline image and preload inline image if necessary.
row.getImageResolver().preloadImages(sbn.getNotification());
if (forceInflate) {
- mCachedContentViews.clear();
+ mRemoteViewCache.clearCache(entry);
}
AsyncInflationTask task = new AsyncInflationTask(
- sbn,
mInflateSynchronously,
contentToBind,
- mCachedContentViews,
+ mRemoteViewCache,
+ entry,
row,
bindParams.isLowPriority,
bindParams.isChildInGroup,
bindParams.usesIncreasedHeight,
bindParams.usesIncreasedHeadsUpHeight,
callback,
- mRemoteViewClickHandler);
+ mRemoteInputManager.getRemoteViewsOnClickHandler());
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
} else {
@@ -123,13 +137,15 @@
result = inflateSmartReplyViews(result, reInflateFlags, entry,
row.getContext(), packageContext, row.getHeadsUpManager(),
row.getExistingSmartRepliesAndActions());
+
apply(
inflateSynchronously,
result,
reInflateFlags,
- mCachedContentViews,
+ mRemoteViewCache,
+ entry,
row,
- mRemoteViewClickHandler,
+ mRemoteInputManager.getRemoteViewsOnClickHandler(),
null);
return result;
}
@@ -149,7 +165,7 @@
int curFlag = 1;
while (contentToUnbind != 0) {
if ((contentToUnbind & curFlag) != 0) {
- freeNotificationView(row, curFlag);
+ freeNotificationView(entry, row, curFlag);
}
contentToUnbind &= ~curFlag;
curFlag = curFlag << 1;
@@ -157,34 +173,25 @@
}
/**
- * Set click handler for notification remote views
- *
- * @param remoteViewClickHandler click handler for remote views
- */
- public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
- mRemoteViewClickHandler = remoteViewClickHandler;
- }
-
- /**
* Frees the content view associated with the inflation flag. Will only succeed if the
* view is safe to remove.
*
* @param inflateFlag the flag corresponding to the content view which should be freed
*/
- private void freeNotificationView(ExpandableNotificationRow row,
+ private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row,
@InflationFlag int inflateFlag) {
switch (inflateFlag) {
case FLAG_CONTENT_VIEW_HEADS_UP:
if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
row.getPrivateLayout().setHeadsUpChild(null);
- mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP);
+ mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
}
break;
case FLAG_CONTENT_VIEW_PUBLIC:
if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
row.getPublicLayout().setContractedChild(null);
- mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC);
+ mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
}
break;
case FLAG_CONTENT_VIEW_CONTRACTED:
@@ -245,11 +252,12 @@
return result;
}
- public static CancellationSignal apply(
+ private static CancellationSignal apply(
boolean inflateSynchronously,
InflationProgress result,
@InflationFlag int reInflateFlags,
- ArrayMap<Integer, RemoteViews> cachedContentViews,
+ NotifRemoteViewCache remoteViewCache,
+ NotificationEntry entry,
ExpandableNotificationRow row,
RemoteViews.OnClickHandler remoteViewClickHandler,
@Nullable InflationCallback callback) {
@@ -261,7 +269,7 @@
if ((reInflateFlags & flag) != 0) {
boolean isNewView =
!canReapplyRemoteView(result.newContentView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -273,8 +281,8 @@
return result.newContentView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
- row, isNewView, remoteViewClickHandler, callback, privateLayout,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler, callback, privateLayout,
privateLayout.getContractedChild(), privateLayout.getVisibleWrapper(
NotificationContentView.VISIBLE_TYPE_CONTRACTED),
runningInflations, applyCallback);
@@ -285,7 +293,7 @@
if (result.newExpandedView != null) {
boolean isNewView =
!canReapplyRemoteView(result.newExpandedView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -297,8 +305,8 @@
return result.newExpandedView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
- cachedContentViews, row, isNewView, remoteViewClickHandler,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler,
callback, privateLayout, privateLayout.getExpandedChild(),
privateLayout.getVisibleWrapper(
NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
@@ -311,7 +319,7 @@
if (result.newHeadsUpView != null) {
boolean isNewView =
!canReapplyRemoteView(result.newHeadsUpView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -323,8 +331,8 @@
return result.newHeadsUpView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
- cachedContentViews, row, isNewView, remoteViewClickHandler,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler,
callback, privateLayout, privateLayout.getHeadsUpChild(),
privateLayout.getVisibleWrapper(
VISIBLE_TYPE_HEADSUP), runningInflations,
@@ -336,7 +344,7 @@
if ((reInflateFlags & flag) != 0) {
boolean isNewView =
!canReapplyRemoteView(result.newPublicView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -348,15 +356,16 @@
return result.newPublicView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
- row, isNewView, remoteViewClickHandler, callback,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler, callback,
publicLayout, publicLayout.getContractedChild(),
publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
runningInflations, applyCallback);
}
// Let's try to finish, maybe nobody is even inflating anything
- finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row);
+ finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry,
+ row);
CancellationSignal cancellationSignal = new CancellationSignal();
cancellationSignal.setOnCancelListener(
() -> runningInflations.values().forEach(CancellationSignal::cancel));
@@ -369,7 +378,8 @@
final InflationProgress result,
final @InflationFlag int reInflateFlags,
@InflationFlag int inflationId,
- final ArrayMap<Integer, RemoteViews> cachedContentViews,
+ final NotifRemoteViewCache remoteViewCache,
+ final NotificationEntry entry,
final ExpandableNotificationRow row,
boolean isNewView,
RemoteViews.OnClickHandler remoteViewClickHandler,
@@ -422,8 +432,8 @@
existingWrapper.onReinflated();
}
runningInflations.remove(inflationId);
- finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations,
- callback, row);
+ finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations,
+ callback, entry, row);
}
@Override
@@ -488,11 +498,11 @@
* @return true if the inflation was finished
*/
private static boolean finishIfDone(InflationProgress result,
- @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews,
+ @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache,
HashMap<Integer, CancellationSignal> runningInflations,
- @Nullable InflationCallback endListener, ExpandableNotificationRow row) {
+ @Nullable InflationCallback endListener, NotificationEntry entry,
+ ExpandableNotificationRow row) {
Assert.isMainThread();
- NotificationEntry entry = row.getEntry();
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
if (runningInflations.isEmpty()) {
@@ -500,23 +510,27 @@
if (result.inflatedContentView != null) {
// New view case
privateLayout.setContractedChild(result.inflatedContentView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED) != null) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
// Reinflation case. Only update if it's still cached (i.e. view has not been
// freed while inflating).
- cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
}
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
if (result.inflatedExpandedView != null) {
privateLayout.setExpandedChild(result.inflatedExpandedView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
} else if (result.newExpandedView == null) {
privateLayout.setExpandedChild(null);
- cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, null);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED) != null) {
- cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
}
if (result.newExpandedView != null) {
privateLayout.setExpandedInflatedSmartReplies(
@@ -530,12 +544,14 @@
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
if (result.inflatedHeadsUpView != null) {
privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
} else if (result.newHeadsUpView == null) {
privateLayout.setHeadsUpChild(null);
- cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, null);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP) != null) {
- cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
}
if (result.newHeadsUpView != null) {
privateLayout.setHeadsUpInflatedSmartReplies(
@@ -548,16 +564,18 @@
if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
if (result.inflatedPublicView != null) {
publicLayout.setContractedChild(result.inflatedPublicView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC) != null) {
- cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
}
}
entry.headsUpStatusBarText = result.headsUpStatusBarText;
entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
if (endListener != null) {
- endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags);
+ endListener.onAsyncInflationFinished(entry, reInflateFlags);
}
return true;
}
@@ -615,7 +633,7 @@
public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
implements InflationCallback, InflationTask {
- private final StatusBarNotification mSbn;
+ private final NotificationEntry mEntry;
private final Context mContext;
private final boolean mInflateSynchronously;
private final boolean mIsLowPriority;
@@ -624,17 +642,17 @@
private final InflationCallback mCallback;
private final boolean mUsesIncreasedHeadsUpHeight;
private @InflationFlag int mReInflateFlags;
- private final ArrayMap<Integer, RemoteViews> mCachedContentViews;
+ private final NotifRemoteViewCache mRemoteViewCache;
private ExpandableNotificationRow mRow;
private Exception mError;
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private CancellationSignal mCancellationSignal;
private AsyncInflationTask(
- StatusBarNotification notification,
boolean inflateSynchronously,
@InflationFlag int reInflateFlags,
- ArrayMap<Integer, RemoteViews> cachedContentViews,
+ NotifRemoteViewCache cache,
+ NotificationEntry entry,
ExpandableNotificationRow row,
boolean isLowPriority,
boolean isChildInGroup,
@@ -642,11 +660,11 @@
boolean usesIncreasedHeadsUpHeight,
InflationCallback callback,
RemoteViews.OnClickHandler remoteViewClickHandler) {
+ mEntry = entry;
mRow = row;
- mSbn = notification;
mInflateSynchronously = inflateSynchronously;
mReInflateFlags = reInflateFlags;
- mCachedContentViews = cachedContentViews;
+ mRemoteViewCache = cache;
mContext = mRow.getContext();
mIsLowPriority = isLowPriority;
mIsChildInGroup = isChildInGroup;
@@ -654,7 +672,6 @@
mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
- NotificationEntry entry = row.getEntry();
entry.setInflationTask(this);
}
@@ -667,12 +684,13 @@
@Override
protected InflationProgress doInBackground(Void... params) {
try {
+ final StatusBarNotification sbn = mEntry.getSbn();
final Notification.Builder recoveredBuilder
= Notification.Builder.recoverBuilder(mContext,
- mSbn.getNotification());
+ sbn.getNotification());
- Context packageContext = mSbn.getPackageContext(mContext);
- Notification notification = mSbn.getNotification();
+ Context packageContext = sbn.getPackageContext(mContext);
+ Notification notification = sbn.getNotification();
if (notification.isMediaNotification()) {
MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
packageContext);
@@ -681,7 +699,7 @@
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, packageContext);
- return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
+ return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
mRow.getExistingSmartRepliesAndActions());
} catch (Exception e) {
@@ -694,15 +712,15 @@
protected void onPostExecute(InflationProgress result) {
if (mError == null) {
mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags,
- mCachedContentViews, mRow, mRemoteViewClickHandler, this);
+ mRemoteViewCache, mEntry, mRow, mRemoteViewClickHandler, this);
} else {
handleError(mError);
}
}
private void handleError(Exception e) {
- mRow.getEntry().onInflationTaskFinished();
- StatusBarNotification sbn = mRow.getEntry().getSbn();
+ mEntry.onInflationTaskFinished();
+ StatusBarNotification sbn = mEntry.getSbn();
final String ident = sbn.getPackageName() + "/0x"
+ Integer.toHexString(sbn.getId());
Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
@@ -736,10 +754,10 @@
@Override
public void onAsyncInflationFinished(NotificationEntry entry,
@InflationFlag int inflatedFlags) {
- mRow.getEntry().onInflationTaskFinished();
+ mEntry.onInflationTaskFinished();
mRow.onNotificationUpdated();
if (mCallback != null) {
- mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags);
+ mCallback.onAsyncInflationFinished(mEntry, inflatedFlags);
}
// Notify the resolver that the inflation task has finished,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
new file mode 100644
index 0000000..ec420f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
+import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.lang.annotation.Retention;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The guts of a conversation notification revealed when performing a long press.
+ */
+public class NotificationConversationInfo extends LinearLayout implements
+ NotificationGuts.GutsContent {
+ private static final String TAG = "ConversationGuts";
+
+
+ private INotificationManager mINotificationManager;
+ private LauncherApps mLauncherApps;
+ ShortcutManager mShortcutManager;
+ private PackageManager mPm;
+ private VisualStabilityManager mVisualStabilityManager;
+
+ private String mPackageName;
+ private String mAppName;
+ private int mAppUid;
+ private String mDelegatePkg;
+ private NotificationChannel mNotificationChannel;
+ private ShortcutInfo mShortcutInfo;
+ private String mConversationId;
+ private NotificationEntry mEntry;
+ private StatusBarNotification mSbn;
+ private boolean mIsDeviceProvisioned;
+
+ private int mStartingChannelImportance;
+ private boolean mStartedAsBubble;
+ private boolean mIsBubbleable;
+ // TODO: remove when launcher api works
+ @VisibleForTesting
+ boolean mShowHomeScreen = false;
+
+ private @UpdateChannelRunnable.Action int mSelectedAction = -1;
+
+ private OnSnoozeClickListener mOnSnoozeClickListener;
+ private OnSettingsClickListener mOnSettingsClickListener;
+ private OnAppSettingsClickListener mAppSettingsClickListener;
+ private NotificationGuts mGutsContainer;
+ private BubbleController mBubbleController;
+
+ @VisibleForTesting
+ boolean mSkipPost = false;
+
+ private OnClickListener mOnBubbleClick = v -> {
+ mSelectedAction = ACTION_BUBBLE;
+ if (mStartedAsBubble) {
+ mBubbleController.onUserDemotedBubbleFromNotification(mEntry);
+ } else {
+ mBubbleController.onUserCreatedBubbleFromNotification(mEntry);
+ }
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnHomeClick = v -> {
+ mSelectedAction = ACTION_HOME;
+ mShortcutManager.requestPinShortcut(mShortcutInfo, null);
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnFavoriteClick = v -> {
+ mSelectedAction = ACTION_FAVORITE;
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnSnoozeClick = v -> {
+ mSelectedAction = ACTION_SNOOZE;
+ mOnSnoozeClickListener.onClick(v, 1);
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnMuteClick = v -> {
+ mSelectedAction = ACTION_MUTE;
+ closeControls(v, true);
+ };
+
+ public NotificationConversationInfo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public interface OnSettingsClickListener {
+ void onClick(View v, NotificationChannel channel, int appUid);
+ }
+
+ public interface OnAppSettingsClickListener {
+ void onClick(View v, Intent intent);
+ }
+
+ public interface OnSnoozeClickListener {
+ void onClick(View v, int hoursToSnooze);
+ }
+
+ public void bindNotification(
+ ShortcutManager shortcutManager,
+ LauncherApps launcherApps,
+ PackageManager pm,
+ INotificationManager iNotificationManager,
+ VisualStabilityManager visualStabilityManager,
+ String pkg,
+ NotificationChannel notificationChannel,
+ NotificationEntry entry,
+ OnSettingsClickListener onSettingsClick,
+ OnAppSettingsClickListener onAppSettingsClick,
+ OnSnoozeClickListener onSnoozeClickListener,
+ boolean isDeviceProvisioned) {
+ mSelectedAction = -1;
+ mINotificationManager = iNotificationManager;
+ mVisualStabilityManager = visualStabilityManager;
+ mBubbleController = Dependency.get(BubbleController.class);
+ mPackageName = pkg;
+ mEntry = entry;
+ mSbn = entry.getSbn();
+ mPm = pm;
+ mAppSettingsClickListener = onAppSettingsClick;
+ mAppName = mPackageName;
+ mOnSettingsClickListener = onSettingsClick;
+ mNotificationChannel = notificationChannel;
+ mStartingChannelImportance = mNotificationChannel.getImportance();
+ mAppUid = mSbn.getUid();
+ mDelegatePkg = mSbn.getOpPkg();
+ mIsDeviceProvisioned = isDeviceProvisioned;
+ mOnSnoozeClickListener = onSnoozeClickListener;
+
+ mShortcutManager = shortcutManager;
+ mLauncherApps = launcherApps;
+ mConversationId = mNotificationChannel.getConversationId();
+ if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+ mConversationId = mSbn.getNotification().getShortcutId();
+ }
+ // TODO: flag this when flag exists
+ if (TextUtils.isEmpty(mConversationId)) {
+ mConversationId = mSbn.getId() + mSbn.getTag() + PLACEHOLDER_CONVERSATION_ID;
+ }
+ // TODO: consider querying this earlier in the notification pipeline and passing it in
+ LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
+ .setPackage(mPackageName)
+ .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
+ .setShortcutIds(Arrays.asList(mConversationId));
+ List<ShortcutInfo> shortcuts = mLauncherApps.getShortcuts(query, mSbn.getUser());
+ if (shortcuts != null && !shortcuts.isEmpty()) {
+ mShortcutInfo = shortcuts.get(0);
+ }
+
+ mIsBubbleable = mEntry.getBubbleMetadata() != null;
+ mStartedAsBubble = mEntry.isBubble();
+
+ createConversationChannelIfNeeded();
+
+ bindHeader();
+ bindActions();
+
+ }
+
+ void createConversationChannelIfNeeded() {
+ // If this channel is not already a customized conversation channel, create
+ // a custom channel
+ if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+ try {
+ // TODO: associate this key with this channel service side so the customization
+ // isn't forgotten on the next update
+ mINotificationManager.createConversationNotificationChannelForPackage(
+ mPackageName, mAppUid, mNotificationChannel, mConversationId);
+ mNotificationChannel = mINotificationManager.getConversationNotificationChannel(
+ mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName,
+ mNotificationChannel.getId(), false, mConversationId);
+
+ // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a
+ // time
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not create conversation channel", e);
+ }
+ }
+ }
+
+ private void bindActions() {
+ // TODO: figure out what should happen for non-configurable channels
+
+ Button bubble = findViewById(R.id.bubble);
+ bubble.setVisibility(mIsBubbleable ? VISIBLE : GONE);
+ bubble.setOnClickListener(mOnBubbleClick);
+ if (mStartedAsBubble) {
+ bubble.setText(R.string.notification_conversation_unbubble);
+ } else {
+ bubble.setText(R.string.notification_conversation_bubble);
+ }
+
+ Button home = findViewById(R.id.home);
+ home.setOnClickListener(mOnHomeClick);
+ home.setVisibility(mShowHomeScreen && mShortcutInfo != null
+ && mShortcutManager.isRequestPinShortcutSupported()
+ ? VISIBLE : GONE);
+
+ Button favorite = findViewById(R.id.fave);
+ favorite.setOnClickListener(mOnFavoriteClick);
+ if (mNotificationChannel.canBypassDnd()) {
+ favorite.setText(R.string.notification_conversation_unfavorite);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_star), null, null, null);
+ } else {
+ favorite.setText(R.string.notification_conversation_favorite);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_star_border), null, null, null);
+ }
+
+ Button snooze = findViewById(R.id.snooze);
+ snooze.setOnClickListener(mOnSnoozeClick);
+
+ Button mute = findViewById(R.id.mute);
+ mute.setOnClickListener(mOnMuteClick);
+ if (mStartingChannelImportance >= IMPORTANCE_DEFAULT
+ || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) {
+ mute.setText(R.string.notification_conversation_mute);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null);
+ } else {
+ mute.setText(R.string.notification_conversation_unmute);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null);
+ }
+
+ }
+
+ private void bindHeader() {
+ bindConversationDetails();
+
+ // Delegate
+ bindDelegate();
+
+ // Set up app settings link (i.e. Customize)
+ View settingsLinkView = findViewById(R.id.app_settings);
+ Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
+ mNotificationChannel,
+ mSbn.getId(), mSbn.getTag());
+ if (settingsIntent != null
+ && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
+ settingsLinkView.setVisibility(VISIBLE);
+ settingsLinkView.setOnClickListener((View view) -> {
+ mAppSettingsClickListener.onClick(view, settingsIntent);
+ });
+ } else {
+ settingsLinkView.setVisibility(View.GONE);
+ }
+
+ // System Settings button.
+ final View settingsButton = findViewById(R.id.info);
+ settingsButton.setOnClickListener(getSettingsOnClickListener());
+ settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
+ }
+
+ private OnClickListener getSettingsOnClickListener() {
+ if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
+ final int appUidF = mAppUid;
+ return ((View view) -> {
+ mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF);
+ });
+ }
+ return null;
+ }
+
+ private void bindConversationDetails() {
+ final TextView channelName = findViewById(R.id.parent_channel_name);
+ channelName.setText(mNotificationChannel.getName());
+
+ bindGroup();
+ bindName();
+ bindPackage();
+ bindIcon();
+
+ }
+
+ private void bindIcon() {
+ ImageView image = findViewById(R.id.conversation_icon);
+ if (mShortcutInfo != null) {
+ image.setImageDrawable(mLauncherApps.getShortcutBadgedIconDrawable(mShortcutInfo,
+ mContext.getResources().getDisplayMetrics().densityDpi));
+ } else {
+ // TODO: flag this behavior
+ if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
+ // TODO: maybe use a generic group icon, or a composite of recent senders
+ image.setImageDrawable(mPm.getDefaultActivityIcon());
+ } else {
+ final List<Notification.MessagingStyle.Message> messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ (Parcelable[]) mSbn.getNotification().extras.get(
+ Notification.EXTRA_MESSAGES));
+
+ final Notification.MessagingStyle.Message latestMessage =
+ Notification.MessagingStyle.findLatestIncomingMessage(messages);
+ Icon personIcon = latestMessage.getSenderPerson().getIcon();
+ if (personIcon != null) {
+ image.setImageIcon(latestMessage.getSenderPerson().getIcon());
+ } else {
+ // TODO: choose something better
+ image.setImageDrawable(mPm.getDefaultActivityIcon());
+ }
+ }
+ }
+ }
+
+ private void bindName() {
+ TextView name = findViewById(R.id.name);
+ if (mShortcutInfo != null) {
+ name.setText(mShortcutInfo.getShortLabel());
+ } else {
+ // TODO: flag this behavior
+ Bundle extras = mSbn.getNotification().extras;
+ String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
+ if (TextUtils.isEmpty(nameString)) {
+ nameString = extras.getString(Notification.EXTRA_TITLE);
+ }
+ name.setText(nameString);
+ }
+ }
+
+ private void bindPackage() {
+ ApplicationInfo info;
+ try {
+ info = mPm.getApplicationInfo(
+ mPackageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (info != null) {
+ mAppName = String.valueOf(mPm.getApplicationLabel(info));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
+ }
+
+ private void bindDelegate() {
+ TextView delegateView = findViewById(R.id.delegate_name);
+ TextView dividerView = findViewById(R.id.pkg_divider);
+
+ if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
+ // this notification was posted by a delegate!
+ delegateView.setVisibility(View.VISIBLE);
+ dividerView.setVisibility(View.VISIBLE);
+ } else {
+ delegateView.setVisibility(View.GONE);
+ dividerView.setVisibility(View.GONE);
+ }
+ }
+
+ private void bindGroup() {
+ // Set group information if this channel has an associated group.
+ CharSequence groupName = null;
+ if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) {
+ try {
+ final NotificationChannelGroup notificationChannelGroup =
+ mINotificationManager.getNotificationChannelGroupForPackage(
+ mNotificationChannel.getGroup(), mPackageName, mAppUid);
+ if (notificationChannelGroup != null) {
+ groupName = notificationChannelGroup.getName();
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ TextView groupNameView = findViewById(R.id.group_name);
+ View groupDivider = findViewById(R.id.group_divider);
+ if (groupName != null) {
+ groupNameView.setText(groupName);
+ groupNameView.setVisibility(VISIBLE);
+ groupDivider.setVisibility(VISIBLE);
+ } else {
+ groupNameView.setVisibility(GONE);
+ groupDivider.setVisibility(GONE);
+ }
+ }
+
+ @Override
+ public boolean post(Runnable action) {
+ if (mSkipPost) {
+ action.run();
+ return true;
+ } else {
+ return super.post(action);
+ }
+ }
+
+ @Override
+ public void onFinishedClosing() {
+ // TODO: do we need to do anything here?
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (mGutsContainer != null &&
+ event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ if (mGutsContainer.isExposed()) {
+ event.getText().add(mContext.getString(
+ R.string.notification_channel_controls_opened_accessibility, mAppName));
+ } else {
+ event.getText().add(mContext.getString(
+ R.string.notification_channel_controls_closed_accessibility, mAppName));
+ }
+ }
+ }
+
+ private Intent getAppSettingsIntent(PackageManager pm, String packageName,
+ NotificationChannel channel, int id, String tag) {
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
+ .setPackage(packageName);
+ final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
+ intent,
+ PackageManager.MATCH_DEFAULT_ONLY
+ );
+ if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
+ return null;
+ }
+ final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
+ intent.setClassName(activityInfo.packageName, activityInfo.name);
+ if (channel != null) {
+ intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
+ }
+ intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
+ intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
+ return intent;
+ }
+
+ /**
+ * Closes the controls and commits the updated importance values (indirectly).
+ *
+ * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
+ * user does not have the ability to undo the action anymore.
+ */
+ @VisibleForTesting
+ void closeControls(View v, boolean save) {
+ int[] parentLoc = new int[2];
+ int[] targetLoc = new int[2];
+ mGutsContainer.getLocationOnScreen(parentLoc);
+ v.getLocationOnScreen(targetLoc);
+ final int centerX = v.getWidth() / 2;
+ final int centerY = v.getHeight() / 2;
+ final int x = targetLoc[0] - parentLoc[0] + centerX;
+ final int y = targetLoc[1] - parentLoc[1] + centerY;
+ mGutsContainer.closeControls(x, y, save, false /* force */);
+ }
+
+ @Override
+ public void setGutsParent(NotificationGuts guts) {
+ mGutsContainer = guts;
+ }
+
+ @Override
+ public boolean willBeRemoved() {
+ return false;
+ }
+
+ @Override
+ public boolean shouldBeSaved() {
+ return mSelectedAction > -1;
+ }
+
+ @Override
+ public View getContentView() {
+ return this;
+ }
+
+ @Override
+ public boolean handleCloseControls(boolean save, boolean force) {
+ if (save && mSelectedAction > -1) {
+ Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+ bgHandler.post(
+ new UpdateChannelRunnable(mINotificationManager, mPackageName,
+ mAppUid, mSelectedAction, mNotificationChannel));
+ mVisualStabilityManager.temporarilyAllowReordering();
+ }
+ return false;
+ }
+
+ @Override
+ public int getActualHeight() {
+ return getHeight();
+ }
+
+ @VisibleForTesting
+ public boolean isAnimating() {
+ return false;
+ }
+
+ static class UpdateChannelRunnable implements Runnable {
+
+ @Retention(SOURCE)
+ @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE,
+ ACTION_DEMOTE})
+ private @interface Action {}
+ static final int ACTION_BUBBLE = 0;
+ static final int ACTION_HOME = 1;
+ static final int ACTION_FAVORITE = 2;
+ static final int ACTION_SNOOZE = 3;
+ static final int ACTION_MUTE = 4;
+ static final int ACTION_DEMOTE = 5;
+
+ private final INotificationManager mINotificationManager;
+ private final String mAppPkg;
+ private final int mAppUid;
+ private NotificationChannel mChannelToUpdate;
+ private final @Action int mAction;
+
+ public UpdateChannelRunnable(INotificationManager notificationManager,
+ String packageName, int appUid, @Action int action,
+ @NonNull NotificationChannel channelToUpdate) {
+ mINotificationManager = notificationManager;
+ mAppPkg = packageName;
+ mAppUid = appUid;
+ mChannelToUpdate = channelToUpdate;
+ mAction = action;
+ }
+
+ @Override
+ public void run() {
+ try {
+ switch (mAction) {
+ case ACTION_BUBBLE:
+ mChannelToUpdate.setAllowBubbles(!mChannelToUpdate.canBubble());
+ break;
+ case ACTION_FAVORITE:
+ // TODO: extend beyond DND
+ mChannelToUpdate.setBypassDnd(!mChannelToUpdate.canBypassDnd());
+ break;
+ case ACTION_MUTE:
+ if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED
+ || mChannelToUpdate.getImportance() >= IMPORTANCE_DEFAULT) {
+ mChannelToUpdate.setImportance(IMPORTANCE_LOW);
+ } else {
+ mChannelToUpdate.setImportance(Math.max(
+ mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
+ }
+ break;
+ case ACTION_DEMOTE:
+ // TODO: when demotion status field exists on notificationchannel
+ break;
+
+ }
+
+ if (mAction != ACTION_HOME && mAction != ACTION_SNOOZE) {
+ mINotificationManager.updateNotificationChannelForPackage(
+ mAppPkg, mAppUid, mChannelToUpdate);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to update notification channel", e);
+ }
+ }
+ }
+
+ @Retention(SOURCE)
+ @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE})
+ private @interface AlertingBehavior {}
+ private static final int BEHAVIOR_ALERTING = 0;
+ private static final int BEHAVIOR_SILENT = 1;
+ private static final int BEHAVIOR_BUBBLE = 2;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 779a224..6789c81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -23,7 +23,9 @@
import android.app.NotificationChannel;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.pm.ShortcutManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -228,6 +230,9 @@
initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
} else if (gutsView instanceof NotificationInfo) {
initializeNotificationInfo(row, (NotificationInfo) gutsView);
+ } else if (gutsView instanceof NotificationConversationInfo) {
+ initializeConversationNotificationInfo(
+ row, (NotificationConversationInfo) gutsView);
}
return true;
} catch (Exception e) {
@@ -339,6 +344,66 @@
}
/**
+ * Sets up the {@link NotificationConversationInfo} inside the notification row's guts.
+ * @param row view to set up the guts for
+ * @param notificationInfoView view to set up/bind within {@code row}
+ */
+ @VisibleForTesting
+ void initializeConversationNotificationInfo(
+ final ExpandableNotificationRow row,
+ NotificationConversationInfo notificationInfoView) throws Exception {
+ NotificationGuts guts = row.getGuts();
+ StatusBarNotification sbn = row.getEntry().getSbn();
+ String packageName = sbn.getPackageName();
+ // Settings link is only valid for notifications that specify a non-system user
+ NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null;
+ UserHandle userHandle = sbn.getUser();
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(
+ mContext, userHandle.getIdentifier());
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
+ INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ final NotificationConversationInfo.OnAppSettingsClickListener onAppSettingsClick =
+ (View v, Intent intent) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
+ guts.resetFalsingCheck();
+ mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(),
+ row);
+ };
+
+ final NotificationConversationInfo.OnSnoozeClickListener onSnoozeClickListener =
+ (View v, int hours) -> {
+ mListContainer.getSwipeActionHelper().snooze(sbn, hours);
+ };
+
+ if (!userHandle.equals(UserHandle.ALL)
+ || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+ onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ mOnSettingsClickListener.onSettingsClick(sbn.getKey());
+ startAppNotificationSettingsActivity(packageName, appUid, channel, row);
+ };
+ }
+
+ notificationInfoView.bindNotification(
+ shortcutManager,
+ launcherApps,
+ pmUser,
+ iNotificationManager,
+ mVisualStabilityManager,
+ packageName,
+ row.getEntry().getChannel(),
+ row.getEntry(),
+ onSettingsClick,
+ onAppSettingsClick,
+ onSnoozeClickListener,
+ mDeviceProvisionedController.isDeviceProvisioned());
+
+ }
+
+ /**
* Closes guts or notification menus that might be visible and saves any changes.
*
* @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index edfd1b4..212cba6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -83,7 +83,6 @@
private OnMenuEventListener mMenuListener;
private boolean mDismissRtl;
private boolean mIsForeground;
- private final boolean mIsUsingBidirectionalSwipe;
private ValueAnimator mFadeAnimator;
private boolean mAnimating;
@@ -116,19 +115,11 @@
private boolean mIsUserTouching;
public NotificationMenuRow(Context context) {
- //TODO: (b/131242807) not using bidirectional swipe for now
- this(context, false);
- }
-
- // Only needed for testing until we want to turn bidirectional swipe back on
- @VisibleForTesting
- NotificationMenuRow(Context context, boolean isUsingBidirectionalSwipe) {
mContext = context;
mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);
mHandler = new Handler(Looper.getMainLooper());
mLeftMenuItems = new ArrayList<>();
mRightMenuItems = new ArrayList<>();
- mIsUsingBidirectionalSwipe = isUsingBidirectionalSwipe;
}
@Override
@@ -269,24 +260,18 @@
mSnoozeItem = createSnoozeItem(mContext);
}
mAppOpsItem = createAppOpsItem(mContext);
- if (mIsUsingBidirectionalSwipe) {
- mInfoItem = createInfoItem(mContext,
- mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_SILENT);
+ if (mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_PEOPLE) {
+ mInfoItem = createConversationItem(mContext);
} else {
mInfoItem = createInfoItem(mContext);
}
- if (!mIsUsingBidirectionalSwipe) {
- if (!isForeground && showSnooze) {
- mRightMenuItems.add(mSnoozeItem);
- }
- mRightMenuItems.add(mInfoItem);
- mRightMenuItems.add(mAppOpsItem);
- mLeftMenuItems.addAll(mRightMenuItems);
- } else {
- ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems;
- menuItems.add(mInfoItem);
+ if (!isForeground && showSnooze) {
+ mRightMenuItems.add(mSnoozeItem);
}
+ mRightMenuItems.add(mInfoItem);
+ mRightMenuItems.add(mAppOpsItem);
+ mLeftMenuItems.addAll(mRightMenuItems);
populateMenuViews();
if (resetState) {
@@ -633,12 +618,12 @@
@Override
public boolean shouldShowGutsOnSnapOpen() {
- return mIsUsingBidirectionalSwipe;
+ return false;
}
@Override
public MenuItem menuItemToExposeOnSnap() {
- return mIsUsingBidirectionalSwipe ? mInfoItem : null;
+ return null;
}
@Override
@@ -664,6 +649,16 @@
return snooze;
}
+ static NotificationMenuItem createConversationItem(Context context) {
+ Resources res = context.getResources();
+ String infoDescription = res.getString(R.string.notification_menu_gear_description);
+ NotificationConversationInfo infoContent =
+ (NotificationConversationInfo) LayoutInflater.from(context).inflate(
+ R.layout.notification_conversation_info, null, false);
+ return new NotificationMenuItem(context, infoDescription, infoContent,
+ R.drawable.ic_settings);
+ }
+
static NotificationMenuItem createInfoItem(Context context) {
Resources res = context.getResources();
String infoDescription = res.getString(R.string.notification_menu_gear_description);
@@ -673,17 +668,6 @@
R.drawable.ic_settings);
}
- static NotificationMenuItem createInfoItem(Context context, boolean isCurrentlySilent) {
- Resources res = context.getResources();
- String infoDescription = res.getString(R.string.notification_menu_gear_description);
- NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
- R.layout.notification_info, null, false);
- int iconResId = isCurrentlySilent
- ? R.drawable.ic_notifications_silence
- : R.drawable.ic_notifications_alert;
- return new NotificationMenuItem(context, infoDescription, infoContent, iconResId);
- }
-
static MenuItem createAppOpsItem(Context context) {
AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(
R.layout.app_ops_info, null, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
new file mode 100644
index 0000000..df8653c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import javax.inject.Singleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+/**
+ * Dagger Module containing notification row and view inflation implementations.
+ */
+@Module
+public abstract class NotificationRowModule {
+ /**
+ * Provides notification row content binder instance.
+ */
+ @Binds
+ @Singleton
+ public abstract NotificationRowContentBinder provideNotificationRowContentBinder(
+ NotificationContentInflater contentBinderImpl);
+
+ /**
+ * Provides notification remote view cache instance.
+ */
+ @Binds
+ @Singleton
+ public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
+ NotifRemoteViewCacheImpl cacheImpl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 823dd5a..dc2d99c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -6207,6 +6207,11 @@
}
@Override
+ public void onSnooze(StatusBarNotification sbn, int hours) {
+ mStatusBar.setNotificationSnoozed(sbn, hours);
+ }
+
+ @Override
public boolean shouldDismissQuickly() {
return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 4845ea1..6c0655e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -282,6 +282,11 @@
mCallback.onSnooze(sbn, snoozeOption);
}
+ @Override
+ public void snooze(StatusBarNotification sbn, int hours) {
+ mCallback.onSnooze(sbn, hours);
+ }
+
@VisibleForTesting
protected void handleMenuCoveredOrDismissed() {
View exposedMenuView = getExposedMenuView();
@@ -447,6 +452,8 @@
void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
+ void onSnooze(StatusBarNotification sbn, int hours);
+
void onDismiss();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index f25f910..9840a7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -97,6 +97,8 @@
// The edge width where touch down is allowed
private int mEdgeWidth;
+ // The bottom gesture area height
+ private int mBottomGestureHeight;
// The slop to distinguish between horizontal and vertical motion
private final float mTouchSlop;
// Duration after which we consider the event as longpress.
@@ -174,6 +176,8 @@
public void updateCurrentUserResources(Resources res) {
mEdgeWidth = res.getDimensionPixelSize(
com.android.internal.R.dimen.config_backGestureInset);
+ mBottomGestureHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_gesture_height);
}
/**
@@ -316,6 +320,11 @@
return false;
}
+ // Disallow if we are in the bottom gesture area
+ if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
+ return false;
+ }
+
// Always allow if the user is in a transient sticky immersive state
if (mIsNavBarShownTransiently) {
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index a6842ba..3e3ef0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -27,6 +27,7 @@
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE;
import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -64,6 +65,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.telecom.TelecomManager;
import android.text.TextUtils;
@@ -157,7 +159,6 @@
private int mNavigationIconHints = 0;
private @TransitionMode int mNavigationBarMode;
private AccessibilityManager mAccessibilityManager;
- private MagnificationContentObserver mMagnificationObserver;
private ContentResolver mContentResolver;
private boolean mAssistantAvailable;
@@ -176,6 +177,8 @@
private Locale mLocale;
private int mLayoutDirection;
+ private boolean mForceNavBarHandleOpaque;
+
/** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */
private @Appearance int mAppearance;
@@ -228,14 +231,17 @@
@Override
public void onNavBarButtonAlphaChanged(float alpha, boolean animate) {
ButtonDispatcher buttonDispatcher = null;
+ boolean forceVisible = false;
if (QuickStepContract.isSwipeUpMode(mNavBarMode)) {
buttonDispatcher = mNavigationBarView.getBackButton();
} else if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+ forceVisible = mForceNavBarHandleOpaque;
buttonDispatcher = mNavigationBarView.getHomeHandle();
}
if (buttonDispatcher != null) {
- buttonDispatcher.setVisibility(alpha > 0 ? View.VISIBLE : View.INVISIBLE);
- buttonDispatcher.setAlpha(alpha, animate);
+ buttonDispatcher.setVisibility(
+ (forceVisible || alpha > 0) ? View.VISIBLE : View.INVISIBLE);
+ buttonDispatcher.setAlpha(forceVisible ? 1f : alpha, animate);
}
}
};
@@ -292,6 +298,21 @@
mDivider = divider;
mRecentsOptional = recentsOptional;
mHandler = mainHandler;
+
+ mForceNavBarHandleOpaque = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ NAV_BAR_HANDLE_FORCE_OPAQUE,
+ /* defaultValue = */ false);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post,
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) {
+ mForceNavBarHandleOpaque = properties.getBoolean(
+ NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ false);
+ }
+ }
+ });
}
// ----- Fragment Lifecycle Callbacks -----
@@ -303,11 +324,6 @@
mWindowManager = getContext().getSystemService(WindowManager.class);
mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
mContentResolver = getContext().getContentResolver();
- mMagnificationObserver = new MagnificationContentObserver(
- getContext().getMainThreadHandler());
- mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
- mMagnificationObserver, UserHandle.USER_ALL);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
@@ -329,7 +345,6 @@
super.onDestroy();
mNavigationModeController.removeListener(this);
mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
- mContentResolver.unregisterContentObserver(mMagnificationObserver);
mContentResolver.unregisterContentObserver(mAssistContentObserver);
}
@@ -969,28 +984,18 @@
* @param outFeedbackEnabled if non-null, sets it to true if accessibility feedback is enabled.
*/
public int getA11yButtonState(@Nullable boolean[] outFeedbackEnabled) {
- int requestingServices = 0;
- try {
- if (Settings.Secure.getIntForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
- UserHandle.USER_CURRENT) == 1) {
- requestingServices++;
- }
- } catch (Settings.SettingNotFoundException e) {
- }
-
boolean feedbackEnabled = false;
// AccessibilityManagerService resolves services for the current user since the local
// AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
final List<AccessibilityServiceInfo> services =
mAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ final List<String> a11yButtonTargets =
+ mAccessibilityManager.getAccessibilityShortcutTargets(
+ AccessibilityManager.ACCESSIBILITY_BUTTON);
+ final int requestingServices = a11yButtonTargets.size();
for (int i = services.size() - 1; i >= 0; --i) {
AccessibilityServiceInfo info = services.get(i);
- if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
- requestingServices++;
- }
-
if (info.feedbackType != 0 && info.feedbackType !=
AccessibilityServiceInfo.FEEDBACK_GENERIC) {
feedbackEnabled = true;
@@ -1114,18 +1119,6 @@
private final AccessibilityServicesStateChangeListener mAccessibilityListener =
this::updateAccessibilityServicesState;
- private class MagnificationContentObserver extends ContentObserver {
-
- public MagnificationContentObserver(Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager);
- }
- }
-
private final Consumer<Integer> mRotationWatcher = rotation -> {
if (mNavigationBarView != null
&& mNavigationBarView.needsReorient(rotation)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index fe0739f..896b6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -32,7 +32,6 @@
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.AsyncInflationTask;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
@@ -428,7 +427,7 @@
* The notification is still pending inflation but we've decided that we no longer need
* the content view (e.g. suppression might have changed and we decided we need to transfer
* back). However, there is no way to abort just this inflation if other inflation requests
- * have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead
+ * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead
* we just flag it as aborted and free when it's inflated.
*/
boolean mAbortOnInflation;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index e11fc1b..8c947ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -116,7 +116,13 @@
*/
private void onEntryRemovedInternal(NotificationEntry removed,
final StatusBarNotification sbn) {
- String groupKey = getGroupKey(sbn);
+ onEntryRemovedInternal(removed, sbn.getGroupKey(), sbn.isGroup(),
+ sbn.getNotification().isGroupSummary());
+ }
+
+ private void onEntryRemovedInternal(NotificationEntry removed, String notifGroupKey, boolean
+ isGroup, boolean isGroupSummary) {
+ String groupKey = getGroupKey(removed.getKey(), notifGroupKey);
final NotificationGroup group = mGroupMap.get(groupKey);
if (group == null) {
// When an app posts 2 different notifications as summary of the same group, then a
@@ -125,7 +131,7 @@
// the close future. See b/23676310 for reference.
return;
}
- if (isGroupChild(sbn)) {
+ if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) {
group.children.remove(removed.getKey());
} else {
group.summary = null;
@@ -229,7 +235,7 @@
private int getNumberOfIsolatedChildren(String groupKey) {
int count = 0;
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
- if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+ if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
count++;
}
}
@@ -238,31 +244,47 @@
private NotificationEntry getIsolatedChild(String groupKey) {
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
- if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+ if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
return mGroupMap.get(sbn.getKey()).summary;
}
}
return null;
}
- public void onEntryUpdated(NotificationEntry entry,
- StatusBarNotification oldNotification) {
- String oldKey = oldNotification.getGroupKey();
- String newKey = entry.getSbn().getGroupKey();
- boolean groupKeysChanged = !oldKey.equals(newKey);
- boolean wasGroupChild = isGroupChild(oldNotification);
+ /**
+ * Update an entry's group information
+ * @param entry notification entry to update
+ * @param oldNotification previous notification info before this update
+ */
+ public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) {
+ onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(),
+ oldNotification.getNotification().isGroupSummary());
+ }
+
+ /**
+ * Updates an entry's group information
+ * @param entry notification entry to update
+ * @param oldGroupKey the notification's previous group key before this update
+ * @param oldIsGroup whether this notification was a group before this update
+ * @param oldIsGroupSummary whether this notification was a group summary before this update
+ */
+ public void onEntryUpdated(NotificationEntry entry, String oldGroupKey, boolean oldIsGroup,
+ boolean oldIsGroupSummary) {
+ String newGroupKey = entry.getSbn().getGroupKey();
+ boolean groupKeysChanged = !oldGroupKey.equals(newGroupKey);
+ boolean wasGroupChild = isGroupChild(entry.getKey(), oldIsGroup, oldIsGroupSummary);
boolean isGroupChild = isGroupChild(entry.getSbn());
mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild;
- if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
- onEntryRemovedInternal(entry, oldNotification);
+ if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) {
+ onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary);
}
onEntryAdded(entry);
mIsUpdatingUnchangedGroup = false;
- if (isIsolated(entry.getSbn())) {
+ if (isIsolated(entry.getSbn().getKey())) {
mIsolatedEntries.put(entry.getKey(), entry.getSbn());
if (groupKeysChanged) {
- updateSuppression(mGroupMap.get(oldKey));
- updateSuppression(mGroupMap.get(newKey));
+ updateSuppression(mGroupMap.get(oldGroupKey));
+ updateSuppression(mGroupMap.get(newGroupKey));
}
} else if (!wasGroupChild && isGroupChild) {
onEntryBecomingChild(entry);
@@ -418,10 +440,14 @@
* @return the key of the notification
*/
public String getGroupKey(StatusBarNotification sbn) {
- if (isIsolated(sbn)) {
- return sbn.getKey();
+ return getGroupKey(sbn.getKey(), sbn.getGroupKey());
+ }
+
+ private String getGroupKey(String key, String groupKey) {
+ if (isIsolated(key)) {
+ return key;
}
- return sbn.getGroupKey();
+ return groupKey;
}
/** @return group expansion state after toggling. */
@@ -434,8 +460,8 @@
return group.expanded;
}
- private boolean isIsolated(StatusBarNotification sbn) {
- return mIsolatedEntries.containsKey(sbn.getKey());
+ private boolean isIsolated(String sbnKey) {
+ return mIsolatedEntries.containsKey(sbnKey);
}
/**
@@ -445,7 +471,7 @@
* @return true if it is visually a group summary
*/
public boolean isGroupSummary(StatusBarNotification sbn) {
- if (isIsolated(sbn)) {
+ if (isIsolated(sbn.getKey())) {
return true;
}
return sbn.getNotification().isGroupSummary();
@@ -458,10 +484,14 @@
* @return true if it is visually a group child
*/
public boolean isGroupChild(StatusBarNotification sbn) {
- if (isIsolated(sbn)) {
+ return isGroupChild(sbn.getKey(), sbn.isGroup(), sbn.getNotification().isGroupSummary());
+ }
+
+ private boolean isGroupChild(String key, boolean isGroup, boolean isGroupSummary) {
+ if (isIsolated(key)) {
return false;
}
- return sbn.isGroup() && !sbn.getNotification().isGroupSummary();
+ return isGroup && !isGroupSummary;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ccc86b1..dc9cf77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -29,7 +29,6 @@
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
@@ -200,8 +199,8 @@
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.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -365,8 +364,7 @@
private final HeadsUpManagerPhone mHeadsUpManager;
private final DynamicPrivacyController mDynamicPrivacyController;
private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
- private final boolean mAllowNotificationLongPress;
- private final Lazy<NewNotifPipeline> mNewNotifPipeline;
+ private final Lazy<NotifPipelineInitializer> mNewNotifPipeline;
private final FalsingManager mFalsingManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final ConfigurationController mConfigurationController;
@@ -388,6 +386,7 @@
private final KeyguardDismissUtil mKeyguardDismissUtil;
private final ExtensionController mExtensionController;
private final UserInfoControllerImpl mUserInfoControllerImpl;
+ private final NotificationRowBinderImpl mNotificationRowBinder;
private final DismissCallbackRegistry mDismissCallbackRegistry;
// expanded notifications
@@ -626,8 +625,7 @@
HeadsUpManagerPhone headsUpManagerPhone,
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
- @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- Lazy<NewNotifPipeline> newNotifPipeline,
+ Lazy<NotifPipelineInitializer> newNotifPipeline,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -693,6 +691,7 @@
KeyguardDismissUtil keyguardDismissUtil,
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
+ NotificationRowBinderImpl notificationRowBinder,
DismissCallbackRegistry dismissCallbackRegistry) {
super(context);
mFeatureFlags = featureFlags;
@@ -707,7 +706,6 @@
mHeadsUpManager = headsUpManagerPhone;
mDynamicPrivacyController = dynamicPrivacyController;
mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
- mAllowNotificationLongPress = allowNotificationLongPress;
mNewNotifPipeline = newNotifPipeline;
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
@@ -772,6 +770,7 @@
mKeyguardDismissUtil = keyguardDismissUtil;
mExtensionController = extensionController;
mUserInfoControllerImpl = userInfoControllerImpl;
+ mNotificationRowBinder = notificationRowBinder;
mDismissCallbackRegistry = dismissCallbackRegistry;
mBubbleExpandListener =
@@ -1237,19 +1236,11 @@
mStatusBarWindowViewController, this, mNotificationPanelViewController,
(NotificationListContainer) mStackScroller);
- final NotificationRowBinderImpl rowBinder =
- new NotificationRowBinderImpl(
- mContext,
- mAllowNotificationLongPress,
- mKeyguardBypassController,
- mStatusBarStateController,
- mNotificationLogger);
-
// TODO: inject this.
mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
- mNotificationAlertingManager, rowBinder, mKeyguardStateController,
+ mNotificationAlertingManager, mNotificationRowBinder, mKeyguardStateController,
mKeyguardIndicationController,
this /* statusBar */, mShadeController, mCommandQueue, mInitController);
@@ -1273,20 +1264,19 @@
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
- mEntryManager.setRowBinder(rowBinder);
- rowBinder.setInflationCallback(mEntryManager);
+ mNotificationRowBinder.setInflationCallback(mEntryManager);
}
mRemoteInputUriController.attach(mEntryManager);
- rowBinder.setNotificationClicker(new NotificationClicker(
+ mNotificationRowBinder.setNotificationClicker(new NotificationClicker(
Optional.of(this), mBubbleController, mNotificationActivityStarter));
mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
mNotificationListController.bind();
if (mFeatureFlags.isNewNotifPipelineEnabled()) {
- mNewNotifPipeline.get().initialize(mNotificationListener, rowBinder);
+ mNewNotifPipeline.get().initialize(mNotificationListener, mNotificationRowBinder);
}
mEntryManager.attach(mNotificationListener);
}
@@ -4036,6 +4026,11 @@
}
}
+ public void setNotificationSnoozed(StatusBarNotification sbn, int hoursToSnooze) {
+ mNotificationListener.snoozeNotification(sbn.getKey(),
+ hoursToSnooze * 60 * 60 * 1000);
+ }
+
@Override
public void toggleSplitScreen() {
toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
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 153ca22..b4d5dad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import android.content.Context;
@@ -66,7 +65,8 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
@@ -117,8 +117,7 @@
HeadsUpManagerPhone headsUpManagerPhone,
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
- @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- Lazy<NewNotifPipeline> newNotifPipeline,
+ Lazy<NotifPipelineInitializer> newNotifPipeline,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -184,6 +183,7 @@
KeyguardDismissUtil keyguardDismissUtil,
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
+ NotificationRowBinderImpl notificationRowBinder,
DismissCallbackRegistry dismissCallbackRegistry) {
return new StatusBar(
context,
@@ -199,7 +199,6 @@
headsUpManagerPhone,
dynamicPrivacyController,
bypassHeadsUpNotifier,
- allowNotificationLongPress,
newNotifPipeline,
falsingManager,
broadcastDispatcher,
@@ -265,6 +264,7 @@
keyguardDismissUtil,
extensionController,
userInfoControllerImpl,
+ notificationRowBinder,
dismissCallbackRegistry);
}
}
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 12a6516..720f229 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -66,7 +66,7 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
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 6b842d5..f05b585 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -15,6 +15,9 @@
*/
package com.android.systemui.statusbar.policy;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
@@ -654,7 +657,7 @@
}
boolean isDataDisabled() {
- return !mPhone.isDataCapable();
+ return !mPhone.isDataConnectionEnabled();
}
@VisibleForTesting
@@ -704,7 +707,11 @@
}
mServiceState = state;
if (mServiceState != null) {
- updateDataNetType(mServiceState.getDataNetworkType());
+ NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo(
+ DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+ if (regInfo != null) {
+ updateDataNetType(regInfo.getAccessNetworkTechnology());
+ }
}
updateTelephony();
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
new file mode 100644
index 0000000..e4b7a20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.app.Activity
+import android.os.Bundle
+import android.os.PersistableBundle
+import androidx.lifecycle.LifecycleOwner
+import com.android.settingslib.core.lifecycle.Lifecycle
+
+open class LifecycleActivity : Activity(), LifecycleOwner {
+
+ private val lifecycle = Lifecycle(this)
+
+ override fun getLifecycle() = lifecycle
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ lifecycle.onAttach(this)
+ lifecycle.onCreate(savedInstanceState)
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onCreate(
+ savedInstanceState: Bundle?,
+ persistentState: PersistableBundle?
+ ) {
+ lifecycle.onAttach(this)
+ lifecycle.onCreate(savedInstanceState)
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
+ super.onCreate(savedInstanceState, persistentState)
+ }
+
+ override fun onStart() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_START)
+ super.onStart()
+ }
+
+ override fun onResume() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_RESUME)
+ super.onResume()
+ }
+
+ override fun onPause() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_PAUSE)
+ super.onPause()
+ }
+
+ override fun onStop() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_STOP)
+ super.onStop()
+ }
+
+ override fun onDestroy() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_DESTROY)
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index cca76bd..8a1759d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -27,6 +27,9 @@
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
import java.util.WeakHashMap
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
/**
* Extension function for all objects which will return a PhysicsAnimator instance for that object.
@@ -35,6 +38,15 @@
private const val TAG = "PhysicsAnimator"
+private val UNSET = -Float.MAX_VALUE
+
+/**
+ * [FlingAnimation] multiplies the friction set via [FlingAnimation.setFriction] by 4.2f, which is
+ * where this number comes from. We use it in [PhysicsAnimator.flingThenSpring] to calculate the
+ * minimum velocity for a fling to reach a certain value, given the fling's friction.
+ */
+private const val FLING_FRICTION_SCALAR_MULTIPLIER = 4.2f
+
typealias EndAction = () -> Unit
/** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
@@ -236,6 +248,71 @@
}
/**
+ * Flings a property using the given start velocity. If the fling animation reaches the min/max
+ * bounds (from the [flingConfig]) with velocity remaining, it'll overshoot it and spring back.
+ *
+ * If the object is already out of the fling bounds, it will immediately spring back within
+ * bounds.
+ *
+ * This is useful for animating objects that are bounded by constraints such as screen edges,
+ * since otherwise the fling animation would end abruptly upon reaching the min/max bounds.
+ *
+ * @param property The property to animate.
+ * @param startVelocity The velocity, in pixels/second, with which to start the fling. If the
+ * object is already outside the fling bounds, this velocity will be used as the start velocity
+ * of the spring that will spring it back within bounds.
+ * @param flingMustReachMinOrMax If true, the fling animation is guaranteed to reach either its
+ * minimum bound (if [startVelocity] is negative) or maximum bound (if it's positive). The
+ * animator will use startVelocity if it's sufficient, or add more velocity if necessary. This
+ * is useful when fling's deceleration-based physics are preferable to the acceleration-based
+ * forces used by springs - typically, when you're allowing the user to move an object somewhere
+ * on the screen, but it needs to be along an edge.
+ * @param flingConfig The configuration to use for the fling portion of the animation.
+ * @param springConfig The configuration to use for the spring portion of the animation.
+ */
+ @JvmOverloads
+ fun flingThenSpring(
+ property: FloatPropertyCompat<in T>,
+ startVelocity: Float,
+ flingConfig: FlingConfig,
+ springConfig: SpringConfig,
+ flingMustReachMinOrMax: Boolean = false
+ ): PhysicsAnimator<T> {
+ val flingConfigCopy = flingConfig.copy()
+ val springConfigCopy = springConfig.copy()
+ val toAtLeast = if (startVelocity < 0) flingConfig.min else flingConfig.max
+
+ // If the fling needs to reach min/max, calculate the velocity required to do so and use
+ // that if the provided start velocity is not sufficient.
+ if (flingMustReachMinOrMax &&
+ toAtLeast != -Float.MAX_VALUE && toAtLeast != Float.MAX_VALUE) {
+ val distanceToDestination = toAtLeast - property.getValue(target)
+
+ // The minimum velocity required for the fling to end up at the given destination,
+ // taking the provided fling friction value.
+ val velocityToReachDestination = distanceToDestination *
+ (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
+
+ // Try to use the provided start velocity, but use the required velocity to reach the
+ // destination if the provided velocity is insufficient.
+ val sufficientVelocity =
+ if (distanceToDestination < 0)
+ min(velocityToReachDestination, startVelocity)
+ else
+ max(velocityToReachDestination, startVelocity)
+
+ flingConfigCopy.startVelocity = sufficientVelocity
+ springConfigCopy.finalPosition = toAtLeast
+ } else {
+ flingConfigCopy.startVelocity = startVelocity
+ }
+
+ flingConfigs[property] = flingConfigCopy
+ springConfigs[property] = springConfigCopy
+ return this
+ }
+
+ /**
* Adds a listener that will be called whenever any property on the animated object is updated.
* This will be called on every animation frame, with the current value of the animated object
* and the new property values.
@@ -246,7 +323,7 @@
}
/**
- * Adds a listener that will be called whenever a property's animation ends. This is useful if
+ * Adds a listener that will be called when a property stops animating. This is useful if
* you care about a specific property ending, or want to use the end value/end velocity from a
* particular property's animation. If you just want to run an action when all property
* animations have ended, use [withEndActions].
@@ -311,6 +388,114 @@
"your test setup.")
}
+ // Functions that will actually start the animations. These are run after we build and add
+ // the InternalListener, since some animations might update/end immediately and we don't
+ // want to miss those updates.
+ val animationStartActions = ArrayList<() -> Unit>()
+
+ for (animatedProperty in getAnimatedProperties()) {
+ val flingConfig = flingConfigs[animatedProperty]
+ val springConfig = springConfigs[animatedProperty]
+
+ // The property's current value on the object.
+ val currentValue = animatedProperty.getValue(target)
+
+ // Start by checking for a fling configuration. If one is present, we're either flinging
+ // or flinging-then-springing. Either way, we'll want to start the fling first.
+ if (flingConfig != null) {
+ animationStartActions.add {
+ // When the animation is starting, adjust the min/max bounds to include the
+ // current value of the property, if necessary. This is required to allow a
+ // fling to bring an out-of-bounds object back into bounds. For example, if an
+ // object was dragged halfway off the left side of the screen, but then flung to
+ // the right, we don't want the animation to end instantly just because the
+ // object started out of bounds. If the fling is in the direction that would
+ // take it farther out of bounds, it will end instantly as expected.
+ flingConfig.apply {
+ min = min(currentValue, this.min)
+ max = max(currentValue, this.max)
+ }
+
+ // Apply the configuration and start the animation.
+ getFlingAnimation(animatedProperty)
+ .also { flingConfig.applyToAnimation(it) }
+ .start()
+ }
+ }
+
+ // Check for a spring configuration. If one is present, we're either springing, or
+ // flinging-then-springing.
+ if (springConfig != null) {
+
+ // If there is no corresponding fling config, we're only springing.
+ if (flingConfig == null) {
+ // Apply the configuration and start the animation.
+ val springAnim = getSpringAnimation(animatedProperty)
+ springConfig.applyToAnimation(springAnim)
+ animationStartActions.add(springAnim::start)
+ } else {
+ // If there's a corresponding fling config, we're flinging-then-springing. Save
+ // the fling's original bounds so we can spring to them when the fling ends.
+ val flingMin = flingConfig.min
+ val flingMax = flingConfig.max
+
+ // Add an end listener that will start the spring when the fling ends.
+ endListeners.add(0, object : EndListener<T> {
+ override fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ ) {
+ // If this isn't the relevant property, it wasn't a fling, or the fling
+ // was explicitly cancelled, don't spring.
+ if (property != animatedProperty || !wasFling || canceled) {
+ return
+ }
+
+ val endedWithVelocity = abs(finalVelocity) > 0
+
+ // If the object was out of bounds when the fling animation started, it
+ // will immediately end. In that case, we'll spring it back in bounds.
+ val endedOutOfBounds = finalValue !in flingMin..flingMax
+
+ // If the fling ended either out of bounds or with remaining velocity,
+ // it's time to spring.
+ if (endedWithVelocity || endedOutOfBounds) {
+ springConfig.startVelocity = finalVelocity
+
+ // If the spring's final position isn't set, this is a
+ // flingThenSpring where flingMustReachMinOrMax was false. We'll
+ // need to set the spring's final position here.
+ if (springConfig.finalPosition == UNSET) {
+ if (endedWithVelocity) {
+ // If the fling ended with negative velocity, that means it
+ // hit the min bound, so spring to that bound (and vice
+ // versa).
+ springConfig.finalPosition =
+ if (finalVelocity < 0) flingMin else flingMax
+ } else if (endedOutOfBounds) {
+ // If the fling ended out of bounds, spring it to the
+ // nearest bound.
+ springConfig.finalPosition =
+ if (finalValue < flingMin) flingMin else flingMax
+ }
+ }
+
+ // Apply the configuration and start the spring animation.
+ getSpringAnimation(animatedProperty)
+ .also { springConfig.applyToAnimation(it) }
+ .start()
+ }
+ }
+ })
+ }
+ }
+ }
+
// Add an internal listener that will dispatch animation events to the provided listeners.
internalListeners.add(InternalListener(
getAnimatedProperties(),
@@ -318,24 +503,10 @@
ArrayList(endListeners),
ArrayList(endActions)))
- for ((property, config) in flingConfigs) {
- val currentValue = property.getValue(target)
-
- // If the fling is already out of bounds, don't start it.
- if (currentValue <= config.min || currentValue >= config.max) {
- continue
- }
-
- val flingAnim = getFlingAnimation(property)
- config.applyToAnimation(flingAnim)
- flingAnim.start()
- }
-
- for ((property, config) in springConfigs) {
- val springAnim = getSpringAnimation(property)
- config.applyToAnimation(springAnim)
- springAnim.start()
- }
+ // Actually start the DynamicAnimations. This is delayed until after the InternalListener is
+ // constructed and added so that we don't miss the end listener firing for any animations
+ // that immediately end.
+ animationStartActions.forEach { it.invoke() }
clearAnimator()
}
@@ -381,7 +552,10 @@
}
anim.addEndListener { _, canceled, value, velocity ->
internalListeners.removeAll {
- it.onInternalAnimationEnd(property, canceled, value, velocity) } }
+ it.onInternalAnimationEnd(
+ property, canceled, value, velocity, anim is FlingAnimation)
+ }
+ }
return anim
}
@@ -434,7 +608,8 @@
property: FloatPropertyCompat<in T>,
canceled: Boolean,
finalValue: Float,
- finalVelocity: Float
+ finalVelocity: Float,
+ isFling: Boolean
): Boolean {
// If this property animation isn't relevant to this listener, ignore it.
@@ -461,7 +636,15 @@
val allEnded = !arePropertiesAnimating(properties)
endListeners.forEach {
- it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) }
+ it.onAnimationEnd(
+ target, property, isFling, canceled, finalValue, finalVelocity,
+ allEnded)
+
+ // Check that the end listener didn't restart this property's animation.
+ if (isPropertyAnimating(property)) {
+ return false
+ }
+ }
// If all of the animations that this listener cares about have ended, run the end
// actions unless the animation was canceled.
@@ -495,7 +678,8 @@
/** Returns whether the given property is animating. */
fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
- return springAnimations[property]?.isRunning ?: false
+ return springAnimations[property]?.isRunning ?: false ||
+ flingAnimations[property]?.isRunning ?: false
}
/** Returns whether any of the given properties are animating. */
@@ -523,15 +707,15 @@
data class SpringConfig internal constructor(
internal var stiffness: Float,
internal var dampingRatio: Float,
- internal var startVel: Float = 0f,
- internal var finalPosition: Float = -Float.MAX_VALUE
+ internal var startVelocity: Float = 0f,
+ internal var finalPosition: Float = UNSET
) {
constructor() :
this(defaultSpring.stiffness, defaultSpring.dampingRatio)
constructor(stiffness: Float, dampingRatio: Float) :
- this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f)
+ this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
/** Apply these configuration settings to the given SpringAnimation. */
internal fun applyToAnimation(anim: SpringAnimation) {
@@ -542,7 +726,7 @@
finalPosition = this@SpringConfig.finalPosition
}
- if (startVel != 0f) anim.setStartVelocity(startVel)
+ if (startVelocity != 0f) anim.setStartVelocity(startVelocity)
}
}
@@ -555,7 +739,7 @@
internal var friction: Float,
internal var min: Float,
internal var max: Float,
- internal var startVel: Float
+ internal var startVelocity: Float
) {
constructor() : this(defaultFling.friction)
@@ -564,7 +748,7 @@
this(friction, defaultFling.min, defaultFling.max)
constructor(friction: Float, min: Float, max: Float) :
- this(friction, min, max, startVel = 0f)
+ this(friction, min, max, startVelocity = 0f)
/** Apply these configuration settings to the given FlingAnimation. */
internal fun applyToAnimation(anim: FlingAnimation) {
@@ -572,7 +756,7 @@
friction = this@FlingConfig.friction
setMinValue(min)
setMaxValue(max)
- setStartVelocity(startVel)
+ setStartVelocity(startVelocity)
}
}
}
@@ -625,6 +809,10 @@
*
* @param target The animated object itself.
* @param property The property whose animation has just ended.
+ * @param wasFling Whether this property ended after a fling animation (as opposed to a
+ * spring animation). If this property was animated via [flingThenSpring], this will be true
+ * if the fling animation did not reach the min/max bounds, decelerating to a stop
+ * naturally. It will be false if it hit the bounds and was sprung back.
* @param canceled Whether the animation was explicitly canceled before it naturally ended.
* @param finalValue The final value of the animated property.
* @param finalVelocity The final velocity (in pixels per second) of the ended animation.
@@ -662,6 +850,7 @@
fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
index e86970c..965decd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -19,6 +19,7 @@
import android.os.Looper
import android.util.ArrayMap
import androidx.dynamicanimation.animation.FloatPropertyCompat
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.prepareForTest
import java.util.ArrayDeque
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -119,6 +120,7 @@
override fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
@@ -389,8 +391,6 @@
val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
animationThreadHandler.post {
- val animatedProperties = animator.getAnimatedProperties()
-
// Add an update listener that dispatches to any test update listeners added by
// tests.
animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
@@ -398,6 +398,10 @@
target: T,
values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
) {
+ values.forEach { (property, value) ->
+ allUpdates.getOrPut(property, { ArrayList() }).add(value)
+ }
+
for (listener in testUpdateListeners) {
listener.onAnimationUpdateForProperty(target, values)
}
@@ -410,6 +414,7 @@
override fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
@@ -417,7 +422,7 @@
) {
for (listener in testEndListeners) {
listener.onAnimationEnd(
- target, property, canceled, finalValue, finalVelocity,
+ target, property, wasFling, canceled, finalValue, finalVelocity,
allRelevantPropertyAnimsEnded)
}
@@ -432,31 +437,6 @@
}
})
- val updateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>().also {
- it.add(object : PhysicsAnimator.UpdateListener<T> {
- override fun onAnimationUpdateForProperty(
- target: T,
- values: ArrayMap<FloatPropertyCompat<in T>,
- PhysicsAnimator.AnimationUpdate>
- ) {
- values.forEach { (property, value) ->
- allUpdates.getOrPut(property, { ArrayList() }).add(value)
- }
- }
- })
- }
-
- /**
- * Add an internal listener at the head of the list that captures update values
- * directly from DynamicAnimation. We use this to build a list of all updates so we
- * can verify that InternalListener dispatches to the real listeners properly.
- */
- animator.internalListeners.add(0, animator.InternalListener(
- animatedProperties,
- updateListeners,
- ArrayList(),
- ArrayList()))
-
animator.startInternal()
unblockLatch.countDown()
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index 7cdba86..cc6d607 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -83,17 +83,19 @@
* Provide a Background-Thread Executor by default.
*/
@Provides
+ @Singleton
public static Executor provideExecutor(@Background Looper looper) {
- return new ExecutorImpl(new Handler(looper));
+ return new ExecutorImpl(looper);
}
/**
* Provide a Background-Thread Executor.
*/
@Provides
+ @Singleton
@Background
public static Executor provideBackgroundExecutor(@Background Looper looper) {
- return new ExecutorImpl(new Handler(looper));
+ return new ExecutorImpl(looper);
}
/**
@@ -109,26 +111,29 @@
* Provide a Background-Thread Executor by default.
*/
@Provides
+ @Singleton
public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) {
- return new ExecutorImpl(new Handler(looper));
+ return new ExecutorImpl(looper);
}
/**
* Provide a Background-Thread Executor.
*/
@Provides
+ @Singleton
@Background
public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) {
- return new ExecutorImpl(new Handler(looper));
+ return new ExecutorImpl(looper);
}
/**
* Provide a Main-Thread Executor.
*/
@Provides
+ @Singleton
@Main
public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
- return new ExecutorImpl(new Handler(looper));
+ return new ExecutorImpl(looper);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
index 7e77321..2bbf950 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
@@ -17,37 +17,69 @@
package com.android.systemui.util.concurrency;
import android.os.Handler;
-import android.os.HandlerExecutor;
+import android.os.Looper;
import android.os.Message;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Implementations of {@link DelayableExecutor} for SystemUI.
*/
-public class ExecutorImpl extends HandlerExecutor implements DelayableExecutor {
+public class ExecutorImpl implements DelayableExecutor {
private final Handler mHandler;
- public ExecutorImpl(Handler handler) {
- super(handler);
- mHandler = handler;
+ ExecutorImpl(Looper looper) {
+ mHandler = new Handler(looper, this::onHandleMessage);
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RejectedExecutionException(mHandler + " is shutting down");
+ }
}
@Override
public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) {
- Object token = new Object();
- Message m = mHandler.obtainMessage(0, token);
+ ExecutionToken token = new ExecutionToken(r);
+ Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token);
mHandler.sendMessageDelayed(m, unit.toMillis(delay));
- return () -> mHandler.removeCallbacksAndMessages(token);
+ return token;
}
@Override
public Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit) {
- Object token = new Object();
- Message m = mHandler.obtainMessage(0, token);
+ ExecutionToken token = new ExecutionToken(r);
+ Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token);
mHandler.sendMessageAtTime(m, unit.toMillis(uptimeMillis));
- return () -> mHandler.removeCallbacksAndMessages(token);
+ return token;
}
+
+ private boolean onHandleMessage(Message msg) {
+ if (msg.what == MSG_EXECUTE_RUNNABLE) {
+ ExecutionToken token = (ExecutionToken) msg.obj;
+ token.runnable.run();
+ } else {
+ throw new IllegalStateException("Unrecognized message: " + msg.what);
+ }
+ return true;
+ }
+
+ private class ExecutionToken implements Runnable {
+ public final Runnable runnable;
+
+ private ExecutionToken(Runnable runnable) {
+ this.runnable = runnable;
+ }
+
+ @Override
+ public void run() {
+ mHandler.removeCallbacksAndMessages(this);
+ }
+ }
+
+ private static final int MSG_EXECUTE_RUNNABLE = 0;
}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index bfb0e15..c51624b 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -35,7 +35,6 @@
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
<uses-permission android:name="android.permission.CONTROL_VPN" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 9c9a627..8d11b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -48,7 +48,7 @@
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -71,7 +71,7 @@
@Mock private NotificationEntryManager mEntryManager;
@Mock private AppOpsController mAppOpsController;
@Mock private Handler mMainHandler;
- @Mock private NotifCollection mNotifCollection;
+ @Mock private NotifPipeline mNotifPipeline;
@Before
public void setUp() throws Exception {
@@ -81,7 +81,7 @@
MockitoAnnotations.initMocks(this);
mFsc = new ForegroundServiceController(mEntryManager, mAppOpsController, mMainHandler);
mListener = new ForegroundServiceNotificationListener(
- mContext, mFsc, mEntryManager, mNotifCollection);
+ mContext, mFsc, mEntryManager, mNotifPipeline);
ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
ArgumentCaptor.forClass(NotificationEntryListener.class);
verify(mEntryManager).addNotificationEntryListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
index 0c53b03..2ecc8ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -52,6 +52,7 @@
@Override
protected <T> T createDependency(Object key) {
if (mObjs.containsKey(key)) return (T) mObjs.get(key);
+
mInstantiatedObjects.add(key);
return super.createDependency(key);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index af0ef82..e5ae1aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -25,11 +25,11 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+
+import static java.lang.Thread.sleep;
import android.app.AppOpsManager;
import android.content.pm.PackageManager;
@@ -57,7 +57,6 @@
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = UserHandle.getUid(0, 0);
private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0);
- private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0);
@Mock
private AppOpsManager mAppOpsManager;
@@ -68,8 +67,6 @@
@Mock
private AppOpsControllerImpl.H mMockHandler;
@Mock
- private PermissionFlagsCache mFlagsCache;
- @Mock
private DumpController mDumpController;
private AppOpsControllerImpl mController;
@@ -85,17 +82,9 @@
// All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of
// TEST_UID_NON_USER_SENSITIVE are user sensitive.
getContext().setMockPackageManager(mPackageManager);
- when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID)))).thenReturn(
- PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
- when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID_OTHER)))).thenReturn(
- PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
- when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID_NON_USER_SENSITIVE)))).thenReturn(0);
- mController = new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mFlagsCache,
- mDumpController);
+ mController =
+ new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mDumpController);
}
@Test
@@ -191,14 +180,6 @@
}
@Test
- public void nonUserSensitiveOpsAreIgnored() {
- mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
- TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true);
- assertEquals(0, mController.getActiveAppOpsForUser(
- UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
- }
-
- @Test
public void opNotedScheduledForRemoval() {
mController.setBGHandler(mMockHandler);
mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
@@ -251,4 +232,100 @@
// Only one post to notify subscribers
verify(mMockHandler, times(2)).scheduleRemoval(any(), anyLong());
}
+
+ @Test
+ public void testActiveOpNotRemovedAfterNoted() throws InterruptedException {
+ // Replaces the timeout delay with 5 ms
+ AppOpsControllerImpl.H testHandler = mController.new H(mTestableLooper.getLooper()) {
+ @Override
+ public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
+ super.scheduleRemoval(item, 5L);
+ }
+ };
+
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+ mController.setBGHandler(testHandler);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mTestableLooper.processAllMessages();
+ List<AppOpItem> list = mController.getActiveAppOps();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ // Duplicates are not removed between active and noted
+ assertEquals(2, list.size());
+
+ sleep(10L);
+
+ mTestableLooper.processAllMessages();
+
+ verify(mCallback, never()).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+ list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ }
+
+ @Test
+ public void testNotedNotRemovedAfterActive() {
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mTestableLooper.processAllMessages();
+ List<AppOpItem> list = mController.getActiveAppOps();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ // Duplicates are not removed between active and noted
+ assertEquals(2, list.size());
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+
+ mTestableLooper.processAllMessages();
+
+ verify(mCallback, never()).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+ list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ }
+
+ @Test
+ public void testNotedAndActiveOnlyOneCall() {
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mTestableLooper.processAllMessages();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+ }
+
+ @Test
+ public void testActiveAndNotedOnlyOneCall() {
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mTestableLooper.processAllMessages();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
deleted file mode 100644
index dc070de..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.appops
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class PermissionFlagsCacheTest : SysuiTestCase() {
-
- companion object {
- const val TEST_PERMISSION = "test_permission"
- const val TEST_PACKAGE = "test_package"
- }
-
- @Mock
- private lateinit var mPackageManager: PackageManager
- @Mock
- private lateinit var mUserHandle: UserHandle
- private lateinit var flagsCache: TestPermissionFlagsCache
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- mContext.setMockPackageManager(mPackageManager)
- flagsCache = TestPermissionFlagsCache(mContext)
- }
-
- @Test
- fun testCallsPackageManager_exactlyOnce() {
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- flagsCache.time = CACHE_EXPIRATION - 1
- verify(mPackageManager).getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- }
-
- @Test
- fun testCallsPackageManager_cacheExpired() {
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- flagsCache.time = CACHE_EXPIRATION + 1
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- verify(mPackageManager, times(2))
- .getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- }
-
- @Test
- fun testCallsPackageMaanger_multipleKeys() {
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- flagsCache.getPermissionFlags(TEST_PERMISSION, "", mUserHandle)
- verify(mPackageManager, times(2))
- .getPermissionFlags(anyString(), anyString(), any())
- }
-
- private class TestPermissionFlagsCache(context: Context) : PermissionFlagsCache(context) {
- var time = 0L
-
- override fun getCurrentTime(): Long {
- return time
- }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
new file mode 100644
index 0000000..7c8c7c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.Binder
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import dagger.Lazy
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsBindingControllerTest : SysuiTestCase() {
+
+ companion object {
+ fun <T> any(): T = Mockito.any<T>()
+ private val TEST_COMPONENT_NAME_1 = ComponentName("TEST_PKG", "TEST_CLS_1")
+ private val TEST_COMPONENT_NAME_2 = ComponentName("TEST_PKG", "TEST_CLS_2")
+ private val TEST_COMPONENT_NAME_3 = ComponentName("TEST_PKG", "TEST_CLS_3")
+ }
+
+ @Mock
+ private lateinit var mockControlsController: ControlsController
+
+ private val executor = FakeExecutor(FakeSystemClock())
+ private lateinit var controller: ControlsBindingController
+ private val providers = TestableControlsBindingControllerImpl.providers
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ controller = TestableControlsBindingControllerImpl(
+ mContext, executor, Lazy { mockControlsController })
+ }
+
+ @After
+ fun tearDown() {
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ providers.clear()
+ }
+
+ @Test
+ fun testBindAndLoad() {
+ val callback: (List<Control>) -> Unit = {}
+ controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
+
+ assertEquals(1, providers.size)
+ val provider = providers.first()
+ verify(provider).maybeBindAndLoad(callback)
+ }
+
+ @Test
+ fun testBindServices() {
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+ executor.runAllReady()
+
+ assertEquals(2, providers.size)
+ assertEquals(setOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2),
+ providers.map { it.componentName }.toSet())
+ providers.forEach {
+ verify(it).bindPermanently()
+ }
+ }
+
+ @Test
+ fun testSubscribe() {
+ val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_3))
+
+ controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+ executor.runAllReady()
+
+ assertEquals(3, providers.size)
+ val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 }
+ val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 }
+ val provider3 = providers.first { it.componentName == TEST_COMPONENT_NAME_3 }
+
+ verify(provider1).maybeBindAndSubscribe(listOf(controlInfo1.controlId))
+ verify(provider2).maybeBindAndSubscribe(listOf(controlInfo2.controlId))
+ verify(provider3, never()).maybeBindAndSubscribe(any())
+ verify(provider3).unbindService() // Not needed services will be unbound
+ }
+
+ @Test
+ fun testUnsubscribe_notRefreshing() {
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+ controller.unsubscribe()
+
+ executor.runAllReady()
+
+ providers.forEach {
+ verify(it, never()).unsubscribe()
+ }
+ }
+
+ @Test
+ fun testUnsubscribe_refreshing() {
+ val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+
+ controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+ controller.unsubscribe()
+
+ executor.runAllReady()
+
+ providers.forEach {
+ verify(it).unsubscribe()
+ }
+ }
+}
+
+class TestableControlsBindingControllerImpl(
+ context: Context,
+ executor: DelayableExecutor,
+ lazyController: Lazy<ControlsController>
+) : ControlsBindingControllerImpl(context, executor, lazyController) {
+
+ companion object {
+ val providers = mutableSetOf<ControlsProviderLifecycleManager>()
+ }
+
+ override fun createProviderManager(component: ComponentName):
+ ControlsProviderLifecycleManager {
+ val provider = mock(ControlsProviderLifecycleManager::class.java)
+ val token = Binder()
+ `when`(provider.componentName).thenReturn(component)
+ `when`(provider.token).thenReturn(token)
+ providers.add(provider)
+ return provider
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
new file mode 100644
index 0000000..a19c299
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.provider.Settings
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.DumpController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Optional
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsControllerImplTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var uiController: ControlsUiController
+ @Mock
+ private lateinit var bindingController: ControlsBindingController
+ @Mock
+ private lateinit var dumpController: DumpController
+ @Mock
+ private lateinit var pendingIntent: PendingIntent
+ @Mock
+ private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper
+
+ @Captor
+ private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>>
+ @Captor
+ private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit>
+
+ private lateinit var delayableExecutor: FakeExecutor
+ private lateinit var controller: ControlsController
+
+ companion object {
+ fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+ fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+
+ private val TEST_COMPONENT = ComponentName("test.pkg", "test.class")
+ private const val TEST_CONTROL_ID = "control1"
+ private const val TEST_CONTROL_TITLE = "Test"
+ private const val TEST_DEVICE_TYPE = DeviceTypes.TYPE_AC_HEATER
+ private val TEST_CONTROL_INFO = ControlInfo(
+ TEST_COMPONENT, TEST_CONTROL_ID, TEST_CONTROL_TITLE, TEST_DEVICE_TYPE)
+
+ private val TEST_COMPONENT_2 = ComponentName("test.pkg", "test.class.2")
+ private const val TEST_CONTROL_ID_2 = "control2"
+ private const val TEST_CONTROL_TITLE_2 = "Test 2"
+ private const val TEST_DEVICE_TYPE_2 = DeviceTypes.TYPE_CAMERA
+ private val TEST_CONTROL_INFO_2 = ControlInfo(
+ TEST_COMPONENT_2, TEST_CONTROL_ID_2, TEST_CONTROL_TITLE_2, TEST_DEVICE_TYPE_2)
+ }
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ Settings.Secure.putInt(mContext.contentResolver,
+ ControlsControllerImpl.CONTROLS_AVAILABLE, 1)
+
+ delayableExecutor = FakeExecutor(FakeSystemClock())
+
+ controller = ControlsControllerImpl(
+ mContext,
+ delayableExecutor,
+ uiController,
+ bindingController,
+ Optional.of(persistenceWrapper),
+ dumpController
+ )
+ assertTrue(controller.available)
+ }
+
+ private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder {
+ return Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
+ .setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle)
+ }
+
+ @Test
+ fun testStartWithoutFavorites() {
+ assertTrue(controller.getFavoriteControls().isEmpty())
+ }
+
+ @Test
+ fun testStartWithSavedFavorites() {
+ `when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_CONTROL_INFO))
+ val controller_other = ControlsControllerImpl(
+ mContext,
+ delayableExecutor,
+ uiController,
+ bindingController,
+ Optional.of(persistenceWrapper),
+ dumpController
+ )
+ assertEquals(listOf(TEST_CONTROL_INFO), controller_other.getFavoriteControls())
+ }
+
+ @Test
+ fun testAddFavorite() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO in favorites)
+ assertEquals(1, favorites.size)
+ }
+
+ @Test
+ fun testAddMultipleFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO in favorites)
+ assertTrue(TEST_CONTROL_INFO_2 in favorites)
+ assertEquals(2, favorites.size)
+ }
+
+ @Test
+ fun testAddAndRemoveFavorite() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO !in favorites)
+ assertTrue(TEST_CONTROL_INFO_2 in favorites)
+ assertEquals(1, favorites.size)
+ }
+
+ @Test
+ fun testFavoritesSavedOnAdd() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ verify(persistenceWrapper).storeFavorites(listOf(TEST_CONTROL_INFO))
+ }
+
+ @Test
+ fun testFavoritesSavedOnRemove() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ reset(persistenceWrapper)
+
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+ verify(persistenceWrapper).storeFavorites(emptyList())
+ }
+
+ @Test
+ fun testFavoritesSavedOnChange() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {}
+
+ reset(persistenceWrapper)
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ verify(persistenceWrapper).storeFavorites(listOf(newControlInfo))
+ }
+
+ @Test
+ fun testFavoritesNotSavedOnRedundantAdd() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ reset(persistenceWrapper)
+
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+ }
+
+ @Test
+ fun testFavoritesNotSavedOnNotRemove() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+ verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+ }
+
+ @Test
+ fun testOnActionResponse() {
+ controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK)
+
+ verify(uiController).onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID,
+ ControlAction.RESPONSE_OK)
+ }
+
+ @Test
+ fun testRefreshStatus() {
+ val list = listOf(Control.StatefulBuilder(TEST_CONTROL_ID, pendingIntent).build())
+ controller.refreshStatus(TEST_COMPONENT, list)
+
+ verify(uiController).onRefreshState(TEST_COMPONENT, list)
+ }
+
+ @Test
+ fun testUnsubscribe() {
+ controller.unsubscribe()
+ verify(bindingController).unsubscribe()
+ }
+
+ @Test
+ fun testSubscribeFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+ controller.subscribeToFavorites()
+
+ verify(bindingController).subscribe(capture(controlInfoListCaptor))
+
+ assertTrue(TEST_CONTROL_INFO in controlInfoListCaptor.value)
+ assertTrue(TEST_CONTROL_INFO_2 in controlInfoListCaptor.value)
+ }
+
+ @Test
+ fun testLoadForComponent_noFavorites() {
+ var loaded = false
+ val control = builderFromInfo(TEST_CONTROL_INFO).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(1, it.size)
+ val controlStatus = it[0]
+ assertEquals(ControlStatus(control, false), controlStatus)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testLoadForComponent_favorites() {
+ var loaded = false
+ val control = builderFromInfo(TEST_CONTROL_INFO).build()
+ val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build()
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(2, it.size)
+ val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID }
+ assertEquals(ControlStatus(control, true), controlStatus)
+
+ val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 }
+ assertEquals(ControlStatus(control2, false), controlStatus2)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control, control2))
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testLoadForComponent_removed() {
+ var loaded = false
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(1, it.size)
+ val controlStatus = it[0]
+ assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
+ assertTrue(controlStatus.favorite)
+ assertTrue(controlStatus.removed)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(emptyList())
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testFavoriteInformationModifiedOnLoad() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {}
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ val favorites = controller.getFavoriteControls()
+ assertEquals(1, favorites.size)
+ assertEquals(newControlInfo, favorites[0])
+ }
+
+ @Test
+ fun testFavoriteInformationModifiedOnRefresh() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.refreshStatus(TEST_COMPONENT, listOf(control))
+
+ delayableExecutor.runAllReady()
+
+ val favorites = controller.getFavoriteControls()
+ assertEquals(1, favorites.size)
+ assertEquals(newControlInfo, favorites[0])
+ }
+
+ @Test
+ fun testClearFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ assertEquals(1, controller.getFavoriteControls().size)
+
+ controller.clearFavorites()
+ assertTrue(controller.getFavoriteControls().isEmpty())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
new file mode 100644
index 0000000..c145c1f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsFavoritePersistenceWrapperTest : SysuiTestCase() {
+
+ private lateinit var file: File
+
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var wrapper: ControlsFavoritePersistenceWrapper
+
+ @Before
+ fun setUp() {
+ file = File.createTempFile("controls_favorites", ".temp")
+ wrapper = ControlsFavoritePersistenceWrapper(file, executor)
+ }
+
+ @After
+ fun tearDown() {
+ if (file.exists() ?: false) {
+ file.delete()
+ }
+ }
+
+ @Test
+ fun testSaveAndRestore() {
+ val controlInfo1 = ControlInfo(
+ ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_1")!!,
+ "id1", "name_1", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(
+ ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_2")!!,
+ "id2", "name_2", DeviceTypes.TYPE_GENERIC_ON_OFF)
+ val list = listOf(controlInfo1, controlInfo2)
+
+ wrapper.storeFavorites(list)
+
+ executor.runAllReady()
+
+ assertEquals(list, wrapper.readFavorites())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
new file mode 100644
index 0000000..556bb40
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.IControlsProvider
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var serviceCallback: IControlsProviderCallback.Stub
+ @Mock
+ private lateinit var service: IControlsProvider.Stub
+
+ private val componentName = ComponentName("test.pkg", "test.cls")
+ private lateinit var manager: ControlsProviderLifecycleManager
+ private lateinit var executor: DelayableExecutor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mContext.addMockService(componentName, service)
+ executor = FakeExecutor(FakeSystemClock())
+ `when`(service.asBinder()).thenCallRealMethod()
+ `when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service)
+
+ manager = ControlsProviderLifecycleManager(
+ context,
+ executor,
+ serviceCallback,
+ componentName
+ )
+ }
+
+ @After
+ fun tearDown() {
+ manager.unbindService()
+ }
+
+ @Test
+ fun testBindService() {
+ manager.bindPermanently()
+ assertTrue(mContext.isBound(componentName))
+ }
+
+ @Test
+ fun testUnbindService() {
+ manager.bindPermanently()
+ manager.unbindService()
+ assertFalse(mContext.isBound(componentName))
+ }
+
+ @Test
+ fun testMaybeBindAndLoad() {
+ val callback: (List<Control>) -> Unit = {}
+ manager.maybeBindAndLoad(callback)
+
+ verify(service).load()
+
+ assertTrue(mContext.isBound(componentName))
+ assertEquals(callback, manager.lastLoadCallback)
+ }
+
+ @Test
+ fun testMaybeUnbind_bindingAndCallback() {
+ manager.maybeBindAndLoad {}
+
+ manager.maybeUnbindAndRemoveCallback()
+ assertFalse(mContext.isBound(componentName))
+ assertNull(manager.lastLoadCallback)
+ }
+
+ @Test
+ fun testUnsubscribe() {
+ manager.bindPermanently()
+ manager.unsubscribe()
+
+ verify(service).unsubscribe()
+ }
+
+ @Test
+ fun testMaybeBindAndSubscribe() {
+ val list = listOf("TEST_ID")
+ manager.maybeBindAndSubscribe(list)
+
+ assertTrue(mContext.isBound(componentName))
+ verify(service).subscribe(list)
+ }
+
+ @Test
+ fun testMaybeBindAndAction() {
+ val controlId = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+ manager.maybeBindAndSendAction(controlId, action)
+
+ assertTrue(mContext.isBound(componentName))
+ verify(service).onAction(controlId, action)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
new file mode 100644
index 0000000..d6993c0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.os.RemoteException
+import android.service.controls.IControlsProvider
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderServiceWrapperTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var service: IControlsProvider
+
+ private val exception = RemoteException()
+
+ private lateinit var wrapper: ControlsProviderServiceWrapper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ wrapper = ControlsProviderServiceWrapper(service)
+ }
+
+ @Test
+ fun testLoad_happyPath() {
+ val result = wrapper.load()
+
+ assertTrue(result)
+ verify(service).load()
+ }
+
+ @Test
+ fun testLoad_error() {
+ `when`(service.load()).thenThrow(exception)
+ val result = wrapper.load()
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testSubscribe_happyPath() {
+ val list = listOf("TEST_ID")
+ val result = wrapper.subscribe(list)
+
+ assertTrue(result)
+ verify(service).subscribe(list)
+ }
+
+ @Test
+ fun testSubscribe_error() {
+ `when`(service.subscribe(any())).thenThrow(exception)
+
+ val list = listOf("TEST_ID")
+ val result = wrapper.subscribe(list)
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testUnsubscribe_happyPath() {
+ val result = wrapper.unsubscribe()
+
+ assertTrue(result)
+ verify(service).unsubscribe()
+ }
+
+ @Test
+ fun testUnsubscribe_error() {
+ `when`(service.unsubscribe()).thenThrow(exception)
+ val result = wrapper.unsubscribe()
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testOnAction_happyPath() {
+ val id = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+
+ val result = wrapper.onAction(id, action)
+
+ assertTrue(result)
+ verify(service).onAction(id, action)
+ }
+
+ @Test
+ fun testOnAction_error() {
+ `when`(service.onAction(any(), any())).thenThrow(exception)
+
+ val id = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+
+ val result = wrapper.onAction(id, action)
+
+ assertFalse(result)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
new file mode 100644
index 0000000..f09aab9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.pm.ServiceInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.settingslib.applications.ServiceListing
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsListingControllerImplTest : SysuiTestCase() {
+
+ companion object {
+ private const val TEST_LABEL = "TEST_LABEL"
+ private const val TEST_PERMISSION = "permission"
+ fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+ fun <T> any(): T = Mockito.any<T>()
+ }
+
+ @Mock
+ private lateinit var mockSL: ServiceListing
+ @Mock
+ private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
+ @Mock
+ private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
+ @Mock
+ private lateinit var serviceInfo: ServiceInfo
+ @Mock
+ private lateinit var componentName: ComponentName
+
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var controller: ControlsListingControllerImpl
+
+ private var serviceListingCallbackCaptor =
+ ArgumentCaptor.forClass(ServiceListing.Callback::class.java)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(serviceInfo.componentName).thenReturn(componentName)
+
+ controller = ControlsListingControllerImpl(mContext, executor, mockSL)
+ verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
+ }
+
+ @After
+ fun tearDown() {
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ }
+
+ @Test
+ fun testNoServices_notListening() {
+ assertTrue(controller.getCurrentServices().isEmpty())
+ }
+
+ @Test
+ fun testStartListening_onFirstCallback() {
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+
+ verify(mockSL).setListening(true)
+ }
+
+ @Test
+ fun testStartListening_onlyOnce() {
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ executor.runAllReady()
+
+ verify(mockSL).setListening(true)
+ }
+
+ @Test
+ fun testStopListening_callbackRemoved() {
+ controller.addCallback(mockCallback)
+
+ executor.runAllReady()
+
+ controller.removeCallback(mockCallback)
+
+ executor.runAllReady()
+
+ verify(mockSL).setListening(false)
+ }
+
+ @Test
+ fun testStopListening_notWhileRemainingCallbacks() {
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ executor.runAllReady()
+
+ controller.removeCallback(mockCallback)
+
+ executor.runAllReady()
+
+ verify(mockSL, never()).setListening(false)
+ }
+
+ @Test
+ fun testReloadOnFirstCallbackAdded() {
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+
+ verify(mockSL).reload()
+ }
+
+ @Test
+ fun testCallbackCalledWhenAdded() {
+ `when`(mockSL.reload()).then {
+ serviceListingCallbackCaptor.value.onServicesReloaded(emptyList())
+ }
+
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+ verify(mockCallback).onServicesUpdated(any())
+ reset(mockCallback)
+
+ controller.addCallback(mockCallbackOther)
+ executor.runAllReady()
+ verify(mockCallbackOther).onServicesUpdated(any())
+ verify(mockCallback, never()).onServicesUpdated(any())
+ }
+
+ @Test
+ fun testCallbackGetsList() {
+ val list = listOf(serviceInfo)
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ @Suppress("unchecked_cast")
+ val captor: ArgumentCaptor<List<CandidateInfo>> =
+ ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<CandidateInfo>>
+
+ executor.runAllReady()
+ reset(mockCallback)
+ reset(mockCallbackOther)
+
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+ verify(mockCallback).onServicesUpdated(capture(captor))
+ assertEquals(1, captor.value.size)
+ assertEquals(componentName.flattenToString(), captor.value[0].key)
+
+ verify(mockCallbackOther).onServicesUpdated(capture(captor))
+ assertEquals(1, captor.value.size)
+ assertEquals(componentName.flattenToString(), captor.value[0].key)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 77659df..3fdbd3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -47,6 +47,9 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
+import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
@@ -72,6 +75,7 @@
public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser());
private static final String GROUP_KEY = "gruKey";
+ private static final String APP_NAME = "appName";
private final Context mContext;
private int mId;
@@ -303,9 +307,6 @@
null /* root */,
false /* attachToRoot */);
ExpandableNotificationRow row = mRow;
- row.setGroupManager(mGroupManager);
- row.setHeadsUpManager(mHeadsUpManager);
- row.setAboveShelfChangedListener(aboveShelf -> {});
final NotificationChannel channel =
new NotificationChannel(
@@ -329,6 +330,23 @@
entry.setRow(row);
entry.createIcons(mContext, entry.getSbn());
row.setEntry(entry);
+
+ NotificationContentInflater contentBinder = new NotificationContentInflater(
+ mock(NotifRemoteViewCache.class),
+ mock(NotificationRemoteInputManager.class));
+ contentBinder.setInflateSynchronously(true);
+
+ row.initialize(
+ APP_NAME,
+ entry.getKey(),
+ mock(ExpansionLogger.class),
+ mock(KeyguardBypassController.class),
+ mGroupManager,
+ mHeadsUpManager,
+ contentBinder,
+ mock(OnExpandClickListener.class));
+ row.setAboveShelfChangedListener(aboveShelf -> { });
+
row.setInflationFlags(extraInflationFlags);
inflateAndWait(row);
@@ -341,7 +359,6 @@
private static void inflateAndWait(ExpandableNotificationRow row) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
- row.getNotificationInflater().setInflateSynchronously(true);
NotificationContentInflater.InflationCallback callback =
new NotificationContentInflater.InflationCallback() {
@Override
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 cd33cf9..cc5514f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -58,16 +58,13 @@
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
-import com.android.systemui.ForegroundServiceController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
-import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -81,22 +78,24 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
+import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.leak.LeakDetector;
import org.junit.Before;
import org.junit.Test;
@@ -130,22 +129,16 @@
@Mock private HeadsUpManager mHeadsUpManager;
@Mock private RankingMap mRankingMap;
@Mock private RemoteInputController mRemoteInputController;
-
- // Dependency mocks:
- @Mock private ForegroundServiceController mForegroundServiceController;
+ @Mock private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotificationGroupManager mGroupManager;
@Mock private NotificationGutsManager mGutsManager;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
- @Mock private NotificationListener mNotificationListener;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
- @Mock private VisualStabilityManager mVisualStabilityManager;
- @Mock private MetricsLogger mMetricsLogger;
- @Mock private SmartReplyController mSmartReplyController;
@Mock private RowInflaterTask mAsyncInflationTask;
- @Mock private NotificationRowBinder mMockedRowBinder;
@Mock private NotifLog mNotifLog;
@Mock private FeatureFlags mFeatureFlags;
+ @Mock private LeakDetector mLeakDetector;
private int mId;
private NotificationEntry mEntry;
@@ -192,21 +185,9 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(ShadeController.class);
- mDependency.injectTestDependency(ForegroundServiceController.class,
- mForegroundServiceController);
- mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
- mLockscreenUserManager);
- mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
- mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
- mDependency.injectTestDependency(NotificationRemoteInputManager.class, mRemoteInputManager);
- mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
- mDependency.injectTestDependency(DeviceProvisionedController.class,
- mDeviceProvisionedController);
- mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
- mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
- mDependency.injectTestDependency(SmartReplyController.class, mSmartReplyController);
- mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
+ if (!mDependency.hasInstantiatedDependency(SmartReplyController.class)) {
+ mDependency.injectMockDependency(SmartReplyController.class);
+ }
mDependency.injectMockDependency(NotificationMediaManager.class);
mCountDownLatch = new CountDownLatch(1);
@@ -222,6 +203,23 @@
mEntry.expandedIcon = mock(StatusBarIconView.class);
+ NotificationRowContentBinder contentBinder = new NotificationContentInflater(
+ mock(NotifRemoteViewCache.class),
+ mRemoteInputManager);
+
+ NotificationRowBinderImpl notificationRowBinder =
+ new NotificationRowBinderImpl(mContext,
+ mRemoteInputManager,
+ mLockscreenUserManager,
+ contentBinder,
+ true, /* allowLongPress */
+ mock(KeyguardBypassController.class),
+ mock(StatusBarStateController.class),
+ mGroupManager,
+ mGutsManager,
+ mNotificationInterruptionStateProvider,
+ mock(NotificationLogger.class));
+
when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
mEntryManager = new TestableNotificationEntryManager(
@@ -237,22 +235,19 @@
mock(PeopleNotificationIdentifier.class),
mock(HighPriorityProvider.class)),
mEnvironment,
- mFeatureFlags
+ mFeatureFlags,
+ () -> notificationRowBinder,
+ () -> mRemoteInputManager,
+ mLeakDetector
);
mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager);
mEntryManager.addNotificationEntryListener(mEntryListener);
mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
- NotificationRowBinderImpl notificationRowBinder =
- new NotificationRowBinderImpl(mContext, true, /* allowLongPress */
- mock(KeyguardBypassController.class),
- mock(StatusBarStateController.class),
- mock(NotificationLogger.class));
notificationRowBinder.setUpWithPresenter(
mPresenter, mListContainer, mHeadsUpManager, mBindCallback);
notificationRowBinder.setInflationCallback(mEntryManager);
notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class));
- mEntryManager.setRowBinder(notificationRowBinder);
setUserSentiment(
mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
@@ -372,9 +367,6 @@
@Test
public void testRemoveNotification_whilePending() {
-
- mEntryManager.setRowBinder(mMockedRowBinder);
-
mEntryManager.addNotification(mSbn, mRankingMap);
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
@@ -447,7 +439,6 @@
@Test
public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() {
// GIVEN an entry manager with a notification
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN a lifetime extender that always tries to extend lifetime
@@ -471,7 +462,6 @@
Assert.sMainLooper = TestableLooper.get(this).getLooper();
// GIVEN an entry manager with a notification whose life has been extended
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender();
mEntryManager.addNotificationLifetimeExtender(extender);
@@ -490,7 +480,6 @@
@Test
public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() {
// GIVEN an entry manager with a notification whose life has been extended
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
@@ -507,7 +496,6 @@
@Test
public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() {
// GIVEN an entry manager with a notification
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN two lifetime extenders, the first which never extends and the second which
@@ -546,7 +534,6 @@
@Test
public void testRemoveInterceptor_interceptsDontGetRemoved() throws InterruptedException {
// GIVEN an entry manager with a notification
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN interceptor that intercepts that entry
@@ -568,7 +555,6 @@
Assert.sMainLooper = TestableLooper.get(this).getLooper();
// GIVEN an entry manager with a notification
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN interceptor that doesn't intercept
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
index 1afee12..29ce920 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
@@ -18,12 +18,15 @@
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder
import com.android.systemui.statusbar.notification.logging.NotifLog
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.NotificationGroupManager
+import com.android.systemui.util.leak.LeakDetector
import java.util.concurrent.CountDownLatch
@@ -35,8 +38,12 @@
gm: NotificationGroupManager,
rm: NotificationRankingManager,
ke: KeyguardEnvironment,
- ff: FeatureFlags
-) : NotificationEntryManager(log, gm, rm, ke, ff) {
+ ff: FeatureFlags,
+ rb: dagger.Lazy<NotificationRowBinder>,
+ notificationRemoteInputManagerLazy: dagger.Lazy<NotificationRemoteInputManager>,
+ leakDetector: LeakDetector
+) : NotificationEntryManager(log, gm, rm, ke, ff, rb,
+ notificationRemoteInputManagerLazy, leakDetector) {
public var countDownLatch: CountDownLatch = CountDownLatch(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 28feaca..fe8d769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -29,6 +29,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -48,13 +49,18 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.DumpController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CoalescedEvent;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.util.Assert;
import org.junit.Before;
@@ -99,7 +105,7 @@
MockitoAnnotations.initMocks(this);
Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mCollection = new NotifCollection(mStatusBarService);
+ mCollection = new NotifCollection(mStatusBarService, mock(DumpController.class));
mCollection.attach(mGroupCoalescer);
mCollection.addCollectionListener(mCollectionListener);
mCollection.setBuildListener(mBuildListener);
@@ -377,7 +383,7 @@
verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
// THEN the entry is not removed
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
// THEN the entry properly records all extenders that returned true
assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders);
@@ -398,7 +404,7 @@
// GIVEN a notification gets lifetime-extended by one of them
mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN the last active extender expires (but new ones become active)
@@ -413,7 +419,7 @@
verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
// THEN the entry is not removed
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
// THEN the entry properly records all extenders that returned true
assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders);
@@ -435,7 +441,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN one (but not all) of the extenders expires
@@ -443,7 +449,7 @@
mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
// THEN the entry is not removed
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
// THEN we don't re-query the extenders
verify(mExtender1, never()).shouldExtendLifetime(eq(entry2), anyInt());
@@ -470,7 +476,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN all of the active extenders expire
@@ -480,7 +486,7 @@
mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2);
// THEN the entry removed
- assertFalse(mCollection.getNotifs().contains(entry2));
+ assertFalse(mCollection.getActiveNotifs().contains(entry2));
verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN, false);
}
@@ -500,7 +506,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN the notification is reposted
@@ -511,7 +517,7 @@
verify(mExtender2).cancelLifetimeExtension(entry2);
// THEN the notification is still present
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
}
@Test(expected = IllegalStateException.class)
@@ -530,7 +536,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension()
@@ -559,7 +565,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN the notification is reposted
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
similarity index 84%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 3e4068b..e915be3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -16,9 +16,10 @@
package com.android.systemui.statusbar.notification.collection;
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
+import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
@@ -27,6 +28,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -37,15 +39,17 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.DumpController;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl.OnRenderListListener;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.util.Assert;
import com.android.systemui.util.time.FakeSystemClock;
@@ -72,9 +76,9 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class NotifListBuilderImplTest extends SysuiTestCase {
+public class ShadeListBuilderTest extends SysuiTestCase {
- private NotifListBuilderImpl mListBuilder;
+ private ShadeListBuilder mListBuilder;
private FakeSystemClock mSystemClock = new FakeSystemClock();
@Mock private NotifLog mNotifLog;
@@ -99,7 +103,7 @@
MockitoAnnotations.initMocks(this);
Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mListBuilder = new NotifListBuilderImpl(mSystemClock, mNotifLog);
+ mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog, mock(DumpController.class));
mListBuilder.setOnRenderListListener(mOnRenderListListener);
mListBuilder.attach(mNotifCollection);
@@ -447,6 +451,29 @@
}
@Test
+ public void testPreRenderNotifsFilteredBreakupGroups() {
+ final String filterTag = "FILTER_ME";
+ // GIVEN a NotifFilter that filters out notifications with a tag
+ NotifFilter filter1 = spy(new NotifFilterWithTag(filterTag));
+ mListBuilder.addPreRenderFilter(filter1);
+
+ // WHEN the pipeline is kicked off on a list of notifs
+ addGroupChildWithTag(0, PACKAGE_2, GROUP_1, filterTag);
+ addGroupChild(1, PACKAGE_2, GROUP_1);
+ addGroupSummary(2, PACKAGE_2, GROUP_1);
+ dispatchBuild();
+
+ // THEN the final list doesn't contain any filtered-out notifs
+ // and groups that are too small are broken up
+ verifyBuiltList(
+ notif(1)
+ );
+
+ // THEN each filtered notif records the filter that did it
+ assertEquals(filter1, mEntrySet.get(0).mExcludingFilter);
+ }
+
+ @Test
public void testNotifFiltersCanBePreempted() {
// GIVEN two notif filters
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
@@ -549,12 +576,17 @@
}
@Test
- public void testNotifsAreSectioned() {
- // GIVEN a filter that removes all PACKAGE_4 notifs and a SectionsProvider that divides
+ public void testNotifSections() {
+ // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide
// notifs based on package name
mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
- final SectionsProvider sectionsProvider = spy(new PackageSectioner());
- mListBuilder.setSectionsProvider(sectionsProvider);
+ final NotifSection pkg1Section = spy(new PackageSection(PACKAGE_1));
+ final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
+ // NOTE: no package 3 section explicitly added, so notifs with package 3 will get set by
+ // ShadeListBuilder's sDefaultSection which will demote it to the last section
+ final NotifSection pkg4Section = spy(new PackageSection(PACKAGE_4));
+ final NotifSection pkg5Section = spy(new PackageSection(PACKAGE_5));
+ mListBuilder.setSections(Arrays.asList(pkg1Section, pkg2Section, pkg4Section, pkg5Section));
// WHEN we build a list with different packages
addNotif(0, PACKAGE_4);
@@ -581,19 +613,93 @@
child(6)
),
notif(8),
- notif(3),
- notif(9)
+ notif(9),
+ notif(3)
);
- // THEN the sections provider is called on all top level elements (but no children and no
- // entries that were filtered out)
- verify(sectionsProvider).getSection(mEntrySet.get(1));
- verify(sectionsProvider).getSection(mEntrySet.get(2));
- verify(sectionsProvider).getSection(mEntrySet.get(3));
- verify(sectionsProvider).getSection(mEntrySet.get(7));
- verify(sectionsProvider).getSection(mEntrySet.get(8));
- verify(sectionsProvider).getSection(mEntrySet.get(9));
- verify(sectionsProvider).getSection(mBuiltList.get(3));
+ // THEN the first section (pkg1Section) is called on all top level elements (but
+ // no children and no entries that were filtered out)
+ verify(pkg1Section).isInSection(mEntrySet.get(1));
+ verify(pkg1Section).isInSection(mEntrySet.get(2));
+ verify(pkg1Section).isInSection(mEntrySet.get(3));
+ verify(pkg1Section).isInSection(mEntrySet.get(7));
+ verify(pkg1Section).isInSection(mEntrySet.get(8));
+ verify(pkg1Section).isInSection(mEntrySet.get(9));
+ verify(pkg1Section).isInSection(mBuiltList.get(3));
+
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(0));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(4));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(5));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(6));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(10));
+
+ // THEN the last section (pkg5Section) is not called on any of the entries that were
+ // filtered or already in a section
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(0));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(1));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(2));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(4));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(5));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(6));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(7));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(8));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(10));
+
+ verify(pkg5Section).isInSection(mEntrySet.get(3));
+ verify(pkg5Section).isInSection(mEntrySet.get(9));
+
+ // THEN the correct section is assigned for entries in pkg1Section
+ assertEquals(pkg1Section, mEntrySet.get(2).mNotifSection);
+ assertEquals(0, mEntrySet.get(2).getSection());
+ assertEquals(pkg1Section, mEntrySet.get(7).mNotifSection);
+ assertEquals(0, mEntrySet.get(7).getSection());
+
+ // THEN the correct section is assigned for entries in pkg2Section
+ assertEquals(pkg2Section, mEntrySet.get(1).mNotifSection);
+ assertEquals(1, mEntrySet.get(1).getSection());
+ assertEquals(pkg2Section, mEntrySet.get(8).mNotifSection);
+ assertEquals(1, mEntrySet.get(8).getSection());
+ assertEquals(pkg2Section, mBuiltList.get(3).mNotifSection);
+ assertEquals(1, mBuiltList.get(3).getSection());
+
+ // THEN no section was assigned to entries in pkg4Section (since they were filtered)
+ assertEquals(null, mEntrySet.get(0).mNotifSection);
+ assertEquals(-1, mEntrySet.get(0).getSection());
+ assertEquals(null, mEntrySet.get(10).mNotifSection);
+ assertEquals(-1, mEntrySet.get(10).getSection());
+
+
+ // THEN the correct section is assigned for entries in pkg5Section
+ assertEquals(pkg5Section, mEntrySet.get(9).mNotifSection);
+ assertEquals(3, mEntrySet.get(9).getSection());
+
+ // THEN the children entries are assigned the same section as its parent
+ assertEquals(mBuiltList.get(3).mNotifSection, child(5).entry.mNotifSection);
+ assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection());
+ assertEquals(mBuiltList.get(3).mNotifSection, child(6).entry.mNotifSection);
+ assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection());
+ }
+
+ @Test
+ public void testNotifUsesDefaultSection() {
+ // GIVEN a Section for Package2
+ final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
+ mListBuilder.setSections(Arrays.asList(pkg2Section));
+
+ // WHEN we build a list with pkg1 and pkg2 packages
+ addNotif(0, PACKAGE_1);
+ addNotif(1, PACKAGE_2);
+ dispatchBuild();
+
+ // THEN the list is sorted according to section
+ verifyBuiltList(
+ notif(1),
+ notif(0)
+ );
+
+ // THEN the entry that didn't have an explicit section gets assigned the DefaultSection
+ assertEquals(1, notif(0).entry.getSection());
+ assertNotNull(notif(0).entry.mNotifSection);
}
@Test
@@ -628,7 +734,7 @@
// GIVEN a bunch of registered listeners and pluggables
NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
NotifPromoter promoter = spy(new IdPromoter(3));
- PackageSectioner sectioner = spy(new PackageSectioner());
+ NotifSection section = spy(new PackageSection(PACKAGE_1));
NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
mListBuilder.addPreGroupFilter(preGroupFilter);
@@ -636,7 +742,7 @@
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
mListBuilder.setComparators(Collections.singletonList(comparator));
- mListBuilder.setSectionsProvider(sectioner);
+ mListBuilder.setSections(Arrays.asList(section));
mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
mListBuilder.addPreRenderFilter(preRenderFilter);
@@ -656,7 +762,7 @@
mOnBeforeTransformGroupsListener,
promoter,
mOnBeforeSortListener,
- sectioner,
+ section,
comparator,
preRenderFilter,
mOnBeforeRenderListListener,
@@ -669,7 +775,7 @@
inOrder.verify(promoter, atLeastOnce())
.shouldPromoteToTopLevel(any(NotificationEntry.class));
inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
- inOrder.verify(sectioner, atLeastOnce()).getSection(any(ListEntry.class));
+ inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class));
inOrder.verify(comparator, atLeastOnce())
.compare(any(ListEntry.class), any(ListEntry.class));
inOrder.verify(preRenderFilter, atLeastOnce())
@@ -683,12 +789,12 @@
// GIVEN a variety of pluggables
NotifFilter packageFilter = new PackageFilter(PACKAGE_1);
NotifPromoter idPromoter = new IdPromoter(4);
- SectionsProvider sectionsProvider = new PackageSectioner();
+ NotifSection section = new PackageSection(PACKAGE_1);
NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
mListBuilder.addPreGroupFilter(packageFilter);
mListBuilder.addPromoter(idPromoter);
- mListBuilder.setSectionsProvider(sectionsProvider);
+ mListBuilder.setSections(Arrays.asList(section));
mListBuilder.setComparators(Collections.singletonList(hypeComparator));
// GIVEN a set of random notifs
@@ -708,7 +814,7 @@
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
- sectionsProvider.invalidateList();
+ section.invalidateList();
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
@@ -981,9 +1087,10 @@
return builder;
}
- /** Same behavior as {@link #addNotif(int, String)}. */
- private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
+ private NotificationEntryBuilder addGroupChildWithTag(int index, String packageId,
+ String groupId, String tag) {
final NotificationEntryBuilder builder = new NotificationEntryBuilder()
+ .setTag(tag)
.setPkg(packageId)
.setId(nextId(packageId))
.setRank(nextRank());
@@ -998,6 +1105,11 @@
return builder;
}
+ /** Same behavior as {@link #addNotif(int, String)}. */
+ private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
+ return addGroupChildWithTag(index, packageId, groupId, null);
+ }
+
private int nextId(String packageName) {
Integer nextId = mNextIdMap.get(packageName);
if (nextId == null) {
@@ -1080,7 +1192,8 @@
}
} catch (AssertionError err) {
throw new AssertionError(
- "List under test failed verification:\n" + dumpList(mBuiltList), err);
+ "List under test failed verification:\n" + dumpTree(mBuiltList,
+ true, ""), err);
}
}
@@ -1165,6 +1278,21 @@
}
}
+ /** Filters out notifications with a particular tag */
+ private static class NotifFilterWithTag extends NotifFilter {
+ private final String mTag;
+
+ NotifFilterWithTag(String tag) {
+ super("NotifFilterWithTag_" + tag);
+ mTag = tag;
+ }
+
+ @Override
+ public boolean shouldFilterOut(NotificationEntry entry, long now) {
+ return Objects.equals(entry.getSbn().getTag(), mTag);
+ }
+ }
+
/** Promotes notifs with particular IDs */
private static class IdPromoter extends NotifPromoter {
private final List<Integer> mIds;
@@ -1201,25 +1329,18 @@
}
}
- /** Sorts notifs into sections based on their package name */
- private static class PackageSectioner extends SectionsProvider {
+ /** Represents a section for the passed pkg */
+ private static class PackageSection extends NotifSection {
+ private final String mPackage;
- PackageSectioner() {
- super("PackageSectioner");
+ PackageSection(String pkg) {
+ super("PackageSection_" + pkg);
+ mPackage = pkg;
}
@Override
- public int getSection(ListEntry entry) {
- switch (entry.getRepresentativeEntry().getSbn().getPackageName()) {
- case PACKAGE_1:
- return 1;
- case PACKAGE_2:
- return 2;
- case PACKAGE_3:
- return 3;
- default:
- return 4;
- }
+ public boolean isInSection(ListEntry entry) {
+ return entry.getRepresentativeEntry().getSbn().getPackageName().equals(mPackage);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
index 7ff3240..5e0baf2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection.notifcollection;
+package com.android.systemui.statusbar.notification.collection.coalescer;
import static com.android.internal.util.Preconditions.checkNotNull;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
index ea6c70a..701cf95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -36,7 +36,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
@@ -65,7 +65,7 @@
@Mock private ActivityManagerInternal mActivityMangerInternal;
@Mock private IPackageManager mIPackageManager;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
- @Mock private NotifListBuilderImpl mNotifListBuilder;
+ @Mock private NotifPipeline mNotifPipeline;
private Notification mNotification;
private NotificationEntry mEntry;
private DeviceProvisionedCoordinator mDeviceProvisionedCoordinator;
@@ -84,8 +84,8 @@
.build();
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- mDeviceProvisionedCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
+ mDeviceProvisionedCoordinator.attach(mNotifPipeline);
+ verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
mDeviceProvisionedFilter = filterCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
index 01bca0d..6cc8dd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
@@ -36,12 +36,11 @@
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import org.junit.Before;
import org.junit.Test;
@@ -59,8 +58,7 @@
@Mock private Handler mMainHandler;
@Mock private ForegroundServiceController mForegroundServiceController;
@Mock private AppOpsController mAppOpsController;
- @Mock private NotifListBuilderImpl mNotifListBuilder;
- @Mock private NotifCollection mNotifCollection;
+ @Mock private NotifPipeline mNotifPipeline;
private NotificationEntry mEntry;
private Notification mNotification;
@@ -84,9 +82,9 @@
ArgumentCaptor<NotifLifetimeExtender> lifetimeExtenderCaptor =
ArgumentCaptor.forClass(NotifLifetimeExtender.class);
- mForegroundCoordinator.attach(mNotifCollection, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
- verify(mNotifCollection, times(1)).addNotificationLifetimeExtender(
+ mForegroundCoordinator.attach(mNotifPipeline);
+ verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
+ verify(mNotifPipeline, times(1)).addNotificationLifetimeExtender(
lifetimeExtenderCaptor.capture());
mForegroundFilter = filterCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index f921cf9..5866d90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -41,7 +41,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
@@ -68,7 +68,7 @@
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private HighPriorityProvider mHighPriorityProvider;
- @Mock private NotifListBuilderImpl mNotifListBuilder;
+ @Mock private NotifPipeline mNotifPipeline;
private NotificationEntry mEntry;
private KeyguardCoordinator mKeyguardCoordinator;
@@ -87,8 +87,8 @@
.build();
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- mKeyguardCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addPreRenderFilter(filterCaptor.capture());
+ mKeyguardCoordinator.attach(mNotifPipeline);
+ verify(mNotifPipeline, times(1)).addPreRenderFilter(filterCaptor.capture());
mKeyguardFilter = filterCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index d3b16c3..e84f9cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -33,7 +33,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
@@ -50,7 +50,7 @@
public class RankingCoordinatorTest extends SysuiTestCase {
@Mock private StatusBarStateController mStatusBarStateController;
- @Mock private NotifListBuilderImpl mNotifListBuilder;
+ @Mock private NotifPipeline mNotifPipeline;
private NotificationEntry mEntry;
private RankingCoordinator mRankingCoordinator;
private NotifFilter mRankingFilter;
@@ -62,8 +62,8 @@
mEntry = new NotificationEntryBuilder().build();
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- mRankingCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
+ mRankingCoordinator.attach(mNotifPipeline);
+ verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
mRankingFilter = filterCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
new file mode 100644
index 0000000..d7214f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class NotifRemoteViewCacheImplTest extends SysuiTestCase {
+
+ private NotifRemoteViewCacheImpl mNotifRemoteViewCache;
+ private NotificationEntry mEntry;
+ private NotificationEntryListener mEntryListener;
+ @Mock private RemoteViews mRemoteViews;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mEntry = new NotificationEntryBuilder().build();
+
+ NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
+ mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager);
+ ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+ ArgumentCaptor.forClass(NotificationEntryListener.class);
+ verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+ mEntryListener = entryListenerCaptor.getValue();
+ }
+
+ @Test
+ public void testPutCachedView() {
+ // GIVEN an initialized cache for an entry.
+ mEntryListener.onPendingEntryAdded(mEntry);
+
+ // WHEN a notification's cached remote views is put in.
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+ // THEN the remote view is cached.
+ assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ assertEquals(
+ "Cached remote view is not the one we put in.",
+ mRemoteViews,
+ mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ }
+
+ @Test
+ public void testRemoveCachedView() {
+ // GIVEN a cache with a cached view.
+ mEntryListener.onPendingEntryAdded(mEntry);
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+ // WHEN we remove the cached view.
+ mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED);
+
+ // THEN the remote view is not cached.
+ assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ }
+
+ @Test
+ public void testClearCache() {
+ // GIVEN a non-empty cache.
+ mEntryListener.onPendingEntryAdded(mEntry);
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews);
+
+ // WHEN we clear the cache.
+ mNotifRemoteViewCache.clearCache(mEntry);
+
+ // THEN the cache is empty.
+ assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index f916fe5..cb9da6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -22,11 +22,16 @@
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.Notification;
import android.content.Context;
@@ -35,16 +40,17 @@
import android.os.Looper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
-import android.util.ArrayMap;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RemoteViews;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
@@ -57,6 +63,8 @@
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
@@ -73,8 +81,11 @@
private Notification.Builder mBuilder;
private ExpandableNotificationRow mRow;
+ @Mock private NotifRemoteViewCache mCache;
+
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mBuilder = new Notification.Builder(mContext).setSmallIcon(
R.drawable.ic_person)
.setContentTitle("Title")
@@ -83,7 +94,9 @@
ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
mBuilder.build());
mRow = spy(row);
- mNotificationInflater = new NotificationContentInflater();
+ mNotificationInflater = new NotificationContentInflater(
+ mCache,
+ mock(NotificationRemoteInputManager.class));
}
@Test
@@ -174,7 +187,9 @@
result,
FLAG_CONTENT_VIEW_EXPANDED,
0,
- new ArrayMap() /* cachedContentViews */, mRow,
+ mock(NotifRemoteViewCache.class),
+ mRow.getEntry(),
+ mRow,
true /* isNewView */, (v, p, r) -> true,
new InflationCallback() {
@Override
@@ -244,6 +259,71 @@
NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
}
+ @Test
+ public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception {
+ // GIVEN a cached view.
+ RemoteViews contractedRemoteView = mBuilder.createContentView();
+ when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(true);
+ when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(contractedRemoteView);
+
+ // GIVEN existing bound view with same layout id.
+ View view = contractedRemoteView.apply(mContext, null /* parent */);
+ mRow.getPrivateLayout().setContractedChild(view);
+
+ // WHEN inflater inflates
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+ // THEN the view should be re-used
+ assertEquals("Binder inflated a new view even though the old one was cached and usable.",
+ view, mRow.getPrivateLayout().getContractedChild());
+ }
+
+ @Test
+ public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception {
+ // GIVEN a cached remote view.
+ RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView();
+ when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(true);
+ when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(contractedRemoteView);
+
+ // GIVEN existing bound view with different layout id.
+ View view = new TextView(mContext);
+ mRow.getPrivateLayout().setContractedChild(view);
+
+ // WHEN inflater inflates
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+ // THEN the view should be a new view
+ assertNotEquals("Binder (somehow) used the same view when inflating.",
+ view, mRow.getPrivateLayout().getContractedChild());
+ }
+
+ @Test
+ public void testInflationCachesCreatedRemoteView() throws Exception {
+ // WHEN inflater inflates
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+ // THEN inflater informs cache of the new remote view
+ verify(mCache).putCachedView(
+ eq(mRow.getEntry()),
+ eq(FLAG_CONTENT_VIEW_CONTRACTED),
+ any());
+ }
+
+ @Test
+ public void testUnbindRemovesCachedRemoteView() {
+ // WHEN inflated unbinds content
+ mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP);
+
+ // THEN inflated informs cache to remove remote view
+ verify(mCache).removeCachedView(
+ eq(mRow.getEntry()),
+ eq(FLAG_CONTENT_VIEW_HEADS_UP));
+ }
+
private static void inflateAndWait(NotificationContentInflater inflater,
@InflationFlag int contentToInflate,
ExpandableNotificationRow row)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 675b3ef..84c6513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -16,20 +16,17 @@
package com.android.systemui.statusbar.notification.row;
-import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
-import android.graphics.drawable.Icon;
import android.util.ArraySet;
import android.view.NotificationHeaderView;
import android.view.View;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
new file mode 100644
index 0000000..0bf458c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.BubblesTestActivity;
+import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationConversationInfoTest extends SysuiTestCase {
+ private static final String TEST_PACKAGE_NAME = "test_package";
+ private static final String TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME;
+ private static final int TEST_UID = 1;
+ private static final String TEST_CHANNEL = "test_channel";
+ private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
+ private static final String CONVERSATION_ID = "convo";
+
+ private TestableLooper mTestableLooper;
+ private NotificationConversationInfo mNotificationInfo;
+ private NotificationChannel mNotificationChannel;
+ private NotificationChannel mConversationChannel;
+ private StatusBarNotification mSbn;
+ private NotificationEntry mEntry;
+ private StatusBarNotification mBubbleSbn;
+ private NotificationEntry mBubbleEntry;
+ @Mock
+ private ShortcutInfo mShortcutInfo;
+ private Drawable mImage;
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private INotificationManager mMockINotificationManager;
+ @Mock
+ private PackageManager mMockPackageManager;
+ @Mock
+ private VisualStabilityManager mVisualStabilityManager;
+ @Mock
+ private BubbleController mBubbleController;
+ @Mock
+ private LauncherApps mLauncherApps;
+ @Mock
+ private ShortcutManager mShortcutManager;
+ @Mock
+ private NotificationGuts mNotificationGuts;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestableLooper = TestableLooper.get(this);
+
+ mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+ mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+ mDependency.injectTestDependency(BubbleController.class, mBubbleController);
+ // Inflate the layout
+ final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+ mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate(
+ R.layout.notification_conversation_info,
+ null);
+ mNotificationInfo.mShowHomeScreen = true;
+ mNotificationInfo.setGutsParent(mNotificationGuts);
+ doAnswer((Answer<Object>) invocation -> {
+ mNotificationInfo.handleCloseControls(true, false);
+ return null;
+ }).when(mNotificationGuts).closeControls(anyInt(), anyInt(), eq(true), eq(false));
+ // Our view is never attached to a window so the View#post methods in NotificationInfo never
+ // get called. Setting this will skip the post and do the action immediately.
+ mNotificationInfo.mSkipPost = true;
+
+ // PackageManager must return a packageInfo and applicationInfo.
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = TEST_PACKAGE_NAME;
+ when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt()))
+ .thenReturn(packageInfo);
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = TEST_UID; // non-zero
+ when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
+ applicationInfo);
+ final PackageInfo systemPackageInfo = new PackageInfo();
+ systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
+ when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt()))
+ .thenReturn(systemPackageInfo);
+ when(mMockPackageManager.getPackageInfo(eq("android"), anyInt()))
+ .thenReturn(packageInfo);
+
+ when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
+ List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
+ when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
+ mImage = mContext.getDrawable(R.drawable.ic_star);
+ when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo),
+ anyInt())).thenReturn(mImage);
+
+ mNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+
+ Notification notification = new Notification.Builder(mContext, mNotificationChannel.getId())
+ .setShortcutId(CONVERSATION_ID)
+ .setStyle(new Notification.MessagingStyle(new Person.Builder().setName("m").build())
+ .addMessage(new Notification.MessagingStyle.Message(
+ "hello!", 1000, new Person.Builder().setName("other").build())))
+ .build();
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
+ notification, UserHandle.CURRENT, null, 0);
+ mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
+
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0,
+ new Intent(mContext, BubblesTestActivity.class), 0);
+ mBubbleSbn = new SbnBuilder(mSbn).setBubbleMetadata(
+ new Notification.BubbleMetadata.Builder()
+ .setIntent(bubbleIntent)
+ .setIcon(Icon.createWithResource(mContext, R.drawable.android)).build())
+ .build();
+ mBubbleEntry = new NotificationEntryBuilder().setSbn(mBubbleSbn).build();
+
+ mConversationChannel = new NotificationChannel(
+ TEST_CHANNEL + " : " + CONVERSATION_ID, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+ mConversationChannel.setConversationId(TEST_CHANNEL, CONVERSATION_ID);
+ when(mMockINotificationManager.getConversationNotificationChannel(anyString(), anyInt(),
+ anyString(), eq(TEST_CHANNEL), eq(false), eq(CONVERSATION_ID)))
+ .thenReturn(mConversationChannel);
+ }
+
+ @Test
+ public void testBindNotification_SetsTextShortcutName() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.name);
+ assertEquals(mShortcutInfo.getShortLabel(), textView.getText().toString());
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsShortcutIcon() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
+ assertEquals(mImage, view.getDrawable());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextApplicationName() {
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
+ assertTrue(textView.getText().toString().contains("App Name"));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextChannelName() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.parent_channel_name);
+ assertTrue(textView.getText().toString().contains(mNotificationChannel.getName()));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextGroupName() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ when(mMockINotificationManager.getNotificationChannelGroupForPackage(
+ anyString(), anyString(), anyInt())).thenReturn(group);
+ mNotificationChannel.setGroup(group.getId());
+ mConversationChannel.setGroup(group.getId());
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+ assertTrue(textView.getText().toString().contains(group.getName()));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ assertEquals(VISIBLE, textView.getVisibility());
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_GroupNameHiddenIfNoGroup() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ assertEquals(GONE, textView.getVisibility());
+ assertEquals(GONE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_noDelegate() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+ assertEquals(GONE, nameView.getVisibility());
+ final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+ assertEquals(GONE, dividerView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_delegate() throws Exception {
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0,
+ mSbn.getNotification(), UserHandle.CURRENT, null, 0);
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = 7; // non-zero
+ when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn(
+ applicationInfo);
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other");
+
+ NotificationEntry entry = new NotificationEntryBuilder().setSbn(mSbn).build();
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ entry,
+ null,
+ null,
+ null,
+ true);
+ final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+ assertEquals(VISIBLE, nameView.getVisibility());
+ assertTrue(nameView.getText().toString().contains("Proxied"));
+ final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+ assertEquals(VISIBLE, dividerView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsOnClickListenerForSettings() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ (View v, NotificationChannel c, int appUid) -> {
+ assertEquals(mConversationChannel, c);
+ latch.countDown();
+ },
+ null,
+ null,
+ true);
+
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ settingsButton.performClick();
+ // Verify that listener was triggered.
+ assertEquals(0, latch.getCount());
+ }
+
+ @Test
+ public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+ }
+
+ @Test
+ public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ (View v, NotificationChannel c, int appUid) -> {
+ assertEquals(mNotificationChannel, c);
+ latch.countDown();
+ },
+ null,
+ null,
+ false);
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+ }
+
+ @Test
+ public void testBindNotification_bubbleActionVisibleWhenCanBubble() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+ assertEquals(View.VISIBLE, bubbleView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_bubbleActionVisibleWhenCannotBubble() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+ assertEquals(View.GONE, bubbleView.getVisibility());
+ }
+
+ @Test
+ public void testAddToHome() throws Exception {
+ when(mShortcutManager.isRequestPinShortcutSupported()).thenReturn(true);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.home).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null);
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ public void testSnooze() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ (View v, int hours) -> {
+ latch.countDown();
+ },
+ true);
+
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.snooze).performClick();
+ mTestableLooper.processAllMessages();
+
+ assertEquals(0, latch.getCount());
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ public void testBubble_promotesBubble() throws Exception {
+ mNotificationChannel.setAllowBubbles(false);
+ mConversationChannel.setAllowBubbles(false);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ assertFalse(mBubbleEntry.isBubble());
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.bubble).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mBubbleController, times(1)).onUserCreatedBubbleFromNotification(mBubbleEntry);
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertTrue(captor.getValue().canBubble());
+ }
+
+ @Test
+ public void testBubble_demotesBubble() throws Exception {
+ mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ assertTrue(mBubbleEntry.isBubble());
+
+ // Demote it
+ mNotificationInfo.findViewById(R.id.bubble).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mBubbleController, times(1)).onUserDemotedBubbleFromNotification(mBubbleEntry);
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertFalse(captor.getValue().canBubble());
+ }
+
+ @Test
+ public void testFavorite_favorite() throws Exception {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ Button fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_favorite),
+ fave.getText().toString());
+
+ fave.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertTrue(captor.getValue().canBypassDnd());
+ }
+
+ @Test
+ public void testFavorite_unfavorite() throws Exception {
+ mNotificationChannel.setBypassDnd(true);
+ mConversationChannel.setBypassDnd(true);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ Button fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
+ fave.getText().toString());
+
+ fave.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertFalse(captor.getValue().canBypassDnd());
+ }
+
+ @Test
+ public void testMute_mute() throws Exception {
+ mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ Button mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_mute),
+ mute.getText().toString());
+
+ mute.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertEquals(IMPORTANCE_LOW, captor.getValue().getImportance());
+ }
+
+ @Test
+ public void testMute_unmute() throws Exception {
+ mNotificationChannel.setImportance(IMPORTANCE_LOW);
+ mNotificationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+ mConversationChannel.setImportance(IMPORTANCE_LOW);
+ mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ Button mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_unmute),
+ mute.getText().toString());
+
+ mute.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance());
+ }
+
+ @Test
+ public void testBindNotification_createsNewChannel() throws Exception {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
+ anyString(), anyInt(), any(), eq(CONVERSATION_ID));
+ }
+
+ @Test
+ public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception {
+ mNotificationChannel.setConversationId("", CONVERSATION_ID);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
+ anyString(), anyInt(), any(), eq(CONVERSATION_ID));
+ }
+
+ @Test
+ public void testAdjustImportanceTemporarilyAllowsReordering() {
+ mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ mNotificationInfo.findViewById(R.id.mute).performClick();
+
+ verify(mVisualStabilityManager).temporarilyAllowReordering();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index f48c40c..b33d26f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -132,16 +132,6 @@
}
@Test
- public void testNoAppOpsInSlowSwipe_biDirectionalSwipe() {
- NotificationMenuRow row = new NotificationMenuRow(mContext, true);
- row.createMenu(mRow, null);
-
- ViewGroup container = (ViewGroup) row.getMenuView();
- // in the new interruption model there is only the blocking item
- assertEquals(1, container.getChildCount());
- }
-
- @Test
public void testIsSnappedAndOnSameSide() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ea8d4ee..d9939f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -71,6 +71,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter;
@@ -89,6 +90,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.leak.LeakDetector;
import org.junit.After;
import org.junit.Before;
@@ -170,7 +172,10 @@
mock(HighPriorityProvider.class)
),
mock(NotificationEntryManager.KeyguardEnvironment.class),
- mock(FeatureFlags.class));
+ mock(FeatureFlags.class),
+ () -> mock(NotificationRowBinder.class),
+ () -> mRemoteInputManager,
+ mock(LeakDetector.class));
mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 5ac7bfb..782e14c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -54,7 +54,7 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
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 7e485f4..fee4852 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -120,7 +120,8 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -213,7 +214,7 @@
@Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
@Mock private KeyguardBypassController mKeyguardBypassController;
@Mock private DynamicPrivacyController mDynamicPrivacyController;
- @Mock private NewNotifPipeline mNewNotifPipeline;
+ @Mock private NotifPipelineInitializer mNewNotifPipeline;
@Mock private ZenModeController mZenModeController;
@Mock private AutoHideController mAutoHideController;
@Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
@@ -255,6 +256,7 @@
@Mock private KeyguardDismissUtil mKeyguardDismissUtil;
@Mock private ExtensionController mExtensionController;
@Mock private UserInfoControllerImpl mUserInfoControllerImpl;
+ @Mock private NotificationRowBinderImpl mNotificationRowBinder;
private ShadeController mShadeController;
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private InitController mInitController = new InitController();
@@ -344,7 +346,6 @@
mHeadsUpManager,
mDynamicPrivacyController,
mBypassHeadsUpNotifier,
- true,
() -> mNewNotifPipeline,
new FalsingManagerFake(),
mBroadcastDispatcher,
@@ -413,6 +414,7 @@
mKeyguardDismissUtil,
mExtensionController,
mUserInfoControllerImpl,
+ mNotificationRowBinder,
mDismissCallbackRegistry);
when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index c4caeb3..13f3a5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.policy;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -39,6 +42,7 @@
import android.os.Handler;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -180,7 +184,7 @@
protected void setupNetworkController() {
// For now just pretend to be the data sim, so we can test that too.
mSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- when(mMockTm.isDataCapable()).thenReturn(true);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(true);
setDefaultSubId(mSubId);
setSubscriptions(mSubId);
mMobileSignalController = mNetworkController.mMobileSignalControllers.get(mSubId);
@@ -358,7 +362,13 @@
}
public void updateDataConnectionState(int dataState, int dataNetType) {
- when(mServiceState.getDataNetworkType()).thenReturn(dataNetType);
+ NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setDomain(DOMAIN_PS)
+ .setAccessNetworkTechnology(dataNetType)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+ .thenReturn(fakeRegInfo);
mPhoneStateListener.onDataConnectionStateChanged(dataState, dataNetType);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 95b055c..f6c750d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,5 +1,8 @@
package com.android.systemui.statusbar.policy;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyInt;
@@ -121,7 +124,7 @@
@Test
public void testNoInternetIcon_withDefaultSub() {
setupNetworkController();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -135,7 +138,7 @@
@Test
public void testDataDisabledIcon_withDefaultSub() {
setupNetworkController();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -149,7 +152,7 @@
@Test
public void testNonDefaultSIM_showsFullSignal_connected() {
setupNetworkController();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
setupDefaultSignal();
setDefaultSubId(mSubId + 1);
updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
@@ -164,7 +167,7 @@
@Test
public void testNonDefaultSIM_showsFullSignal_disconnected() {
setupNetworkController();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
setupDefaultSignal();
setDefaultSubId(mSubId + 1);
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
@@ -418,7 +421,13 @@
// State from NR_5G to NONE NR_STATE_RESTRICTED, showing corresponding icon
doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
- doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+ NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setDomain(DOMAIN_PS)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+ .build();
+ doReturn(fakeRegInfo).when(mServiceState)
+ .getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
TelephonyManager.NETWORK_TYPE_LTE);
@@ -429,7 +438,7 @@
@Test
public void testDataDisabledIcon_UserNotSetup() {
setupNetworkController();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -444,7 +453,7 @@
@Test
public void testAlwaysShowDataRatIcon() {
setupDefaultSignal();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED,
TelephonyManager.NETWORK_TYPE_GSM);
@@ -480,8 +489,13 @@
verifyDataIndicators(TelephonyIcons.ICON_LTE);
- when(mServiceState.getDataNetworkType())
- .thenReturn(TelephonyManager.NETWORK_TYPE_HSPA);
+ NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setDomain(DOMAIN_PS)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_HSPA)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+ .thenReturn(fakeRegInfo);
updateServiceState();
verifyDataIndicators(TelephonyIcons.ICON_H);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 4103d71..cd89d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -30,12 +30,12 @@
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.net.DataUsageController;
@@ -418,7 +418,7 @@
intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn);
intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn);
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mSubId);
+ SubscriptionManager.putSubscriptionIdExtra(intent, mSubId);
return intent;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
index 709a1a8..a785d4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -19,6 +19,7 @@
import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -154,6 +155,7 @@
verify(mockEndListener).onAnimationEnd(
testView,
DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
canceled = false,
finalValue = 10f,
finalVelocity = 0f,
@@ -175,6 +177,7 @@
verify(mockEndListener).onAnimationEnd(
testView,
DynamicAnimation.TRANSLATION_Y,
+ wasFling = false,
canceled = false,
finalValue = 500f,
finalVelocity = 0f,
@@ -214,8 +217,8 @@
verifyUpdateListenerCalls(animator, mockUpdateListener)
verify(mockEndListener, times(1)).onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(),
- eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(false), anyFloat(),
+ anyFloat(), eq(true))
verify(mockEndAction, times(1)).run()
animator
@@ -329,13 +332,24 @@
// The original end listener should also have been called, with allEnded = true since it was
// provided to an animator that animated only TRANSLATION_X.
verify(mockEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, true)
+ .onAnimationEnd(
+ testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 200f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
verifyNoMoreInteractions(mockEndListener)
// The second end listener should have been called, but with allEnded = false since it was
// provided to an animator that animated both TRANSLATION_X and TRANSLATION_Y.
verify(secondEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, false)
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 200f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = false)
verifyNoMoreInteractions(secondEndListener)
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
@@ -345,7 +359,12 @@
verifyNoMoreInteractions(mockEndListener)
verify(secondEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true)
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y,
+ wasFling = false,
+ canceled = false,
+ finalValue = 4000f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
verifyNoMoreInteractions(secondEndListener)
}
@@ -364,8 +383,8 @@
assertEquals(10f, testView.translationX, 1f)
verify(mockEndListener, times(1))
.onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f),
- anyFloat(), eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+ eq(10f), anyFloat(), eq(true))
animator
.fling(
@@ -381,8 +400,39 @@
assertEquals(-5f, testView.translationX, 1f)
verify(mockEndListener, times(1))
.onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f),
- anyFloat(), eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+ eq(-5f), anyFloat(), eq(true))
+ }
+
+ @Test
+ fun testIsPropertyAnimating() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ testView.physicsAnimator
+ .spring(DynamicAnimation.TRANSLATION_X, 500f, springConfig)
+ .fling(DynamicAnimation.TRANSLATION_Y, 10f, flingConfig)
+ .spring(DynamicAnimation.TRANSLATION_Z, 1000f, springConfig)
+ .start()
+
+ // All of the properties we just started should be animating.
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+ // Block until x and y end.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(testView.physicsAnimator,
+ DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)
+
+ // Verify that x and y are no longer animating, but that Z is (it's springing to 1000f).
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Z)
+
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
}
@Test
@@ -395,6 +445,118 @@
assertEquals(200f, testView.translationX, 1f)
}
+ @Test
+ fun testFlingThenSpring() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ // Start at 500f and fling hard to the left. We should quickly reach the 250f minimum, fly
+ // past it since there's so much velocity remaining, then spring back to 250f.
+ testView.translationX = 500f
+ animator
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -5000f,
+ flingConfig.apply { min = 250f },
+ springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // Block until we pass the minimum.
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+ animator) { v -> v.translationX <= 250f }
+
+ // Double check that the view is there.
+ assertTrue(testView.translationX <= 250f)
+
+ // The update listener should have been called with a value < 500f, and then a value less
+ // than or equal to the 250f minimum.
+ verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < 500f },
+ { u -> u.value <= 250f })
+
+ // Despite the fact that the fling has ended, the end listener shouldn't have been called
+ // since we're about to begin springing the same property.
+ verifyNoMoreInteractions(mockEndListener)
+ verifyNoMoreInteractions(mockEndAction)
+
+ // Wait for the spring to finish.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_X)
+
+ // Make sure we continued past 250f since the spring should have been started with some
+ // remaining negative velocity from the fling.
+ verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < 250f })
+
+ // At this point, the animation end listener should have been called once, and only once,
+ // when the spring ended at 250f.
+ verify(mockEndListener).onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 250f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // The end action should also have been called once.
+ verify(mockEndAction, times(1)).run()
+ verifyNoMoreInteractions(mockEndAction)
+
+ assertEquals(250f, testView.translationX)
+ }
+
+ @Test
+ fun testFlingThenSpring_objectOutsideFlingBounds() {
+ // Start the view at x = -500, well outside the fling bounds of min = 0f, with strong
+ // negative velocity.
+ testView.translationX = -500f
+ animator
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -5000f,
+ flingConfig.apply { min = 0f },
+ springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // The initial -5000f velocity should result in frames to the left of -500f before the view
+ // springs back towards 0f.
+ verifyAnimationUpdateFrames(
+ animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < -500f },
+ { u -> u.value > -500f })
+
+ // We should end up at the fling min.
+ assertEquals(0f, testView.translationX, 1f)
+ }
+
+ @Test
+ fun testFlingToMinMaxThenSpring() {
+ // Start at x = 500f.
+ testView.translationX = 500f
+
+ // Fling to the left at the very sad rate of -1 pixels per second. That won't get us much of
+ // anywhere, and certainly not to the 0f min.
+ animator
+ // Good thing we have flingToMinMaxThenSpring!
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -10000f,
+ flingConfig.apply { min = 0f },
+ springConfig,
+ flingMustReachMinOrMax = true)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // Thanks, flingToMinMaxThenSpring, for adding enough velocity to get us here.
+ assertEquals(0f, testView.translationX, 1f)
+ }
+
/**
* Verifies that the calls to the mock update listener match the animation update frames
* reported by the test internal listener, in order.
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index d297f3f..e441fb5 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -16,7 +16,7 @@
java_defaults {
name: "TetheringAndroidLibraryDefaults",
- platform_apis: true,
+ sdk_version: "system_current",
srcs: [
"src/**/*.java",
":framework-tethering-shared-srcs",
@@ -29,6 +29,7 @@
"netlink-client",
"networkstack-aidl-interfaces-unstable-java",
"android.hardware.tetheroffload.control-V1.0-java",
+ "net-utils-framework-common",
],
libs: [
"framework-tethering",
@@ -80,7 +81,7 @@
// Common defaults for compiling the actual APK.
java_defaults {
name: "TetheringAppDefaults",
- platform_apis: true,
+ sdk_version: "system_current",
privileged: true,
// Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
// explicitly.
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index e99c2c5..c71d0d7 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -32,9 +32,11 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
<uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<application
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 37e679d..19e8d69 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -1,7 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<resources>
<!--
OEMs that wish to change the below settings must do so via a runtime resource overlay package
and *NOT* by changing this file. This file is part of the tethering mainline module.
+ TODO: define two resources for each config item: a default_* resource and a config_* resource,
+ config_* is empty by default but may be overridden by RROs.
-->
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ USB interfaces. If the device doesn't want to support tethering over USB this should
+ be empty. An example would be "usb.*" -->
+ <string-array translatable="false" name="config_tether_usb_regexs">
+ <item>"usb\\d"</item>
+ <item>"rndis\\d"</item>
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ Wifi interfaces. If the device doesn't want to support tethering over Wifi this
+ should be empty. An example would be "softap.*" -->
+ <string-array translatable="false" name="config_tether_wifi_regexs">
+ <item>"wlan\\d"</item>
+ <item>"softap\\d"</item>
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ Wifi P2P interfaces. If the device doesn't want to support tethering over Wifi P2p this
+ should be empty. An example would be "p2p-p2p\\d-.*" -->
+ <string-array translatable="false" name="config_tether_wifi_p2p_regexs">
+ <item>"p2p-p2p\\d-.*"</item>
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ bluetooth interfaces. If the device doesn't want to support tethering over bluetooth this
+ should be empty. -->
+ <string-array translatable="false" name="config_tether_bluetooth_regexs">
+ <item>"bt-pan"</item>
+ </string-array>
+
+ <!-- Use the old dnsmasq DHCP server for tethering instead of the framework implementation. -->
+ <bool translatable="false" name="config_tether_enable_legacy_dhcp_server">false</bool>
+
+ <!-- Dhcp range (min, max) to use for tethering purposes -->
+ <string-array translatable="false" name="config_tether_dhcp_range">
+ </string-array>
+
+ <!-- Array of ConnectivityManager.TYPE_{BLUETOOTH, ETHERNET, MOBILE, MOBILE_DUN, MOBILE_HIPRI,
+ WIFI} values allowable for tethering.
+
+ Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
+ [1,7,0] for TYPE_WIFI, TYPE_BLUETOOTH, and TYPE_MOBILE.
+
+ This list is also modified by code within the framework, including:
+
+ - TYPE_ETHERNET (9) is prepended to this list, and
+
+ - the return value of TelephonyManager.isTetheringApnRequired()
+ determines how the array is further modified:
+
+ * TRUE (DUN REQUIRED).
+ TYPE_MOBILE is removed (if present).
+ TYPE_MOBILE_HIPRI is removed (if present).
+ TYPE_MOBILE_DUN is appended (if not already present).
+
+ * FALSE (DUN NOT REQUIRED).
+ TYPE_MOBILE_DUN is removed (if present).
+ If both of TYPE_MOBILE{,_HIPRI} are not present:
+ TYPE_MOBILE is appended.
+ TYPE_MOBILE_HIPRI is appended.
+
+ For other changes applied to this list, now and in the future, see
+ com.android.server.connectivity.tethering.TetheringConfiguration.
+
+ Note also: the order of this is important. The first upstream type
+ for which a satisfying network exists is used.
+ -->
+ <integer-array translatable="false" name="config_tether_upstream_types">
+ </integer-array>
+
+ <!-- When true, the tethering upstream network follows the current default
+ Internet network (except when the current default network is mobile,
+ in which case a DUN network will be used if required).
+
+ When true, overrides the config_tether_upstream_types setting above.
+ -->
+ <bool translatable="false" name="config_tether_upstream_automatic">true</bool>
+
+
+ <!-- If the mobile hotspot feature requires provisioning, a package name and class name
+ can be provided to launch a supported application that provisions the devices.
+ EntitlementManager will send an intent to Settings with the specified package name and
+ class name in extras to launch provision app.
+ TODO: note what extras here.
+
+ See EntitlementManager#runUiTetherProvisioning and
+ packages/apps/Settings/src/com/android/settings/network/TetherProvisioningActivity.java
+ for more details.
+
+ For ui-less/periodic recheck support see config_mobile_hotspot_provision_app_no_ui
+ -->
+ <!-- The first element is the package name and the second element is the class name
+ of the provisioning app -->
+ <string-array translatable="false" name="config_mobile_hotspot_provision_app">
+ <!--
+ <item>com.example.provisioning</item>
+ <item>com.example.provisioning.Activity</item>
+ -->
+ </string-array>
+
+ <!-- If the mobile hotspot feature requires provisioning, an action can be provided
+ that will be broadcast in non-ui cases for checking the provisioning status.
+ EntitlementManager will pass the specified name to Settings and Settings would
+ launch provisioning app by sending an intent with the package name.
+
+ A second broadcast, action defined by config_mobile_hotspot_provision_response,
+ will be sent back to notify if provisioning succeeded or not. The response will
+ match that of the activity in config_mobile_hotspot_provision_app, but instead
+ contained within the int extra "EntitlementResult".
+ TODO: provide the system api for "EntitlementResult" extra and note it here.
+
+ See EntitlementManager#runSilentTetherProvisioning and
+ packages/apps/Settings/src/com/android/settings/wifi/tether/TetherService.java for more
+ details.
+ -->
+ <string translatable="false" name="config_mobile_hotspot_provision_app_no_ui"></string>
+
+ <!-- Sent in response to a provisioning check. The caller must hold the
+ permission android.permission.TETHER_PRIVILEGED for Settings to
+ receive this response.
+
+ See config_mobile_hotspot_provision_response
+ -->
+ <string translatable="false" name="config_mobile_hotspot_provision_response"></string>
+
+ <!-- Number of hours between each background provisioning call -->
+ <integer translatable="false" name="config_mobile_hotspot_provision_check_period">24</integer>
+
+ <!-- ComponentName of the service used to run no ui tether provisioning. -->
+ <string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string>
</resources>
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
new file mode 100644
index 0000000..e089d9d
--- /dev/null
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <overlayable name="TetheringConfig">
+ <policy type="product|system|vendor">
+ <item type="array" name="config_tether_usb_regexs"/>
+ <item type="array" name="config_tether_wifi_regexs"/>
+ <item type="array" name="config_tether_wifi_p2p_regexs"/>
+ <item type="array" name="config_tether_bluetooth_regexs"/>
+ <item type="array" name="config_tether_dhcp_range"/>
+ <item type="bool" name="config_tether_enable_legacy_dhcp_server"/>
+ <item type="array" name="config_tether_upstream_types"/>
+ <item type="bool" name="config_tether_upstream_automatic"/>
+ <!-- Configuration values for tethering entitlement check -->
+ <item type="array" name="config_mobile_hotspot_provision_app"/>
+ <item type="string" name="config_mobile_hotspot_provision_app_no_ui"/>
+ <item type="string" name="config_mobile_hotspot_provision_response"/>
+ <item type="integer" name="config_mobile_hotspot_provision_check_period"/>
+ <item type="string" name="config_wifi_tether_enable"/>
+ </policy>
+ </overlayable>
+</resources>
diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml
index ca866a9..792bce9 100644
--- a/packages/Tethering/res/values/strings.xml
+++ b/packages/Tethering/res/values/strings.xml
@@ -1,4 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<resources>
<!-- Shown when the device is tethered -->
<!-- Strings for tethered notification title [CHAR LIMIT=200] -->
@@ -9,8 +23,11 @@
<!-- This notification is shown when tethering has been disabled on a user's device.
The device is managed by the user's employer. Tethering can't be turned on unless the
IT administrator allows it. The noun "admin" is another reference for "IT administrator." -->
- <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
+ <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
<string name="disable_tether_notification_title">Tethering is disabled</string>
- <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
+ <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
<string name="disable_tether_notification_message">Contact your admin for details</string>
+
+ <!-- Strings for tether notification channel name [CHAR LIMIT=200] -->
+ <string name="notification_channel_tethering_status">Hotspot & tethering status</string>
</resources>
\ No newline at end of file
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 6ac467e..4306cf0b 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -26,7 +26,6 @@
import android.net.INetd;
import android.net.INetworkStackStatusCallback;
-import android.net.INetworkStatsService;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -176,7 +175,6 @@
private final SharedLog mLog;
private final INetd mNetd;
- private final INetworkStatsService mStatsService;
private final Callback mCallback;
private final InterfaceController mInterfaceCtrl;
@@ -208,12 +206,10 @@
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
- INetd netd, INetworkStatsService statsService, Callback callback,
- boolean usingLegacyDhcp, Dependencies deps) {
+ INetd netd, Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
super(ifaceName, looper);
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
- mStatsService = statsService;
mCallback = callback;
mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
mIfaceName = ifaceName;
@@ -882,12 +878,6 @@
// to remove their rules, which generates errors.
// Just do the best we can.
try {
- // About to tear down NAT; gather remaining statistics.
- mStatsService.forceUpdate();
- } catch (Exception e) {
- mLog.e("Exception in forceUpdate: " + e.toString());
- }
- try {
mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface);
} catch (RemoteException | ServiceSpecificException e) {
mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString());
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index 4e2a2c1..1cabc8d 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -27,8 +27,6 @@
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED;
-import static com.android.internal.R.string.config_wifi_tether_enable;
-
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -36,7 +34,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.res.Resources;
import android.net.util.SharedLog;
import android.os.Bundle;
import android.os.Handler;
@@ -54,6 +51,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StateMachine;
+import com.android.networkstack.tethering.R;
import java.io.PrintWriter;
@@ -75,9 +73,7 @@
"com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM";
private static final String EXTRA_SUBID = "subId";
- // {@link ComponentName} of the Service used to run tether provisioning.
- private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(
- Resources.getSystem().getString(config_wifi_tether_enable));
+ private final ComponentName mSilentProvisioningService;
private static final int MS_PER_HOUR = 60 * 60 * 1000;
private static final int EVENT_START_PROVISIONING = 0;
private static final int EVENT_STOP_PROVISIONING = 1;
@@ -122,6 +118,8 @@
mHandler = new EntitlementHandler(masterHandler.getLooper());
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM),
null, mHandler);
+ mSilentProvisioningService = ComponentName.unflattenFromString(
+ mContext.getResources().getString(R.string.config_wifi_tether_enable));
}
public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) {
@@ -377,7 +375,7 @@
intent.putExtra(EXTRA_RUN_PROVISION, true);
intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
intent.putExtra(EXTRA_SUBID, subId);
- intent.setComponent(TETHER_SERVICE);
+ intent.setComponent(mSilentProvisioningService);
// Only admin user can change tethering and SilentTetherProvisioning don't need to
// show UI, it is fine to always start setting's background service as system user.
mContext.startService(intent);
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
index ce7c2a6..cc36f4a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
@@ -16,35 +16,40 @@
package com.android.server.connectivity.tethering;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_UID;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
-import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.NetworkStats.UID_TETHERING;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.usage.NetworkStatsManager;
import android.content.ContentResolver;
-import android.net.ITetheringStatsProvider;
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
import android.net.RouteInfo;
import android.net.netlink.ConntrackMessage;
import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkSocket;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
import android.net.util.SharedLog;
import android.os.Handler;
-import android.os.INetworkManagementService;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.SystemClock;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.text.TextUtils;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
@@ -73,13 +78,19 @@
private static final String ANYIP = "0.0.0.0";
private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
+ @VisibleForTesting
+ enum StatsType {
+ STATS_PER_IFACE,
+ STATS_PER_UID,
+ }
+
private enum UpdateType { IF_NEEDED, FORCE };
private final Handler mHandler;
private final OffloadHardwareInterface mHwInterface;
private final ContentResolver mContentResolver;
- private final INetworkManagementService mNms;
- private final ITetheringStatsProvider mStatsProvider;
+ private final @NonNull OffloadTetheringStatsProvider mStatsProvider;
+ private final @Nullable NetworkStatsProviderCallback mStatsProviderCb;
private final SharedLog mLog;
private final HashMap<String, LinkProperties> mDownstreams;
private boolean mConfigInitialized;
@@ -109,22 +120,23 @@
private int mNatUpdateNetlinkErrors;
public OffloadController(Handler h, OffloadHardwareInterface hwi,
- ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
+ ContentResolver contentResolver, NetworkStatsManager nsm, SharedLog log) {
mHandler = h;
mHwInterface = hwi;
mContentResolver = contentResolver;
- mNms = nms;
mStatsProvider = new OffloadTetheringStatsProvider();
mLog = log.forSubComponent(TAG);
mDownstreams = new HashMap<>();
mExemptPrefixes = new HashSet<>();
mLastLocalPrefixStrs = new HashSet<>();
-
+ NetworkStatsProviderCallback providerCallback = null;
try {
- mNms.registerTetheringStatsProvider(mStatsProvider, getClass().getSimpleName());
- } catch (RemoteException e) {
- mLog.e("Cannot register offload stats provider: " + e);
+ providerCallback = nsm.registerNetworkStatsProvider(
+ getClass().getSimpleName(), mStatsProvider);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Cannot register offload stats provider: " + e);
}
+ mStatsProviderCb = providerCallback;
}
/** Start hardware offload. */
@@ -173,7 +185,7 @@
// and we need to synchronize stats and limits between
// software and hardware forwarding.
updateStatsForAllUpstreams();
- forceTetherStatsPoll();
+ mStatsProvider.pushTetherStats();
}
@Override
@@ -186,7 +198,7 @@
// limits set take into account any software tethering
// traffic that has been happening in the meantime.
updateStatsForAllUpstreams();
- forceTetherStatsPoll();
+ mStatsProvider.pushTetherStats();
// [2] (Re)Push all state.
computeAndPushLocalPrefixes(UpdateType.FORCE);
pushAllDownstreamState();
@@ -204,14 +216,11 @@
// the HAL queued the callback.
// TODO: rev the HAL so that it provides an interface name.
- // Fetch current stats, so that when our notification reaches
- // NetworkStatsService and triggers a poll, we will respond with
- // current data (which will be above the limit that was reached).
- // Note that if we just changed upstream, this is unnecessary but harmless.
- // The stats for the previous upstream were already updated on this thread
- // just after the upstream was changed, so they are also up-to-date.
updateStatsForCurrentUpstream();
- forceTetherStatsPoll();
+ mStatsProvider.pushTetherStats();
+ // Push stats to service does not cause the service react to it immediately.
+ // Inform the service about limit reached.
+ if (mStatsProviderCb != null) mStatsProviderCb.onLimitReached();
}
@Override
@@ -253,42 +262,37 @@
return mConfigInitialized && mControlInitialized;
}
- private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
- @Override
- public NetworkStats getTetherStats(int how) {
- // getTetherStats() is the only function in OffloadController that can be called from
- // a different thread. Do not attempt to update stats by querying the offload HAL
- // synchronously from a different thread than our Handler thread. http://b/64771555.
- Runnable updateStats = () -> {
- updateStatsForCurrentUpstream();
- };
- if (Looper.myLooper() == mHandler.getLooper()) {
- updateStats.run();
- } else {
- mHandler.post(updateStats);
- }
+ @VisibleForTesting
+ class OffloadTetheringStatsProvider extends AbstractNetworkStatsProvider {
+ // These stats must only ever be touched on the handler thread.
+ @NonNull
+ private NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+ @NonNull
+ private NetworkStats mUidStats = new NetworkStats(0L, 0);
- NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
- NetworkStats.Entry entry = new NetworkStats.Entry();
- entry.set = SET_DEFAULT;
- entry.tag = TAG_NONE;
- entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;
+ @VisibleForTesting
+ @NonNull
+ NetworkStats getTetherStats(@NonNull StatsType how) {
+ NetworkStats stats = new NetworkStats(0L, 0);
+ final int uid = (how == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL;
- for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
- ForwardedStats value = kv.getValue();
- entry.iface = kv.getKey();
- entry.rxBytes = value.rxBytes;
- entry.txBytes = value.txBytes;
- stats.addEntry(entry);
+ for (final Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
+ final ForwardedStats value = kv.getValue();
+ final Entry entry = new Entry(kv.getKey(), uid, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, value.rxBytes, 0L, value.txBytes, 0L, 0L);
+ stats = stats.addValues(entry);
}
return stats;
}
@Override
- public void setInterfaceQuota(String iface, long quotaBytes) {
+ public void setLimit(String iface, long quotaBytes) {
+ mLog.i("setLimit: " + iface + "," + quotaBytes);
+ // Listen for all iface is necessary since upstream might be changed after limit
+ // is set.
mHandler.post(() -> {
- if (quotaBytes == ITetheringStatsProvider.QUOTA_UNLIMITED) {
+ if (quotaBytes == QUOTA_UNLIMITED) {
mInterfaceQuotas.remove(iface);
} else {
mInterfaceQuotas.put(iface, quotaBytes);
@@ -296,6 +300,42 @@
maybeUpdateDataLimit(iface);
});
}
+
+ /**
+ * Push stats to service, but does not cause a force polling. Note that this can only be
+ * called on the handler thread.
+ */
+ public void pushTetherStats() {
+ // TODO: remove the accumulated stats and report the diff from HAL directly.
+ if (null == mStatsProviderCb) return;
+ final NetworkStats ifaceDiff =
+ getTetherStats(StatsType.STATS_PER_IFACE).subtract(mIfaceStats);
+ final NetworkStats uidDiff =
+ getTetherStats(StatsType.STATS_PER_UID).subtract(mUidStats);
+ try {
+ mStatsProviderCb.onStatsUpdated(0 /* token */, ifaceDiff, uidDiff);
+ mIfaceStats = mIfaceStats.add(ifaceDiff);
+ mUidStats = mUidStats.add(uidDiff);
+ } catch (RuntimeException e) {
+ mLog.e("Cannot report network stats: ", e);
+ }
+ }
+
+ @Override
+ public void requestStatsUpdate(int token) {
+ mLog.i("requestStatsUpdate: " + token);
+ // Do not attempt to update stats by querying the offload HAL
+ // synchronously from a different thread than the Handler thread. http://b/64771555.
+ mHandler.post(() -> {
+ updateStatsForCurrentUpstream();
+ pushTetherStats();
+ });
+ }
+
+ @Override
+ public void setAlert(long quotaBytes) {
+ // TODO: Ask offload HAL to notify alert without stopping traffic.
+ }
}
private String currentUpstreamInterface() {
@@ -353,14 +393,6 @@
}
}
- private void forceTetherStatsPoll() {
- try {
- mNms.tetherLimitReached(mStatsProvider);
- } catch (RemoteException e) {
- mLog.e("Cannot report data limit reached: " + e);
- }
- }
-
/** Set current tethering upstream LinkProperties. */
public void setUpstreamLinkProperties(LinkProperties lp) {
if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 4a8ef1f..90b9d3f 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -29,6 +29,8 @@
import android.os.RemoteException;
import android.system.OsConstants;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
@@ -91,6 +93,12 @@
txBytes = 0;
}
+ @VisibleForTesting
+ public ForwardedStats(long rxBytes, long txBytes) {
+ this.rxBytes = rxBytes;
+ this.txBytes = txBytes;
+ }
+
/** Add Tx/Rx bytes. */
public void add(ForwardedStats other) {
rxBytes += other.rxBytes;
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 d6abfb9..37e0cc1 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -20,6 +20,7 @@
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
@@ -50,8 +51,10 @@
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
@@ -62,9 +65,8 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
import android.net.ITetheringEventCallback;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -106,8 +108,6 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
@@ -177,8 +177,6 @@
private final Context mContext;
private final ArrayMap<String, TetherState> mTetherStates;
private final BroadcastReceiver mStateReceiver;
- private final INetworkStatsService mStatsService;
- private final INetworkPolicyManager mPolicyManager;
private final Looper mLooper;
private final StateMachine mTetherMasterSM;
private final OffloadController mOffloadController;
@@ -208,13 +206,12 @@
private boolean mWifiTetherRequested;
private Network mTetherUpstream;
private TetherStatesParcel mTetherStatesParcel;
+ private boolean mDataSaverEnabled = false;
public Tethering(TetheringDependencies deps) {
mLog.mark("Tethering.constructed");
mDeps = deps;
mContext = mDeps.getContext();
- mStatsService = mDeps.getINetworkStatsService();
- mPolicyManager = mDeps.getINetworkPolicyManager();
mNetd = mDeps.getINetd(mContext);
mLooper = mDeps.getTetheringLooper();
@@ -225,10 +222,12 @@
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
mTetherMasterSM.start();
+ final NetworkStatsManager statsManager =
+ (NetworkStatsManager) mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
mHandler = mTetherMasterSM.getHandler();
mOffloadController = new OffloadController(mHandler,
mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(),
- mDeps.getINetworkManagementService(), mLog);
+ statsManager, mLog);
mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
mForwardedDownstreams = new HashSet<>();
@@ -265,7 +264,7 @@
}
final UserManager userManager = (UserManager) mContext.getSystemService(
- Context.USER_SERVICE);
+ Context.USER_SERVICE);
mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
final TetheringThreadExecutor executor = new TetheringThreadExecutor(mHandler);
mActiveDataSubIdListener = new ActiveDataSubIdListener(executor);
@@ -289,6 +288,7 @@
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
+ filter.addAction(ACTION_RESTRICT_BACKGROUND_CHANGED);
mContext.registerReceiver(mStateReceiver, filter, null, handler);
}
@@ -485,7 +485,7 @@
}
private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
- final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ final BluetoothAdapter adapter = mDeps.getBluetoothAdapter();
if (adapter == null || !adapter.isEnabled()) {
Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: "
+ (adapter == null));
@@ -663,19 +663,19 @@
if (usbTethered) {
if (wifiTethered || bluetoothTethered) {
- showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL);
+ showTetheredNotification(R.drawable.stat_sys_tether_general);
} else {
- showTetheredNotification(SystemMessage.NOTE_TETHER_USB);
+ showTetheredNotification(R.drawable.stat_sys_tether_usb);
}
} else if (wifiTethered) {
if (bluetoothTethered) {
- showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL);
+ showTetheredNotification(R.drawable.stat_sys_tether_general);
} else {
/* We now have a status bar icon for WifiTethering, so drop the notification */
clearTetheredNotification();
}
} else if (bluetoothTethered) {
- showTetheredNotification(SystemMessage.NOTE_TETHER_BLUETOOTH);
+ showTetheredNotification(R.drawable.stat_sys_tether_bluetooth);
} else {
clearTetheredNotification();
}
@@ -688,30 +688,22 @@
@VisibleForTesting
protected void showTetheredNotification(int id, boolean tetheringOn) {
NotificationManager notificationManager =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
+ .getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
return;
}
- int icon = 0;
- switch(id) {
- case SystemMessage.NOTE_TETHER_USB:
- icon = R.drawable.stat_sys_tether_usb;
- break;
- case SystemMessage.NOTE_TETHER_BLUETOOTH:
- icon = R.drawable.stat_sys_tether_bluetooth;
- break;
- case SystemMessage.NOTE_TETHER_GENERAL:
- default:
- icon = R.drawable.stat_sys_tether_general;
- break;
- }
+ final NotificationChannel channel = new NotificationChannel(
+ "TETHERING_STATUS",
+ mContext.getResources().getString(R.string.notification_channel_tethering_status),
+ NotificationManager.IMPORTANCE_LOW);
+ notificationManager.createNotificationChannel(channel);
if (mLastNotificationId != 0) {
- if (mLastNotificationId == icon) {
+ if (mLastNotificationId == id) {
return;
}
- notificationManager.cancelAsUser(null, mLastNotificationId,
- UserHandle.ALL);
+ notificationManager.cancel(null, mLastNotificationId);
mLastNotificationId = 0;
}
@@ -719,8 +711,8 @@
intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
- PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0,
- null, UserHandle.CURRENT);
+ PendingIntent pi = PendingIntent.getActivity(
+ mContext.createContextAsUser(UserHandle.CURRENT, 0), 0, intent, 0, null);
Resources r = mContext.getResources();
final CharSequence title;
@@ -735,32 +727,30 @@
}
if (mTetheredNotificationBuilder == null) {
- mTetheredNotificationBuilder = new Notification.Builder(mContext,
- SystemNotificationChannels.NETWORK_STATUS);
+ mTetheredNotificationBuilder = new Notification.Builder(mContext, channel.getId());
mTetheredNotificationBuilder.setWhen(0)
.setOngoing(true)
.setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
+ android.R.color.system_notification_accent_color))
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setCategory(Notification.CATEGORY_STATUS);
}
- mTetheredNotificationBuilder.setSmallIcon(icon)
+ mTetheredNotificationBuilder.setSmallIcon(id)
.setContentTitle(title)
.setContentText(message)
.setContentIntent(pi);
mLastNotificationId = id;
- notificationManager.notifyAsUser(null, mLastNotificationId,
- mTetheredNotificationBuilder.buildInto(new Notification()), UserHandle.ALL);
+ notificationManager.notify(null, mLastNotificationId, mTetheredNotificationBuilder.build());
}
@VisibleForTesting
protected void clearTetheredNotification() {
NotificationManager notificationManager =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
+ .getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null && mLastNotificationId != 0) {
- notificationManager.cancelAsUser(null, mLastNotificationId,
- UserHandle.ALL);
+ notificationManager.cancel(null, mLastNotificationId);
mLastNotificationId = 0;
}
}
@@ -785,6 +775,9 @@
} else if (action.equals(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)) {
mLog.log("OBSERVED user restrictions changed");
handleUserRestrictionAction();
+ } else if (action.equals(ACTION_RESTRICT_BACKGROUND_CHANGED)) {
+ mLog.log("OBSERVED data saver changed");
+ handleDataSaverChanged();
}
}
@@ -895,6 +888,20 @@
private void handleUserRestrictionAction() {
mTetheringRestriction.onUserRestrictionsChanged();
}
+
+ private void handleDataSaverChanged() {
+ final ConnectivityManager connMgr = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ final boolean isDataSaverEnabled = connMgr.getRestrictBackgroundStatus()
+ != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+
+ if (mDataSaverEnabled == isDataSaverEnabled) return;
+
+ mDataSaverEnabled = isDataSaverEnabled;
+ if (mDataSaverEnabled) {
+ untetherAll();
+ }
+ }
}
@VisibleForTesting
@@ -1992,15 +1999,6 @@
mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, error));
- try {
- // Notify that we're tethering (or not) this interface.
- // This is how data saver for instance knows if the user explicitly
- // turned on tethering (thus keeping us from being in data saver mode).
- mPolicyManager.onTetheringChanged(iface, state == IpServer.STATE_TETHERED);
- } catch (RemoteException e) {
- // Not really very much we can do here.
- }
-
// If TetherMasterSM is in ErrorState, TetherMasterSM stays there.
// Thus we give a chance for TetherMasterSM to recover to InitialState
// by sending CMD_CLEAR_ERROR
@@ -2064,7 +2062,7 @@
mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
final TetherState tetherState = new TetherState(
- new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mStatsService,
+ new IpServer(iface, mLooper, interfaceType, mLog, mNetd,
makeControlCallback(), mConfig.enableLegacyDhcpServer,
mDeps.getIpServerDependencies()));
mTetherStates.put(iface, tetherState);
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 397ba8a..068c346 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -21,30 +21,19 @@
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
-import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
-import static com.android.internal.R.array.config_tether_bluetooth_regexs;
-import static com.android.internal.R.array.config_tether_dhcp_range;
-import static com.android.internal.R.array.config_tether_upstream_types;
-import static com.android.internal.R.array.config_tether_usb_regexs;
-import static com.android.internal.R.array.config_tether_wifi_p2p_regexs;
-import static com.android.internal.R.array.config_tether_wifi_regexs;
-import static com.android.internal.R.bool.config_tether_upstream_automatic;
-import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;
-import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
-
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.TetheringConfigurationParcel;
import android.net.util.SharedLog;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.networkstack.tethering.R;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -84,6 +73,12 @@
private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
+ /**
+ * Use the old dnsmasq DHCP server for tethering instead of the framework implementation.
+ */
+ public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER =
+ "tether_enable_legacy_dhcp_server";
+
public final String[] tetherableUsbRegexs;
public final String[] tetherableWifiRegexs;
public final String[] tetherableWifiP2pRegexs;
@@ -107,27 +102,30 @@
activeDataSubId = id;
Resources res = getResources(ctx, activeDataSubId);
- tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs);
+ tetherableUsbRegexs = getResourceStringArray(res, R.array.config_tether_usb_regexs);
// TODO: Evaluate deleting this altogether now that Wi-Fi always passes
// us an interface name. Careful consideration needs to be given to
// implications for Settings and for provisioning checks.
- tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs);
- tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs);
- tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
+ tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs);
+ tetherableWifiP2pRegexs = getResourceStringArray(
+ res, R.array.config_tether_wifi_p2p_regexs);
+ tetherableBluetoothRegexs = getResourceStringArray(
+ res, R.array.config_tether_bluetooth_regexs);
isDunRequired = checkDunRequired(ctx);
- chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic);
+ chooseUpstreamAutomatically = getResourceBoolean(
+ res, R.bool.config_tether_upstream_automatic);
preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
legacyDhcpRanges = getLegacyDhcpRanges(res);
defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
- enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx);
+ enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
- provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);
+ provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app);
provisioningAppNoUi = getProvisioningAppNoUi(res);
provisioningCheckPeriod = getResourceInteger(res,
- config_mobile_hotspot_provision_check_period,
+ R.integer.config_mobile_hotspot_provision_check_period,
0 /* No periodic re-check */);
configLog.log(toString());
@@ -242,7 +240,7 @@
}
private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
- final int[] ifaceTypes = res.getIntArray(config_tether_upstream_types);
+ final int[] ifaceTypes = res.getIntArray(R.array.config_tether_upstream_types);
final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
for (int i : ifaceTypes) {
switch (i) {
@@ -292,7 +290,7 @@
}
private static String[] getLegacyDhcpRanges(Resources res) {
- final String[] fromResource = getResourceStringArray(res, config_tether_dhcp_range);
+ final String[] fromResource = getResourceStringArray(res, R.array.config_tether_dhcp_range);
if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
return fromResource;
}
@@ -301,7 +299,7 @@
private static String getProvisioningAppNoUi(Resources res) {
try {
- return res.getString(config_mobile_hotspot_provision_app_no_ui);
+ return res.getString(R.string.config_mobile_hotspot_provision_app_no_ui);
} catch (Resources.NotFoundException e) {
return "";
}
@@ -332,10 +330,14 @@
}
}
- private static boolean getEnableLegacyDhcpServer(Context ctx) {
- final ContentResolver cr = ctx.getContentResolver();
- final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
- return intVal != 0;
+ private boolean getEnableLegacyDhcpServer(final Resources res) {
+ return getResourceBoolean(res, R.bool.config_tether_enable_legacy_dhcp_server)
+ || getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER);
+ }
+
+ @VisibleForTesting
+ protected boolean getDeviceConfigBoolean(final String name) {
+ return DeviceConfig.getBoolean(NAMESPACE_CONNECTIVITY, name, false /** defaultValue */);
}
private Resources getResources(Context ctx, int subId) {
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
index b16b329..e019c3a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -16,18 +16,15 @@
package com.android.server.connectivity.tethering;
+import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
import android.net.NetworkRequest;
import android.net.ip.IpServer;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.IBinder;
-import android.os.INetworkManagementService;
import android.os.Looper;
-import android.os.ServiceManager;
import com.android.internal.util.StateMachine;
@@ -97,33 +94,6 @@
}
/**
- * Get a reference to INetworkManagementService to registerTetheringStatsProvider from
- * OffloadController. Note: This should be removed soon by Usage refactor work in R
- * development cycle.
- */
- public INetworkManagementService getINetworkManagementService() {
- return INetworkManagementService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
- }
-
- /**
- * Get a reference to INetworkStatsService to force update tethering usage.
- * Note: This should be removed in R development cycle.
- */
- public INetworkStatsService getINetworkStatsService() {
- return INetworkStatsService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
- }
-
- /**
- * Get a reference to INetworkPolicyManager to be used by tethering.
- */
- public INetworkPolicyManager getINetworkPolicyManager() {
- return INetworkPolicyManager.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
- }
-
- /**
* Get a reference to INetd to be used by tethering.
*/
public INetd getINetd(Context context) {
@@ -140,4 +110,9 @@
* Get Context of TetheringSerice.
*/
public abstract Context getContext();
+
+ /**
+ * Get a reference to BluetoothAdapter to be used by tethering.
+ */
+ public abstract BluetoothAdapter getBluetoothAdapter();
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
index d5cdd8a..4dd6830 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
@@ -22,6 +22,8 @@
import android.net.RouteInfo;
import android.net.util.InterfaceSet;
+import com.android.net.module.util.NetUtils;
+
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -85,7 +87,7 @@
private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) {
final RouteInfo ri = (lp != null)
- ? RouteInfo.selectBestRoute(lp.getAllRoutes(), dst)
+ ? NetUtils.selectBestRoute(lp.getAllRoutes(), dst)
: null;
return (ri != null) ? ri.getInterface() : null;
}
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 e4e4a09..cb7d392 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -24,6 +24,7 @@
import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.net.IIntResultListener;
@@ -42,7 +43,6 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.Settings;
@@ -363,7 +363,7 @@
IBinder connector;
try {
final long before = System.currentTimeMillis();
- while ((connector = ServiceManager.getService(
+ while ((connector = (IBinder) mContext.getSystemService(
Context.NETWORK_STACK_SERVICE)) == null) {
if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) {
Log.wtf(TAG, "Timeout, fail to get INetworkStackConnector");
@@ -377,6 +377,11 @@
}
return INetworkStackConnector.Stub.asInterface(connector);
}
+
+ @Override
+ public BluetoothAdapter getBluetoothAdapter() {
+ return BluetoothAdapter.getDefaultAdapter();
+ }
};
}
return mDeps;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 5692a6f..2875f71 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -23,6 +23,8 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -215,19 +217,28 @@
mLog.e("registerMobileNetworkRequest() already registered");
return;
}
- // The following use of the legacy type system cannot be removed until
- // after upstream selection no longer finds networks by legacy type.
- // See also http://b/34364553 .
- final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
- final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder()
- .setCapabilities(networkCapabilitiesForType(legacyType))
- .build();
+ final NetworkRequest mobileUpstreamRequest;
+ if (mDunRequired) {
+ mobileUpstreamRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_DUN)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ } else {
+ mobileUpstreamRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ }
// The existing default network and DUN callbacks will be notified.
// Therefore, to avoid duplicate notifications, we only register a no-op.
mMobileNetworkCallback = new UpstreamNetworkCallback(CALLBACK_MOBILE_REQUEST);
+ // The following use of the legacy type system cannot be removed until
+ // upstream selection no longer finds networks by legacy type.
+ // See also http://b/34364553 .
+ final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
+
// TODO: Change the timeout from 0 (no onUnavailable callback) to some
// moderate callback timeout. This might be useful for updating some UI.
// Additionally, we log a message to aid in any subsequent debugging.
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 65a0ac1..1f50b6b 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -52,7 +52,6 @@
import static org.mockito.Mockito.when;
import android.net.INetd;
-import android.net.INetworkStatsService;
import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -99,7 +98,6 @@
private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
@Mock private INetd mNetd;
- @Mock private INetworkStatsService mStatsService;
@Mock private IpServer.Callback mCallback;
@Mock private SharedLog mSharedLog;
@Mock private IDhcpServer mDhcpServer;
@@ -139,13 +137,13 @@
mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH;
}
mIpServer = new IpServer(
- IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mStatsService,
+ IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
mCallback, usingLegacyDhcp, mDependencies);
mIpServer.start();
// Starting the state machine always puts us in a consistent state and notifies
// the rest of the world that we've changed from an unknown to available state.
mLooper.dispatchAll();
- reset(mNetd, mStatsService, mCallback);
+ reset(mNetd, mCallback);
when(mRaDaemon.start()).thenReturn(true);
}
@@ -162,7 +160,7 @@
if (upstreamIface != null) {
dispatchTetherConnectionChanged(upstreamIface);
}
- reset(mNetd, mStatsService, mCallback);
+ reset(mNetd, mCallback);
}
@Before public void setUp() throws Exception {
@@ -173,13 +171,13 @@
@Test
public void startsOutAvailable() {
mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
- mNetd, mStatsService, mCallback, false /* usingLegacyDhcp */, mDependencies);
+ mNetd, mCallback, false /* usingLegacyDhcp */, mDependencies);
mIpServer.start();
mLooper.dispatchAll();
verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mCallback, mNetd, mStatsService);
+ verifyNoMoreInteractions(mCallback, mNetd);
}
@Test
@@ -198,7 +196,7 @@
// None of these commands should trigger us to request action from
// the rest of the system.
dispatchCommand(command);
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
}
@@ -210,7 +208,7 @@
verify(mCallback).updateInterfaceState(
mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -228,7 +226,7 @@
mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -236,7 +234,7 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, null);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNetd, mStatsService, mCallback);
+ InOrder inOrder = inOrder(mNetd, mCallback);
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
@@ -245,7 +243,7 @@
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -265,7 +263,7 @@
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -285,7 +283,7 @@
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -298,7 +296,7 @@
InOrder inOrder = inOrder(mNetd);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -306,13 +304,12 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mStatsService);
- inOrder.verify(mStatsService).forceUpdate();
+ InOrder inOrder = inOrder(mNetd);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -322,12 +319,10 @@
doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mStatsService);
- inOrder.verify(mStatsService).forceUpdate();
+ InOrder inOrder = inOrder(mNetd);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mStatsService).forceUpdate();
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@@ -340,13 +335,11 @@
IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mStatsService);
- inOrder.verify(mStatsService).forceUpdate();
+ InOrder inOrder = inOrder(mNetd);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mStatsService).forceUpdate();
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@@ -356,8 +349,7 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNetd, mStatsService, mCallback);
- inOrder.verify(mStatsService).forceUpdate();
+ InOrder inOrder = inOrder(mNetd, mCallback);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
@@ -368,7 +360,7 @@
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -435,11 +427,11 @@
public void ignoresDuplicateUpstreamNotifications() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
for (int i = 0; i < 5; i++) {
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 66eba9a..4f07461 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -22,6 +22,7 @@
import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -39,7 +40,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.util.SharedLog;
@@ -49,18 +49,16 @@
import android.os.ResultReceiver;
import android.os.SystemProperties;
import android.os.test.TestLooper;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.telephony.CarrierConfigManager;
-import android.test.mock.MockContentResolver;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.R;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.R;
import org.junit.After;
import org.junit.Before;
@@ -94,7 +92,6 @@
private final PersistableBundle mCarrierConfig = new PersistableBundle();
private final TestLooper mLooper = new TestLooper();
private Context mMockContext;
- private MockContentResolver mContentResolver;
private TestStateMachine mSM;
private WrappedEntitlementManager mEnMgr;
@@ -110,11 +107,6 @@
public Resources getResources() {
return mResources;
}
-
- @Override
- public ContentResolver getContentResolver() {
- return mContentResolver;
- }
}
public class WrappedEntitlementManager extends EntitlementManager {
@@ -151,28 +143,33 @@
MockitoAnnotations.initMocks(this);
mMockingSession = mockitoSession()
.initMocks(this)
- .spyStatic(SystemProperties.class)
+ .mockStatic(SystemProperties.class)
+ .mockStatic(DeviceConfig.class)
.strictness(Strictness.WARN)
.startMocking();
// Don't disable tethering provisioning unless requested.
doReturn(false).when(
() -> SystemProperties.getBoolean(
eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean()));
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
when(mResources.getStringArray(R.array.config_tether_dhcp_range))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getIntArray(R.array.config_tether_upstream_types))
- .thenReturn(new int[0]);
+ .thenReturn(new int[0]);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
+ when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
when(mLog.forSubComponent(anyString())).thenReturn(mLog);
- mContentResolver = new MockContentResolver();
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
mMockContext = new MockContext(mContext);
mSM = new TestStateMachine();
mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE);
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 7886ca6..7e62e5a 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -16,21 +16,26 @@
package com.android.server.connectivity.tethering;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_IFACE;
-import static android.net.NetworkStats.STATS_PER_UID;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStats.UID_TETHERING;
import static android.net.RouteInfo.RTN_UNICAST;
-import static android.net.TrafficStats.UID_TETHERING;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_IFACE;
+import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_UID;
import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
import static com.android.testutils.MiscAssertsKt.assertContainsAll;
import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.NetworkStatsUtilsKt.orderInsensitiveEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
@@ -39,11 +44,14 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
+import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.ITetheringStatsProvider;
@@ -51,10 +59,12 @@
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
import android.net.RouteInfo;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
import android.net.util.SharedLog;
import android.os.Handler;
-import android.os.INetworkManagementService;
import android.os.Looper;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -97,11 +107,13 @@
@Mock private OffloadHardwareInterface mHardware;
@Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
- @Mock private INetworkManagementService mNMService;
+ @Mock private NetworkStatsManager mStatsManager;
+ @Mock private NetworkStatsProviderCallback mTetherStatsProviderCb;
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
ArgumentCaptor.forClass(ArrayList.class);
- private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
- ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
+ private final ArgumentCaptor<OffloadController.OffloadTetheringStatsProvider>
+ mTetherStatsProviderCaptor =
+ ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class);
private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor =
ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class);
private MockContentResolver mContentResolver;
@@ -114,6 +126,8 @@
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mContext.getContentResolver()).thenReturn(mContentResolver);
FakeSettingsProvider.clearSettingsProvider();
+ when(mStatsManager.registerNetworkStatsProvider(anyString(), any()))
+ .thenReturn(mTetherStatsProviderCb);
}
@After public void tearDown() throws Exception {
@@ -139,9 +153,9 @@
private OffloadController makeOffloadController() throws Exception {
OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()),
- mHardware, mContentResolver, mNMService, new SharedLog("test"));
- verify(mNMService).registerTetheringStatsProvider(
- mTetherStatsProviderCaptor.capture(), anyString());
+ mHardware, mContentResolver, mStatsManager, new SharedLog("test"));
+ verify(mStatsManager).registerNetworkStatsProvider(anyString(),
+ mTetherStatsProviderCaptor.capture());
return offload;
}
@@ -384,12 +398,11 @@
inOrder.verifyNoMoreInteractions();
}
- private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) {
- assertEquals(iface, entry.iface);
- assertEquals(stats.rxBytes, entry.rxBytes);
- assertEquals(stats.txBytes, entry.txBytes);
- assertEquals(SET_DEFAULT, entry.set);
- assertEquals(TAG_NONE, entry.tag);
+ private static @NonNull Entry buildTestEntry(@NonNull OffloadController.StatsType how,
+ @NonNull String iface, long rxBytes, long txBytes) {
+ return new Entry(iface, how == STATS_PER_IFACE ? UID_ALL : UID_TETHERING, SET_DEFAULT,
+ TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, 0L,
+ txBytes, 0L, 0L);
}
@Test
@@ -400,19 +413,16 @@
final OffloadController offload = makeOffloadController();
offload.start();
+ final OffloadController.OffloadTetheringStatsProvider provider =
+ mTetherStatsProviderCaptor.getValue();
+
final String ethernetIface = "eth1";
final String mobileIface = "rmnet_data0";
- ForwardedStats ethernetStats = new ForwardedStats();
- ethernetStats.rxBytes = 12345;
- ethernetStats.txBytes = 54321;
-
- ForwardedStats mobileStats = new ForwardedStats();
- mobileStats.rxBytes = 999;
- mobileStats.txBytes = 99999;
-
- when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
- when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);
+ when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
+ new ForwardedStats(12345, 54321));
+ when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(
+ new ForwardedStats(999, 99999));
InOrder inOrder = inOrder(mHardware);
@@ -432,10 +442,35 @@
// Expect that we fetch stats from the previous upstream.
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(mobileIface));
- ethernetStats = new ForwardedStats();
- ethernetStats.rxBytes = 100000;
- ethernetStats.txBytes = 100000;
- when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
+ // Verify that the fetched stats are stored.
+ final NetworkStats ifaceStats = provider.getTetherStats(STATS_PER_IFACE);
+ final NetworkStats uidStats = provider.getTetherStats(STATS_PER_UID);
+ final NetworkStats expectedIfaceStats = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 12345, 54321));
+
+ final NetworkStats expectedUidStats = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 12345, 54321));
+
+ assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStats));
+ assertTrue(orderInsensitiveEquals(expectedUidStats, uidStats));
+
+ final ArgumentCaptor<NetworkStats> ifaceStatsCaptor = ArgumentCaptor.forClass(
+ NetworkStats.class);
+ final ArgumentCaptor<NetworkStats> uidStatsCaptor = ArgumentCaptor.forClass(
+ NetworkStats.class);
+
+ // Force pushing stats update to verify the stats reported.
+ provider.pushTetherStats();
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(),
+ ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
+ assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStatsCaptor.getValue()));
+ assertTrue(orderInsensitiveEquals(expectedUidStats, uidStatsCaptor.getValue()));
+
+
+ when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
+ new ForwardedStats(100000, 100000));
offload.setUpstreamLinkProperties(null);
// Expect that we first clear the HAL's upstream parameters.
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
@@ -443,37 +478,38 @@
// Expect that we fetch stats from the previous upstream.
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));
- ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
- NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE);
- NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID);
- waitForIdle();
// There is no current upstream, so no stats are fetched.
inOrder.verify(mHardware, never()).getForwardedStats(any());
inOrder.verifyNoMoreInteractions();
- assertEquals(2, stats.size());
- assertEquals(2, perUidStats.size());
+ // Verify that the stored stats is accumulated.
+ final NetworkStats ifaceStatsAccu = provider.getTetherStats(STATS_PER_IFACE);
+ final NetworkStats uidStatsAccu = provider.getTetherStats(STATS_PER_UID);
+ final NetworkStats expectedIfaceStatsAccu = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 112345, 154321));
- NetworkStats.Entry entry = null;
- for (int i = 0; i < stats.size(); i++) {
- assertEquals(UID_ALL, stats.getValues(i, entry).uid);
- assertEquals(UID_TETHERING, perUidStats.getValues(i, entry).uid);
- }
+ final NetworkStats expectedUidStatsAccu = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 112345, 154321));
- int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1;
- int mobilePosition = 1 - ethernetPosition;
+ assertTrue(orderInsensitiveEquals(expectedIfaceStatsAccu, ifaceStatsAccu));
+ assertTrue(orderInsensitiveEquals(expectedUidStatsAccu, uidStatsAccu));
- entry = stats.getValues(mobilePosition, entry);
- assertNetworkStats(mobileIface, mobileStats, entry);
- entry = perUidStats.getValues(mobilePosition, entry);
- assertNetworkStats(mobileIface, mobileStats, entry);
+ // Verify that only diff of stats is reported.
+ reset(mTetherStatsProviderCb);
+ provider.pushTetherStats();
+ final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 0, 0))
+ .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 100000, 100000));
- ethernetStats.rxBytes = 12345 + 100000;
- ethernetStats.txBytes = 54321 + 100000;
- entry = stats.getValues(ethernetPosition, entry);
- assertNetworkStats(ethernetIface, ethernetStats, entry);
- entry = perUidStats.getValues(ethernetPosition, entry);
- assertNetworkStats(ethernetIface, ethernetStats, entry);
+ final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 0, 0))
+ .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 100000, 100000));
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(),
+ ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
+ assertTrue(orderInsensitiveEquals(expectedIfaceStatsDiff, ifaceStatsCaptor.getValue()));
+ assertTrue(orderInsensitiveEquals(expectedUidStatsDiff, uidStatsCaptor.getValue()));
}
@Test
@@ -493,19 +529,19 @@
lp.setInterfaceName(ethernetIface);
offload.setUpstreamLinkProperties(lp);
- ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
+ AbstractNetworkStatsProvider provider = mTetherStatsProviderCaptor.getValue();
final InOrder inOrder = inOrder(mHardware);
when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true);
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);
// Applying an interface quota to the current upstream immediately sends it to the hardware.
- provider.setInterfaceQuota(ethernetIface, ethernetLimit);
+ provider.setLimit(ethernetIface, ethernetLimit);
waitForIdle();
inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit);
inOrder.verifyNoMoreInteractions();
// Applying an interface quota to another upstream does not take any immediate action.
- provider.setInterfaceQuota(mobileIface, mobileLimit);
+ provider.setLimit(mobileIface, mobileLimit);
waitForIdle();
inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
@@ -518,7 +554,7 @@
// Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set
// to Long.MAX_VALUE.
- provider.setInterfaceQuota(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
+ provider.setLimit(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
waitForIdle();
inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE);
@@ -526,7 +562,7 @@
when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false);
lp.setInterfaceName(ethernetIface);
offload.setUpstreamLinkProperties(lp);
- provider.setInterfaceQuota(mobileIface, mobileLimit);
+ provider.setLimit(mobileIface, mobileLimit);
waitForIdle();
inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
@@ -535,7 +571,7 @@
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false);
lp.setInterfaceName(mobileIface);
offload.setUpstreamLinkProperties(lp);
- provider.setInterfaceQuota(mobileIface, mobileLimit);
+ provider.setLimit(mobileIface, mobileLimit);
waitForIdle();
inOrder.verify(mHardware).getForwardedStats(ethernetIface);
inOrder.verify(mHardware).stopOffloadControl();
@@ -551,7 +587,7 @@
OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
callback.onStoppedLimitReached();
- verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
}
@Test
@@ -654,9 +690,10 @@
// Verify forwarded stats behaviour.
verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
+ // TODO: verify the exact stats reported.
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
+ verifyNoMoreInteractions(mTetherStatsProviderCb);
verifyNoMoreInteractions(mHardware);
- verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
- verifyNoMoreInteractions(mNMService);
}
@Test
@@ -719,8 +756,8 @@
// Verify forwarded stats behaviour.
verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
- verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
- verifyNoMoreInteractions(mNMService);
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
+ verifyNoMoreInteractions(mTetherStatsProviderCb);
// TODO: verify local prefixes and downstreams are also pushed to the HAL.
verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index 7799da4..3635964 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -21,40 +21,38 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
-import static com.android.internal.R.array.config_tether_bluetooth_regexs;
-import static com.android.internal.R.array.config_tether_dhcp_range;
-import static com.android.internal.R.array.config_tether_upstream_types;
-import static com.android.internal.R.array.config_tether_usb_regexs;
-import static com.android.internal.R.array.config_tether_wifi_regexs;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.util.SharedLog;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.telephony.TelephonyManager;
-import android.test.mock.MockContentResolver;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.R;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.Arrays;
import java.util.Iterator;
@@ -69,9 +67,10 @@
@Mock private TelephonyManager mTelephonyManager;
@Mock private Resources mResources;
@Mock private Resources mResourcesForSubId;
- private MockContentResolver mContentResolver;
private Context mMockContext;
private boolean mHasTelephonyManager;
+ private boolean mEnableLegacyDhcpServer;
+ private MockitoSession mMockingSession;
private class MockTetheringConfiguration extends TetheringConfiguration {
MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
@@ -101,32 +100,44 @@
}
return super.getSystemService(name);
}
-
- @Override
- public ContentResolver getContentResolver() {
- return mContentResolver;
- }
}
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- when(mResources.getStringArray(config_tether_dhcp_range)).thenReturn(new String[0]);
- when(mResources.getStringArray(config_tether_usb_regexs)).thenReturn(new String[0]);
- when(mResources.getStringArray(config_tether_wifi_regexs))
+ // TODO: use a dependencies class instead of mock statics.
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(DeviceConfig.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
+
+ when(mResources.getStringArray(R.array.config_tether_dhcp_range)).thenReturn(
+ new String[0]);
+ when(mResources.getStringArray(R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
+ when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
.thenReturn(new String[]{ "test_wlan\\d" });
- when(mResources.getStringArray(config_tether_bluetooth_regexs)).thenReturn(new String[0]);
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
- when(mResources.getStringArray(config_mobile_hotspot_provision_app))
+ when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)).thenReturn(
+ new String[0]);
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+ when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
.thenReturn(new String[0]);
- mContentResolver = new MockContentResolver();
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
mHasTelephonyManager = true;
mMockContext = new MockContext(mContext);
+ mEnableLegacyDhcpServer = false;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mMockingSession.finishMocking();
}
private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
legacyTetherUpstreamTypes);
return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
}
@@ -210,7 +221,7 @@
@Test
public void testNoDefinedUpstreamTypesAddsEthernet() {
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{});
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[]{});
when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
final TetheringConfiguration cfg = new TetheringConfiguration(
@@ -232,7 +243,7 @@
@Test
public void testDefinedUpstreamTypesSansEthernetAddsEthernet() {
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI});
when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
@@ -250,7 +261,7 @@
@Test
public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() {
- when(mResources.getIntArray(config_tether_upstream_types))
+ when(mResources.getIntArray(R.array.config_tether_upstream_types))
.thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI});
when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
@@ -268,19 +279,38 @@
@Test
public void testNewDhcpServerDisabled() {
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ true);
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
- final TetheringConfiguration cfg = new TetheringConfiguration(
- mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
- assertTrue(cfg.enableLegacyDhcpServer);
+ final TetheringConfiguration enableByRes =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertTrue(enableByRes.enableLegacyDhcpServer);
+
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
+ doReturn(true).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
+
+ final TetheringConfiguration enableByDevConfig =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertTrue(enableByDevConfig.enableLegacyDhcpServer);
}
@Test
public void testNewDhcpServerEnabled() {
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
- final TetheringConfiguration cfg = new TetheringConfiguration(
- mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ final TetheringConfiguration cfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+
assertFalse(cfg.enableLegacyDhcpServer);
}
@@ -299,16 +329,17 @@
private void setUpResourceForSubId() {
when(mResourcesForSubId.getStringArray(
- config_tether_dhcp_range)).thenReturn(new String[0]);
+ R.array.config_tether_dhcp_range)).thenReturn(new String[0]);
when(mResourcesForSubId.getStringArray(
- config_tether_usb_regexs)).thenReturn(new String[0]);
+ R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
when(mResourcesForSubId.getStringArray(
- config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
+ R.array.config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
when(mResourcesForSubId.getStringArray(
- config_tether_bluetooth_regexs)).thenReturn(new String[0]);
- when(mResourcesForSubId.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
+ R.array.config_tether_bluetooth_regexs)).thenReturn(new String[0]);
+ when(mResourcesForSubId.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
+ new int[0]);
when(mResourcesForSubId.getStringArray(
- config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
+ R.array.config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
}
}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 7af48a8..5910624 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -19,6 +19,9 @@
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
@@ -35,7 +38,6 @@
import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static org.junit.Assert.assertArrayEquals;
@@ -52,6 +54,7 @@
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
@@ -59,6 +62,8 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.app.usage.NetworkStatsManager;
+import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -67,9 +72,8 @@
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
import android.net.ITetheringEventCallback;
import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
@@ -98,7 +102,6 @@
import android.net.wifi.p2p.WifiP2pManager;
import android.os.Bundle;
import android.os.Handler;
-import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.RemoteException;
@@ -119,6 +122,7 @@
import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.R;
import org.junit.After;
import org.junit.Before;
@@ -132,6 +136,7 @@
import java.net.Inet6Address;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Vector;
@RunWith(AndroidJUnit4.class)
@@ -150,9 +155,7 @@
@Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
- @Mock private INetworkManagementService mNMService;
- @Mock private INetworkStatsService mStatsService;
- @Mock private INetworkPolicyManager mPolicyManager;
+ @Mock private NetworkStatsManager mStatsManager;
@Mock private OffloadHardwareInterface mOffloadHardwareInterface;
@Mock private Resources mResources;
@Mock private TelephonyManager mTelephonyManager;
@@ -166,6 +169,7 @@
@Mock private INetd mNetd;
@Mock private UserManager mUserManager;
@Mock private NetworkRequest mNetworkRequest;
+ @Mock private ConnectivityManager mCm;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -216,6 +220,8 @@
if (Context.USB_SERVICE.equals(name)) return mUsbManager;
if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
if (Context.USER_SERVICE.equals(name)) return mUserManager;
+ if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager;
+ if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
return super.getSystemService(name);
}
@@ -224,6 +230,11 @@
if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE;
return super.getSystemServiceName(serviceClass);
}
+
+ @Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ return mContext;
+ }
}
public class MockIpServerDependencies extends IpServer.Dependencies {
@@ -265,6 +276,11 @@
}
@Override
+ protected boolean getDeviceConfigBoolean(final String name) {
+ return false;
+ }
+
+ @Override
protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
return mResources;
}
@@ -323,21 +339,6 @@
}
@Override
- public INetworkManagementService getINetworkManagementService() {
- return mNMService;
- }
-
- @Override
- public INetworkStatsService getINetworkStatsService() {
- return mStatsService;
- }
-
- @Override
- public INetworkPolicyManager getINetworkPolicyManager() {
- return mPolicyManager;
- }
-
- @Override
public INetd getINetd(Context context) {
return mNetd;
}
@@ -351,6 +352,12 @@
public Context getContext() {
return mServiceContext;
}
+
+ @Override
+ public BluetoothAdapter getBluetoothAdapter() {
+ // TODO: add test for bluetooth tethering.
+ return null;
+ }
}
private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
@@ -409,32 +416,33 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range))
+ when(mResources.getStringArray(R.array.config_tether_dhcp_range))
.thenReturn(new String[0]);
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs))
+ when(mResources.getStringArray(R.array.config_tether_usb_regexs))
.thenReturn(new String[] { "test_rndis\\d" });
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs))
+ when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
.thenReturn(new String[]{ "test_wlan\\d" });
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+ when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
.thenReturn(new String[]{ "test_p2p-p2p\\d-.*" });
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
+ when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
.thenReturn(new String[0]);
- when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
- .thenReturn(new int[0]);
- when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
- .thenReturn(false);
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+ when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(false);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
when(mNetd.interfaceGetList())
.thenReturn(new String[] {
TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME});
+ when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
mInterfaceConfiguration = new InterfaceConfigurationParcel();
mInterfaceConfiguration.flags = new String[0];
when(mRouterAdvertisementDaemon.start())
.thenReturn(true);
mServiceContext = new TestContext(mContext);
+ when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null);
mContentResolver = new MockContentResolver(mServiceContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
mIntents = new Vector<>();
mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -445,7 +453,7 @@
mServiceContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(ACTION_TETHER_STATE_CHANGED));
mTethering = makeTethering();
- verify(mNMService).registerTetheringStatsProvider(any(), anyString());
+ verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
verify(mNetd).registerUnsolicitedEventListener(any());
final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor =
ArgumentCaptor.forClass(PhoneStateListener.class);
@@ -489,13 +497,15 @@
p2pInfo.groupFormed = isGroupFormed;
p2pInfo.isGroupOwner = isGroupOwner;
- WifiP2pGroup group = new WifiP2pGroup();
- group.setIsGroupOwner(isGroupOwner);
- group.setInterface(ifname);
+ WifiP2pGroup group = mock(WifiP2pGroup.class);
+ when(group.isGroupOwner()).thenReturn(isGroupOwner);
+ when(group.getInterface()).thenReturn(ifname);
- final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
- intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, p2pInfo);
- intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, group);
+ final Intent intent = mock(Intent.class);
+ when(intent.getAction()).thenReturn(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ when(intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO)).thenReturn(p2pInfo);
+ when(intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)).thenReturn(group);
+
mServiceContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL,
P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST);
}
@@ -686,7 +696,8 @@
@Test
public void workingMobileUsbTethering_IPv4LegacyDhcp() {
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ true);
sendConfigurationChanged();
final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
@@ -774,8 +785,7 @@
@Test
public void configTetherUpstreamAutomaticIgnoresConfigTetherUpstreamTypes() throws Exception {
- when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
- .thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
sendConfigurationChanged();
// Setup IPv6
@@ -1303,7 +1313,7 @@
private void workingWifiP2pGroupOwnerLegacyMode(
boolean emulateInterfaceStatusChanged) throws Exception {
// change to legacy mode and update tethering information by chaning SIM
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+ when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
.thenReturn(new String[]{});
final int fakeSubId = 1234;
mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId);
@@ -1341,6 +1351,50 @@
workingWifiP2pGroupClient(false);
}
+ private void setDataSaverEnabled(boolean enabled) {
+ final Intent intent = new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED);
+ mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+ final int status = enabled ? RESTRICT_BACKGROUND_STATUS_ENABLED
+ : RESTRICT_BACKGROUND_STATUS_DISABLED;
+ when(mCm.getRestrictBackgroundStatus()).thenReturn(status);
+ mLooper.dispatchAll();
+ }
+
+ @Test
+ public void testDataSaverChanged() {
+ // Start Tethering.
+ final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
+ runUsbTethering(upstreamState);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+ // Data saver is ON.
+ setDataSaverEnabled(true);
+ // Verify that tethering should be disabled.
+ verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ mTethering.interfaceRemoved(TEST_USB_IFNAME);
+ mLooper.dispatchAll();
+ assertEquals(mTethering.getTetheredIfaces(), new String[0]);
+ reset(mUsbManager);
+
+ runUsbTethering(upstreamState);
+ // Verify that user can start tethering again without turning OFF data saver.
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+
+ // If data saver is keep ON with change event, tethering should not be OFF this time.
+ setDataSaverEnabled(true);
+ verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+
+ // If data saver is turned OFF, it should not change tethering.
+ setDataSaverEnabled(false);
+ verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+ }
+
+ private static <T> void assertContains(Collection<T> collection, T element) {
+ assertTrue(element + " not found in " + collection, collection.contains(element));
+ }
+
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
}
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp
index 2a94237..61f8143 100644
--- a/packages/services/PacProcessor/jni/Android.bp
+++ b/packages/services/PacProcessor/jni/Android.bp
@@ -37,4 +37,10 @@
"-Wunused",
"-Wunreachable-code",
],
+ sanitize: {
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
}
diff --git a/proto/Android.bp b/proto/Android.bp
index 65bccbb..7cf6ce7 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -27,3 +27,8 @@
srcs: ["src/metrics_constants/metrics_constants.proto"],
sdk_version: "system_current",
}
+
+filegroup {
+ name: "system-messages-proto-src",
+ srcs: ["src/system_messages.proto"],
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 5eaa80a..c3965c4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -36,8 +36,12 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -54,6 +58,7 @@
import android.view.Display;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowInfo;
import android.view.accessibility.AccessibilityCache;
@@ -90,6 +95,7 @@
private static final String LOG_TAG = "AbstractAccessibilityServiceConnection";
private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000;
+ protected static final String TAKE_SCREENSHOT = "takeScreenshot";
protected final Context mContext;
protected final SystemSupport mSystemSupport;
protected final WindowManagerInternal mWindowManagerService;
@@ -934,6 +940,54 @@
mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
}
+ @Nullable
+ @Override
+ public Bitmap takeScreenshot(int displayId) {
+ synchronized (mLock) {
+ if (!hasRightsToCurrentUserLocked()) {
+ return null;
+ }
+
+ if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
+ return null;
+ }
+ }
+
+ if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+ return null;
+ }
+
+ final Display display = DisplayManagerGlobal.getInstance()
+ .getRealDisplay(displayId);
+ if (display == null) {
+ return null;
+ }
+ final Point displaySize = new Point();
+ display.getRealSize(displaySize);
+
+ final int rotation = display.getRotation();
+ Bitmap screenShot = null;
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+ // TODO (b/145893483): calling new API with the display as a parameter
+ // when surface control supported.
+ screenShot = SurfaceControl.screenshot(crop, displaySize.x, displaySize.y,
+ rotation);
+ if (screenShot != null) {
+ // Optimization for telling the bitmap that all of the pixels are known to be
+ // opaque (false). This is meant as a drawing hint, as in some cases a bitmap
+ // that is known to be opaque can take a faster drawing case than one that may
+ // have non-opaque per-pixel alpha values.
+ screenShot.setHasAlpha(false);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return screenShot;
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -1018,6 +1072,20 @@
}
}
+ /**
+ * Gets windowId of given token.
+ *
+ * @param token The token
+ * @return window id
+ */
+ @Override
+ public int getWindowIdForLeashToken(@NonNull IBinder token) {
+ synchronized (mLock) {
+ // TODO: Add a method to lookup window ID by given leash token.
+ return -1;
+ }
+ }
+
public void resetLocked() {
mSystemSupport.getKeyEventDispatcher().flush(this);
try {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 58d3489..36a9e0eb 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -411,6 +411,7 @@
if (reboundAService) {
onUserStateChangedLocked(userState);
}
+ migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName);
}
}
@@ -811,9 +812,7 @@
userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
userState.setDisplayMagnificationEnabledLocked(false);
- userState.setNavBarMagnificationEnabledLocked(false);
userState.disableShortcutMagnificationLocked();
-
userState.setAutoclickEnabledLocked(false);
userState.mEnabledServices.clear();
userState.mEnabledServices.add(service);
@@ -853,17 +852,20 @@
* navigation area has been clicked.
*
* @param displayId The logical display id.
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
*/
@Override
- public void notifyAccessibilityButtonClicked(int displayId) {
+ public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Caller does not hold permission "
+ android.Manifest.permission.STATUS_BAR_SERVICE);
}
- synchronized (mLock) {
- notifyAccessibilityButtonClickedLocked(displayId);
- }
+ mMainHandler.sendMessage(obtainMessage(
+ AccessibilityManagerService::performAccessibilityShortcutInternal, this,
+ displayId, ACCESSIBILITY_BUTTON, targetName));
}
/**
@@ -1023,6 +1025,7 @@
// the state since the context in which the current user
// state was used has changed since it was inactive.
onUserStateChangedLocked(userState);
+ migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null);
if (announceNewUser) {
// Schedule announcement of the current user if needed.
@@ -1132,66 +1135,6 @@
}
}
- // TODO(a11y shortcut): Remove this function and Use #performAccessibilityShortcutInternal(
- // ACCESSIBILITY_BUTTON) instead, after the new Settings shortcut Ui merged.
- private void notifyAccessibilityButtonClickedLocked(int displayId) {
- final AccessibilityUserState state = getCurrentUserStateLocked();
-
- int potentialTargets = state.isNavBarMagnificationEnabledLocked() ? 1 : 0;
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton) {
- potentialTargets++;
- }
- }
-
- if (potentialTargets == 0) {
- return;
- }
- if (potentialTargets == 1) {
- if (state.isNavBarMagnificationEnabledLocked()) {
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
- displayId));
- return;
- } else {
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton) {
- service.notifyAccessibilityButtonClickedLocked(displayId);
- return;
- }
- }
- }
- } else {
- if (state.getServiceAssignedToAccessibilityButtonLocked() == null
- && !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityTargetsSelection, this,
- displayId, ACCESSIBILITY_BUTTON));
- } else if (state.isNavBarMagnificationEnabledLocked()
- && state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
- displayId));
- return;
- } else {
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
- state.getServiceAssignedToAccessibilityButtonLocked()))) {
- service.notifyAccessibilityButtonClickedLocked(displayId);
- return;
- }
- }
- }
- // The user may have turned off the assigned service or feature
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityTargetsSelection, this,
- displayId, ACCESSIBILITY_BUTTON));
- }
- }
-
private void sendAccessibilityButtonToInputFilter(int displayId) {
synchronized (mLock) {
if (mHasInputFilter && mInputFilter != null) {
@@ -1635,8 +1578,7 @@
if (userState.isDisplayMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
}
- if (userState.isNavBarMagnificationEnabledLocked()
- || userState.isShortcutKeyMagnificationEnabledLocked()) {
+ if (userState.isShortcutMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
}
if (userHasMagnificationServicesLocked(userState)) {
@@ -1886,14 +1828,8 @@
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
0, userState.mUserId) == 1;
- final boolean navBarMagnificationEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
- 0, userState.mUserId) == 1;
- if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())
- || (navBarMagnificationEnabled != userState.isNavBarMagnificationEnabledLocked())) {
+ if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) {
userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
- userState.setNavBarMagnificationEnabledLocked(navBarMagnificationEnabled);
return true;
}
return false;
@@ -2088,8 +2024,7 @@
// displays in one display. It's not a real display and there's no input events for it.
final ArrayList<Display> displays = getValidDisplayList();
if (userState.isDisplayMagnificationEnabledLocked()
- || userState.isNavBarMagnificationEnabledLocked()
- || userState.isShortcutKeyMagnificationEnabledLocked()) {
+ || userState.isShortcutMagnificationEnabledLocked()) {
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
getMagnificationController().register(display.getDisplayId());
@@ -2208,6 +2143,85 @@
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
}
+ /**
+ * 1) Check if the service assigned to accessibility button target sdk version > Q.
+ * If it isn't, remove it from the list and associated setting.
+ * (It happens when an accessibility service package is downgraded.)
+ * 2) Check if an enabled service targeting sdk version > Q and requesting a11y button is
+ * assigned to a shortcut. If it isn't, assigns it to the accessibility button.
+ * (It happens when an enabled accessibility service package is upgraded.)
+ *
+ * @param packageName The package name to check, or {@code null} to check all services.
+ */
+ private void migrateAccessibilityButtonSettingsIfNecessaryLocked(
+ AccessibilityUserState userState, @Nullable String packageName) {
+ final Set<String> buttonTargets =
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+ int lastSize = buttonTargets.size();
+ buttonTargets.removeIf(name -> {
+ if (packageName != null && name != null && !name.contains(packageName)) {
+ return false;
+ }
+ final ComponentName componentName = ComponentName.unflattenFromString(name);
+ if (componentName == null) {
+ return false;
+ }
+ final AccessibilityServiceInfo serviceInfo =
+ userState.getInstalledServiceInfoLocked(componentName);
+ if (serviceInfo == null) {
+ return false;
+ }
+ if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo
+ .targetSdkVersion > Build.VERSION_CODES.Q) {
+ return false;
+ }
+ // A11y services targeting sdk version <= Q should not be in the list.
+ return true;
+ });
+ boolean changed = (lastSize != buttonTargets.size());
+ lastSize = buttonTargets.size();
+
+ final Set<String> shortcutKeyTargets =
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+ userState.mEnabledServices.forEach(componentName -> {
+ if (packageName != null && componentName != null
+ && !packageName.equals(componentName.getPackageName())) {
+ return;
+ }
+ final AccessibilityServiceInfo serviceInfo =
+ userState.getInstalledServiceInfoLocked(componentName);
+ if (serviceInfo == null) {
+ return;
+ }
+ final boolean requestA11yButton = (serviceInfo.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ if (!(serviceInfo.getResolveInfo().serviceInfo.applicationInfo
+ .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton)) {
+ return;
+ }
+ final String serviceName = serviceInfo.getComponentName().flattenToString();
+ if (TextUtils.isEmpty(serviceName)) {
+ return;
+ }
+ if (shortcutKeyTargets.contains(serviceName) || buttonTargets.contains(serviceName)) {
+ return;
+ }
+ // For enabled a11y services targeting sdk version > Q and requesting a11y button should
+ // be assigned to a shortcut.
+ buttonTargets.add(serviceName);
+ });
+ changed |= (lastSize != buttonTargets.size());
+ if (!changed) {
+ return;
+ }
+
+ // Update setting key with new value.
+ persistColonDelimitedSetToSettingLocked(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ userState.mUserId, buttonTargets, str -> str);
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+ }
+
private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) {
int newNonInteractiveUiTimeout = userState.getUserNonInteractiveUiTimeoutLocked();
int newInteractiveUiTimeout = userState.getUserInteractiveUiTimeoutLocked();
@@ -2269,9 +2283,13 @@
* AIDL-exposed method to be called when the accessibility shortcut key is enabled. Requires
* permission to write secure settings, since someone with that permission can enable
* accessibility services themselves.
+ *
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
*/
@Override
- public void performAccessibilityShortcut() {
+ public void performAccessibilityShortcut(String targetName) {
if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
&& (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
!= PackageManager.PERMISSION_GRANTED)) {
@@ -2280,7 +2298,7 @@
}
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::performAccessibilityShortcutInternal, this,
- Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY));
+ Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName));
}
/**
@@ -2288,20 +2306,31 @@
*
* @param shortcutType The shortcut type.
* @param displayId The display id of the accessibility button.
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
*/
private void performAccessibilityShortcutInternal(int displayId,
- @ShortcutType int shortcutType) {
+ @ShortcutType int shortcutType, @Nullable String targetName) {
final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
if (shortcutTargets.isEmpty()) {
Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
return;
}
- // In case there are many targets assigned to the given shortcut.
- if (shortcutTargets.size() > 1) {
- showAccessibilityTargetsSelection(displayId, shortcutType);
- return;
+ // In case the caller specified a target name
+ if (targetName != null) {
+ if (!shortcutTargets.contains(targetName)) {
+ Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
+ return;
+ }
+ } else {
+ // In case there are many targets assigned to the given shortcut.
+ if (shortcutTargets.size() > 1) {
+ showAccessibilityTargetsSelection(displayId, shortcutType);
+ return;
+ }
+ targetName = shortcutTargets.get(0);
}
- final String targetName = shortcutTargets.get(0);
// In case user assigned magnification to the given shortcut.
if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) {
sendAccessibilityButtonToInputFilter(displayId);
@@ -2844,11 +2873,6 @@
private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
- // TODO(a11y shortcut): Remove this setting key, and have a migrate function in
- // Setting provider after new shortcut UI merged.
- private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
-
private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
@@ -2888,8 +2912,6 @@
false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri,
false, this, UserHandle.USER_ALL);
- contentResolver.registerContentObserver(mNavBarMagnificationEnabledUri,
- false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mAutoclickEnabledUri,
false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri,
@@ -2924,8 +2946,7 @@
if (readTouchExplorationEnabledSettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
- } else if (mDisplayMagnificationEnabledUri.equals(uri)
- || mNavBarMagnificationEnabledUri.equals(uri)) {
+ } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
if (readMagnificationEnabledSettingsLocked(userState)) {
onUserStateChangedLocked(userState);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 2032109..7dbec7c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -326,6 +326,19 @@
}
/**
+ * Checks if a service can take screenshot.
+ *
+ * @param service The service requesting access
+ *
+ * @return Whether ot not the service may take screenshot
+ */
+ public boolean canTakeScreenshotLocked(
+ @NonNull AbstractAccessibilityServiceConnection service) {
+ return (service.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT) != 0;
+ }
+
+ /**
* Returns the parent userId of the profile according to the specified userId.
*
* @param userId The userId to check
@@ -426,6 +439,7 @@
return false;
}
}
+ // TODO: Check parent windowId if the giving windowId is from embedded view hierarchy.
if (windowId == mAccessibilityWindowManager.getActiveWindowId(userId)) {
return true;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index cbff6bd..25911a7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT;
+
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.Manifest;
@@ -25,16 +27,21 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.view.Display;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -264,6 +271,24 @@
}
@Override
+ public boolean switchToInputMethod(String imeId) {
+ synchronized (mLock) {
+ if (!hasRightsToCurrentUserLocked()) {
+ return false;
+ }
+ }
+ final boolean result;
+ final int callingUserId = UserHandle.getCallingUserId();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ result = InputMethodManagerInternal.get().switchToInputMethod(imeId, callingUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return result;
+ }
+
+ @Override
public boolean isAccessibilityButtonAvailable() {
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -294,48 +319,16 @@
}
}
- // TODO(a11y shortcut): Refactoring the logic here, after the new Settings shortcut Ui merged.
public boolean isAccessibilityButtonAvailableLocked(AccessibilityUserState userState) {
// If the service does not request the accessibility button, it isn't available
if (!mRequestAccessibilityButton) {
return false;
}
-
// If the accessibility button isn't currently shown, it cannot be available to services
if (!mSystemSupport.isAccessibilityButtonShown()) {
return false;
}
-
- // If magnification is on and assigned to the accessibility button, services cannot be
- if (userState.isNavBarMagnificationEnabledLocked()
- && userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
- return false;
- }
-
- int requestingServices = 0;
- for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton) {
- requestingServices++;
- }
- }
-
- if (requestingServices == 1) {
- // If only a single service is requesting, it must be this service, and the
- // accessibility button is available to it
- return true;
- } else {
- // With more than one active service, we derive the target from the user's settings
- if (userState.getServiceAssignedToAccessibilityButtonLocked() == null) {
- // If the user has not made an assignment, we treat the button as available to
- // all services until the user interacts with the button to make an assignment
- return true;
- } else {
- // If an assignment was made, it defines availability
- return mComponentName.equals(
- userState.getServiceAssignedToAccessibilityButtonLocked());
- }
- }
+ return true;
}
@Override
@@ -400,4 +393,15 @@
}
}
}
+
+ @Override
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {
+ mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
+ final Bitmap screenshot = super.takeScreenshot(displayId);
+ // Send back the result.
+ final Bundle payload = new Bundle();
+ payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT, screenshot);
+ callback.sendResult(payload);
+ }, null).recycleOnUse());
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index a163f74..ebe2af6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -100,7 +100,6 @@
private boolean mIsAutoclickEnabled;
private boolean mIsDisplayMagnificationEnabled;
private boolean mIsFilterKeyEventsEnabled;
- private boolean mIsNavBarMagnificationEnabled;
private boolean mIsPerformGesturesEnabled;
private boolean mIsTextHighContrastEnabled;
private boolean mIsTouchExplorationEnabled;
@@ -153,7 +152,6 @@
mAccessibilityButtonTargets.clear();
mIsTouchExplorationEnabled = false;
mIsDisplayMagnificationEnabled = false;
- mIsNavBarMagnificationEnabled = false;
mIsAutoclickEnabled = false;
mUserNonInteractiveUiTimeout = 0;
mUserInteractiveUiTimeout = 0;
@@ -435,8 +433,6 @@
pw.append(", touchExplorationEnabled=").append(String.valueOf(mIsTouchExplorationEnabled));
pw.append(", displayMagnificationEnabled=").append(String.valueOf(
mIsDisplayMagnificationEnabled));
- pw.append(", navBarMagnificationEnabled=").append(String.valueOf(
- mIsNavBarMagnificationEnabled));
pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
@@ -553,8 +549,12 @@
mLastSentClientState = state;
}
- public boolean isShortcutKeyMagnificationEnabledLocked() {
- return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
+ /**
+ * Returns true if navibar magnification or shortcut key magnification is enabled.
+ */
+ public boolean isShortcutMagnificationEnabledLocked() {
+ return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME)
+ || mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
}
/**
@@ -690,28 +690,4 @@
public void setUserNonInteractiveUiTimeoutLocked(int timeout) {
mUserNonInteractiveUiTimeout = timeout;
}
-
- // TODO(a11y shortcut): These functions aren't necessary, after the new Settings shortcut Ui
- // is merged.
- boolean isNavBarMagnificationEnabledLocked() {
- return mIsNavBarMagnificationEnabled;
- }
-
- void setNavBarMagnificationEnabledLocked(boolean enabled) {
- mIsNavBarMagnificationEnabled = enabled;
- }
-
- boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() {
- return mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
- }
-
- ComponentName getServiceAssignedToAccessibilityButtonLocked() {
- final String targetName = mAccessibilityButtonTargets.isEmpty() ? null
- : mAccessibilityButtonTargets.valueAt(0);
- if (targetName == null) {
- return null;
- }
- return ComponentName.unflattenFromString(targetName);
- }
- // TODO(a11y shortcut): End
}
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 7dd4a70..5d9af26 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -25,6 +25,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Slog;
import android.view.Display;
@@ -297,6 +298,11 @@
}
@Override
+ public boolean switchToInputMethod(String imeId) {
+ return false;
+ }
+
+ @Override
public boolean isAccessibilityButtonAvailable() {
return false;
}
@@ -320,5 +326,8 @@
@Override
public void onFingerprintGesture(int gesture) {}
+
+ @Override
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
index 2891c6c..4c9e590 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -82,7 +82,7 @@
mCurrentTaps++;
if (mCurrentTaps == mTargetTaps) {
// Done.
- completeAfterTapTimeout(event, rawEvent, policyFlags);
+ completeGesture(event, rawEvent, policyFlags);
return;
}
// Needs more taps.
diff --git a/services/api/current.txt b/services/api/current.txt
index d802177..18e38b1 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -1 +1,31 @@
// Signature format: 2.0
+package com.android.server {
+
+ public abstract class SystemService {
+ ctor public SystemService(@NonNull android.content.Context);
+ method @NonNull public final android.content.Context getContext();
+ method public boolean isSupportedUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onBootPhase(int);
+ method public void onCleanupUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public abstract void onStart();
+ method public void onStartUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onStopUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onSwitchUser(@Nullable com.android.server.SystemService.TargetUser, @NonNull com.android.server.SystemService.TargetUser);
+ method public void onUnlockUser(@NonNull com.android.server.SystemService.TargetUser);
+ method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder);
+ method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder, boolean);
+ field public static final int PHASE_ACTIVITY_MANAGER_READY = 550; // 0x226
+ field public static final int PHASE_BOOT_COMPLETED = 1000; // 0x3e8
+ field public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520; // 0x208
+ field public static final int PHASE_LOCK_SETTINGS_READY = 480; // 0x1e0
+ field public static final int PHASE_SYSTEM_SERVICES_READY = 500; // 0x1f4
+ field public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600; // 0x258
+ field public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // 0x64
+ }
+
+ public static final class SystemService.TargetUser {
+ method @NonNull public android.os.UserHandle getUserHandle();
+ }
+
+}
+
diff --git a/services/art-profile b/services/art-profile
index a0338d5..4e113c8 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -3066,18 +3066,18 @@
HSPLcom/android/server/am/AppBindRecord;->dumpInIntentBind(Ljava/io/PrintWriter;Ljava/lang/String;)V
PLcom/android/server/am/AppBindRecord;->toString()Ljava/lang/String;
PLcom/android/server/am/AppBindRecord;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
-PLcom/android/server/am/AppCompactor$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLcom/android/server/am/AppCompactor$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V
-HSPLcom/android/server/am/AppCompactor;-><init>(Lcom/android/server/am/ActivityManagerService;)V
-HSPLcom/android/server/am/AppCompactor;->access$1000(Lcom/android/server/am/AppCompactor;)V
-HSPLcom/android/server/am/AppCompactor;->access$700(Lcom/android/server/am/AppCompactor;)Lcom/android/server/am/ActivityManagerService;
-HSPLcom/android/server/am/AppCompactor;->access$800(Lcom/android/server/am/AppCompactor;)Ljava/util/ArrayList;
-HSPLcom/android/server/am/AppCompactor;->access$900(Lcom/android/server/am/AppCompactor;)Ljava/util/Random;
-PLcom/android/server/am/AppCompactor;->dump(Ljava/io/PrintWriter;)V
-HSPLcom/android/server/am/AppCompactor;->init()V
-HSPLcom/android/server/am/AppCompactor;->updateCompactionThrottles()V
-HSPLcom/android/server/am/AppCompactor;->updateUseCompaction()V
-HSPLcom/android/server/am/AppCompactor;->useCompaction()Z
+PLcom/android/server/am/CachedAppOptimizer$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+HSPLcom/android/server/am/CachedAppOptimizer$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V
+HSPLcom/android/server/am/CachedAppOptimizer;-><init>(Lcom/android/server/am/ActivityManagerService;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->access$1000(Lcom/android/server/am/CachedAppOptimizer;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->access$700(Lcom/android/server/am/CachedAppOptimizer;)Lcom/android/server/am/ActivityManagerService;
+HSPLcom/android/server/am/CachedAppOptimizer;->access$800(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/ArrayList;
+HSPLcom/android/server/am/CachedAppOptimizer;->access$900(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/Random;
+PLcom/android/server/am/CachedAppOptimizer;->dump(Ljava/io/PrintWriter;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->init()V
+HSPLcom/android/server/am/CachedAppOptimizer;->updateCompactionThrottles()V
+HSPLcom/android/server/am/CachedAppOptimizer;->updateUseCompaction()V
+HSPLcom/android/server/am/CachedAppOptimizer;->useCompaction()Z
PLcom/android/server/am/AppErrorDialog$1;->handleMessage(Landroid/os/Message;)V
PLcom/android/server/am/AppErrorDialog;-><init>(Landroid/content/Context;Lcom/android/server/am/ActivityManagerService;Lcom/android/server/am/AppErrorDialog$Data;)V
PLcom/android/server/am/AppErrorDialog;->onClick(Landroid/view/View;)V
@@ -18632,9 +18632,9 @@
Lcom/android/server/am/ActivityManagerService$UidObserverRegistration;
Lcom/android/server/am/ActivityManagerService;
Lcom/android/server/am/AppBindRecord;
-Lcom/android/server/am/AppCompactor$1;
-Lcom/android/server/am/AppCompactor$MemCompactionHandler;
-Lcom/android/server/am/AppCompactor;
+Lcom/android/server/am/CachedAppOptimizer$1;
+Lcom/android/server/am/CachedAppOptimizer$MemCompactionHandler;
+Lcom/android/server/am/CachedAppOptimizer;
Lcom/android/server/am/AppErrorDialog$Data;
Lcom/android/server/am/AppErrorResult;
Lcom/android/server/am/AppErrors$BadProcessInfo;
diff --git a/services/art-profile-boot b/services/art-profile-boot
index e09424b..fe4178a 100644
--- a/services/art-profile-boot
+++ b/services/art-profile-boot
@@ -538,7 +538,7 @@
Lcom/android/server/am/ActivityManagerService;->updateLowMemStateLocked(III)Z
Lcom/android/server/wm/ConfigurationContainer;->getWindowConfiguration()Landroid/app/WindowConfiguration;
Lcom/android/server/am/OomAdjuster;->applyOomAdjLocked(Lcom/android/server/am/ProcessRecord;ZJJ)Z
-Lcom/android/server/am/AppCompactor;->useCompaction()Z
+Lcom/android/server/am/CachedAppOptimizer;->useCompaction()Z
Lcom/android/server/am/ProcessList;->procStatesDifferForMem(II)Z
Lcom/android/server/am/ActivityManagerService;->dispatchUidsChanged()V
Lcom/android/server/audio/AudioService$VolumeStreamState;->setIndex(IILjava/lang/String;)Z
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 03d9626..5405fc7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -331,8 +331,8 @@
}
@Override // from SystemService
- public boolean isSupported(UserInfo userInfo) {
- return userInfo.isFull() || userInfo.isManagedProfile();
+ public boolean isSupportedUser(TargetUser user) {
+ return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
}
@Override // from SystemService
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f34b5e7..7d354d2 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -162,6 +162,9 @@
/** uid the session is for */
public final int uid;
+ /** user id the session is for */
+ public final int userId;
+
/** ID of the task associated with this session's activity */
public final int taskId;
@@ -613,7 +616,7 @@
int newState, int flags) {
if (isInlineSuggestionsEnabled()) {
mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl();
- mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
+ mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(userId,
mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback);
}
@@ -759,6 +762,7 @@
mFlags = flags;
this.taskId = taskId;
this.uid = uid;
+ this.userId = userId;
mStartTime = SystemClock.elapsedRealtime();
mService = service;
mLock = lock;
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
index 17cb739..1e3ee88 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
@@ -16,24 +16,36 @@
package com.android.server.autofill.ui;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.PixelFormat;
+import android.graphics.drawable.Icon;
import android.os.IBinder;
import android.service.autofill.Dataset;
import android.util.Log;
import android.util.Slog;
+import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.R;
+
+import java.util.List;
+
/**
* This is a temporary inline suggestion UI inflater which will be replaced by the ExtServices
* implementation.
@@ -72,18 +84,66 @@
mContext.getDisplay(), (IBinder) null);
final SurfaceControl sc = wvr.getSurfacePackage().getSurfaceControl();
- TextView textView = new TextView(mContext);
- textView.setText(datasetValue.getTextValue());
- textView.setBackgroundColor(Color.WHITE);
- textView.setTextColor(Color.BLACK);
- if (onClickListener != null) {
- textView.setOnClickListener(onClickListener);
- }
+ final ViewGroup suggestionView =
+ (ViewGroup) renderSlice(dataset.getFieldInlinePresentation(index).getSlice());
WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(width, height,
WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
- wvr.addView(textView, lp);
+ wvr.addView(suggestionView, lp);
return sc;
}
+
+ private View renderSlice(Slice slice) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ final ViewGroup suggestionView =
+ (ViewGroup) inflater.inflate(R.layout.autofill_inline_suggestion, null);
+
+ final ImageView startIconView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_start_icon);
+ final TextView titleView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_title);
+ final TextView subtitleView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_subtitle);
+ final ImageView endIconView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_end_icon);
+
+ boolean hasStartIcon = false;
+ boolean hasEndIcon = false;
+ boolean hasSubtitle = false;
+ final List<SliceItem> sliceItems = slice.getItems();
+ for (int i = 0; i < sliceItems.size(); i++) {
+ final SliceItem sliceItem = sliceItems.get(i);
+ if (sliceItem.getFormat().equals(FORMAT_IMAGE)) {
+ final Icon sliceIcon = sliceItem.getIcon();
+ if (i == 0) { // start icon
+ startIconView.setImageIcon(sliceIcon);
+ hasStartIcon = true;
+ } else { // end icon
+ endIconView.setImageIcon(sliceIcon);
+ hasEndIcon = true;
+ }
+ } else if (sliceItem.getFormat().equals(FORMAT_TEXT)) {
+ final List<String> sliceHints = sliceItem.getHints();
+ final String sliceText = sliceItem.getText().toString();
+ if (sliceHints.contains("inline_title")) { // title
+ titleView.setText(sliceText);
+ } else { // subtitle
+ subtitleView.setText(sliceText);
+ hasSubtitle = true;
+ }
+ }
+ }
+ if (!hasStartIcon) {
+ startIconView.setVisibility(View.GONE);
+ }
+ if (!hasEndIcon) {
+ endIconView.setVisibility(View.GONE);
+ }
+ if (!hasSubtitle) {
+ subtitleView.setVisibility(View.GONE);
+ }
+
+ return suggestionView;
+ }
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index d45a54e..b13bef2 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1406,10 +1406,7 @@
long oldId = Binder.clearCallingIdentity();
final int[] userIds;
try {
- userIds =
- mContext
- .getSystemService(UserManager.class)
- .getProfileIds(callingUserId, false);
+ userIds = getUserManager().getProfileIds(callingUserId, false);
} finally {
Binder.restoreCallingIdentity(oldId);
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 7b95ab5..2c229b4 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -2538,7 +2538,6 @@
KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
} else {
if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
- synchronized (mQueueLock) {
// Fire the intent that kicks off the whole shebang...
try {
mRunBackupIntent.send();
@@ -2546,10 +2545,8 @@
// should never happen
Slog.e(TAG, "run-backup intent cancelled!");
}
-
// ...and cancel any pending scheduled job, because we've just superseded it
KeyValueBackupJob.cancel(mUserId, mContext);
- }
}
} finally {
Binder.restoreCallingIdentity(oldId);
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index eb62620..b06fc52 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -21,12 +21,10 @@
import static com.android.server.backup.BackupManagerService.TAG;
import android.app.backup.RestoreSet;
-import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.EventLog;
import android.util.Pair;
import android.util.Slog;
@@ -40,7 +38,6 @@
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.fullbackup.PerformAdbBackupTask;
-import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.keyvalue.BackupRequest;
import com.android.server.backup.keyvalue.KeyValueBackupTask;
import com.android.server.backup.params.AdbBackupParams;
@@ -73,10 +70,7 @@
public static final int MSG_RESTORE_SESSION_TIMEOUT = 8;
public static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
public static final int MSG_RUN_ADB_RESTORE = 10;
- public static final int MSG_RETRY_INIT = 11;
public static final int MSG_RETRY_CLEAR = 12;
- public static final int MSG_WIDGET_BROADCAST = 13;
- public static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14;
public static final int MSG_REQUEST_BACKUP = 15;
public static final int MSG_SCHEDULE_BACKUP_PACKAGE = 16;
public static final int MSG_BACKUP_OPERATION_TIMEOUT = 17;
@@ -279,12 +273,6 @@
break;
}
- case MSG_RUN_FULL_TRANSPORT_BACKUP: {
- PerformFullTransportBackupTask task = (PerformFullTransportBackupTask) msg.obj;
- (new Thread(task, "transport-backup")).start();
- break;
- }
-
case MSG_RUN_RESTORE: {
RestoreParams params = (RestoreParams) msg.obj;
Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
@@ -445,12 +433,6 @@
break;
}
- case MSG_WIDGET_BROADCAST: {
- final Intent intent = (Intent) msg.obj;
- backupManagerService.getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
- break;
- }
-
case MSG_REQUEST_BACKUP: {
BackupParams params = (BackupParams) msg.obj;
if (MORE_DEBUG) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 27824af..8eca62a 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -175,8 +175,8 @@
}
@Override // from SystemService
- public boolean isSupported(UserInfo userInfo) {
- return userInfo.isFull() || userInfo.isManagedProfile();
+ public boolean isSupportedUser(TargetUser user) {
+ return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
}
@Override // from SystemService
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 2f8c506..f3647602 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -23,8 +23,6 @@
import android.os.UserHandle;
import android.os.UserManager;
-import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
-
import java.util.List;
import java.util.Set;
@@ -198,6 +196,12 @@
long beginTime, long endTime, boolean obfuscateInstantApps);
/**
+ * Returns the events for the user in the given time period.
+ */
+ public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime,
+ long endTime, boolean shouldObfuscateInstantApps);
+
+ /**
* Used to persist the last time a job was run for this app, in order to make decisions later
* whether a job should be deferred until later. The time passed in should be in elapsed
* realtime since boot.
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 312dd46..368416b 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -41,6 +41,7 @@
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
@@ -478,10 +479,12 @@
* will be disabled. Pass in null or an empty list to disable
* all overlays. The order of the items is significant if several
* overlays modify the same resource.
+ * @param outUpdatedPackageNames An output list that contains the package names of packages
+ * affected by the update of enabled overlays.
* @return true if all packages names were known by the package manager, false otherwise
*/
public abstract boolean setEnabledOverlayPackages(int userId, String targetPackageName,
- List<String> overlayPackageNames);
+ List<String> overlayPackageNames, Collection<String> outUpdatedPackageNames);
/**
* Resolves an activity intent, allowing instant apps to be resolved.
@@ -811,6 +814,12 @@
public abstract boolean isApexPackage(String packageName);
/**
+ * Returns list of {@code packageName} of apks inside the given apex.
+ * @param apexPackageName Package name of the apk container of apex
+ */
+ public abstract List<String> getApksInApex(String apexPackageName);
+
+ /**
* Uninstalls given {@code packageName}.
*
* @param packageName apex package to uninstall.
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 73b6c7a..0f2fb92 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -208,6 +208,7 @@
AppWakeupHistory mAppWakeupHistory;
ClockReceiver mClockReceiver;
final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
+ IBinder.DeathRecipient mListenerDeathRecipient;
Intent mTimeTickIntent;
IAlarmListener mTimeTickTrigger;
PendingIntent mDateChangeSender;
@@ -1447,6 +1448,18 @@
public void onStart() {
mInjector.init();
+ mListenerDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ }
+
+ @Override
+ public void binderDied(IBinder who) {
+ final IAlarmListener listener = IAlarmListener.Stub.asInterface(who);
+ removeImpl(null, listener);
+ }
+ };
+
synchronized (mLock) {
mHandler = new AlarmHandler();
mConstants = new Constants(mHandler);
@@ -1653,6 +1666,15 @@
return;
}
+ if (directReceiver != null) {
+ try {
+ directReceiver.asBinder().linkToDeath(mListenerDeathRecipient, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Dropping unreachable alarm listener " + listenerTag);
+ return;
+ }
+ }
+
// Sanity check the window length. This will catch people mistakenly
// trying to pass an end-of-window timestamp rather than a duration.
if (windowLength > AlarmManager.INTERVAL_HALF_DAY) {
@@ -2079,8 +2101,9 @@
@Override
public long currentNetworkTimeMillis() {
final NtpTrustedTime time = NtpTrustedTime.getInstance(getContext());
- if (time.hasCache()) {
- return time.currentTimeMillis();
+ NtpTrustedTime.TimeResult ntpResult = time.getCachedTimeResult();
+ if (ntpResult != null) {
+ return ntpResult.currentTimeMillis();
} else {
throw new ParcelableException(new DateTimeException("Missing NTP fix"));
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index c2e32d3..c1e23e4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -79,17 +79,15 @@
import android.net.IpMemoryStore;
import android.net.IpPrefix;
import android.net.LinkProperties;
-import android.net.LinkProperties.CompareResult;
import android.net.MatchAllNetworkSpecifier;
import android.net.NattSocketKeepalive;
import android.net.Network;
import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
import android.net.NetworkMonitorManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkProvider;
@@ -114,6 +112,7 @@
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
import android.net.shared.PrivateDnsConfig;
+import android.net.util.LinkPropertiesUtils.CompareResult;
import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
import android.os.Binder;
@@ -364,10 +363,10 @@
private static final int EVENT_PROXY_HAS_CHANGED = 16;
/**
- * used internally when registering NetworkFactories
- * obj = NetworkFactoryInfo
+ * used internally when registering NetworkProviders
+ * obj = NetworkProviderInfo
*/
- private static final int EVENT_REGISTER_NETWORK_FACTORY = 17;
+ private static final int EVENT_REGISTER_NETWORK_PROVIDER = 17;
/**
* used internally when registering NetworkAgents
@@ -403,10 +402,10 @@
private static final int EVENT_RELEASE_NETWORK_REQUEST = 22;
/**
- * used internally when registering NetworkFactories
+ * used internally when registering NetworkProviders
* obj = Messenger
*/
- private static final int EVENT_UNREGISTER_NETWORK_FACTORY = 23;
+ private static final int EVENT_UNREGISTER_NETWORK_PROVIDER = 23;
/**
* used internally to expire a wakelock when transitioning
@@ -2394,9 +2393,9 @@
return;
}
- pw.print("NetworkFactories for:");
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- pw.print(" " + nfi.name);
+ pw.print("NetworkProviders for:");
+ for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ pw.print(" " + npi.name);
}
pw.println();
pw.println();
@@ -2631,8 +2630,8 @@
if (nai.everConnected) {
loge("ERROR: cannot call explicitlySelected on already-connected network");
}
- nai.networkMisc.explicitlySelected = toBool(msg.arg1);
- nai.networkMisc.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
+ nai.networkAgentConfig.explicitlySelected = toBool(msg.arg1);
+ nai.networkAgentConfig.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
// Mark the network as temporarily accepting partial connectivity so that it
// will be validated (and possibly become default) even if it only provides
// partial internet access. Note that if user connects to partial connectivity
@@ -2640,7 +2639,7 @@
// out of wifi coverage) and if the same wifi is available again, the device
// will auto connect to this wifi even though the wifi has "no internet".
// TODO: Evaluate using a separate setting in IpMemoryStore.
- nai.networkMisc.acceptPartialConnectivity = toBool(msg.arg2);
+ nai.networkAgentConfig.acceptPartialConnectivity = toBool(msg.arg2);
break;
}
case NetworkAgent.EVENT_SOCKET_KEEPALIVE: {
@@ -2672,10 +2671,10 @@
}
// Only show the notification when the private DNS is broken and the
// PRIVATE_DNS_BROKEN notification hasn't shown since last valid.
- if (privateDnsBroken && !nai.networkMisc.hasShownBroken) {
+ if (privateDnsBroken && !nai.networkAgentConfig.hasShownBroken) {
showNetworkNotification(nai, NotificationType.PRIVATE_DNS_BROKEN);
}
- nai.networkMisc.hasShownBroken = privateDnsBroken;
+ nai.networkAgentConfig.hasShownBroken = privateDnsBroken;
} else if (nai.networkCapabilities.isPrivateDnsBroken()) {
// If probePrivateDnsCompleted is false but nai.networkCapabilities says
// private DNS is broken, it means this network is being reevaluated.
@@ -2685,7 +2684,7 @@
nai.networkCapabilities.setPrivateDnsBroken(false);
final int oldScore = nai.getCurrentScore();
updateCapabilities(oldScore, nai, nai.networkCapabilities);
- nai.networkMisc.hasShownBroken = false;
+ nai.networkAgentConfig.hasShownBroken = false;
}
break;
}
@@ -2727,7 +2726,7 @@
nai.lastValidated = valid;
nai.everValidated |= valid;
updateCapabilities(oldScore, nai, nai.networkCapabilities);
- // If score has changed, rebroadcast to NetworkFactories. b/17726566
+ // If score has changed, rebroadcast to NetworkProviders. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
if (valid) {
handleFreshlyValidatedNetwork(nai);
@@ -2744,7 +2743,7 @@
// If network becomes valid, the hasShownBroken should be reset for
// that network so that the notification will be fired when the private
// DNS is broken again.
- nai.networkMisc.hasShownBroken = false;
+ nai.networkAgentConfig.hasShownBroken = false;
}
} else if (partialConnectivityChanged) {
updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -2803,9 +2802,10 @@
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
break;
}
- if (!nai.networkMisc.provisioningNotificationDisabled) {
+ if (!nai.networkAgentConfig.provisioningNotificationDisabled) {
mNotifier.showNotification(netId, NotificationType.SIGN_IN, nai, null,
- (PendingIntent) msg.obj, nai.networkMisc.explicitlySelected);
+ (PendingIntent) msg.obj,
+ nai.networkAgentConfig.explicitlySelected);
}
}
break;
@@ -2842,25 +2842,11 @@
return true;
}
- private boolean maybeHandleNetworkFactoryMessage(Message msg) {
- switch (msg.what) {
- default:
- return false;
- case NetworkFactory.EVENT_UNFULFILLABLE_REQUEST: {
- handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.sendingUid,
- /* callOnUnavailable */ true);
- break;
- }
- }
- return true;
- }
-
@Override
public void handleMessage(Message msg) {
if (!maybeHandleAsyncChannelMessage(msg)
&& !maybeHandleNetworkMonitorMessage(msg)
- && !maybeHandleNetworkAgentInfoMessage(msg)
- && !maybeHandleNetworkFactoryMessage(msg)) {
+ && !maybeHandleNetworkAgentInfoMessage(msg)) {
maybeHandleNetworkAgentMessage(msg);
}
}
@@ -3031,16 +3017,16 @@
private void handleAsyncChannelHalfConnect(Message msg) {
ensureRunningOnConnectivityServiceThread();
final AsyncChannel ac = (AsyncChannel) msg.obj;
- if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
+ if (mNetworkProviderInfos.containsKey(msg.replyTo)) {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
if (VDBG) log("NetworkFactory connected");
// Finish setting up the full connection
- NetworkFactoryInfo nfi = mNetworkFactoryInfos.get(msg.replyTo);
- nfi.completeConnection();
- sendAllRequestsToFactory(nfi);
+ NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo);
+ npi.completeConnection();
+ sendAllRequestsToProvider(npi);
} else {
loge("Error connecting NetworkFactory");
- mNetworkFactoryInfos.remove(msg.obj);
+ mNetworkProviderInfos.remove(msg.obj);
}
} else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
@@ -3072,8 +3058,8 @@
if (nai != null) {
disconnectAndDestroyNetwork(nai);
} else {
- NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
- if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
+ NetworkProviderInfo npi = mNetworkProviderInfos.remove(msg.replyTo);
+ if (DBG && npi != null) log("unregisterNetworkFactory for " + npi.name);
}
}
@@ -3156,7 +3142,7 @@
// ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
// after we've rematched networks with requests which should make a potential
// fallback network the default or requested a new network from the
- // NetworkFactories, so network traffic isn't interrupted for an unnecessarily
+ // NetworkProviders, so network traffic isn't interrupted for an unnecessarily
// long time.
destroyNativeNetwork(nai);
mDnsManager.removeNetwork(nai.network);
@@ -3169,8 +3155,8 @@
// This should never fail. Specifying an already in use NetID will cause failure.
if (networkAgent.isVPN()) {
mNetd.networkCreateVpn(networkAgent.network.netId,
- (networkAgent.networkMisc == null
- || !networkAgent.networkMisc.allowBypass));
+ (networkAgent.networkAgentConfig == null
+ || !networkAgent.networkAgentConfig.allowBypass));
} else {
mNetd.networkCreatePhysical(networkAgent.network.netId,
getNetworkPermission(networkAgent.networkCapabilities));
@@ -3419,8 +3405,8 @@
}
}
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- nfi.cancelRequest(nri.request);
+ for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ npi.cancelRequest(nri.request);
}
} else {
// listens don't have a singular affectedNetwork. Check all networks to see
@@ -3470,16 +3456,16 @@
return;
}
- if (!nai.networkMisc.explicitlySelected) {
+ if (!nai.networkAgentConfig.explicitlySelected) {
Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network");
}
- if (accept != nai.networkMisc.acceptUnvalidated) {
- nai.networkMisc.acceptUnvalidated = accept;
+ if (accept != nai.networkAgentConfig.acceptUnvalidated) {
+ nai.networkAgentConfig.acceptUnvalidated = accept;
// If network becomes partial connectivity and user already accepted to use this
// network, we should respect the user's option and don't need to popup the
// PARTIAL_CONNECTIVITY notification to user again.
- nai.networkMisc.acceptPartialConnectivity = accept;
+ nai.networkAgentConfig.acceptPartialConnectivity = accept;
rematchAllNetworksAndRequests();
sendUpdatedScoreToFactories(nai);
}
@@ -3516,8 +3502,8 @@
return;
}
- if (accept != nai.networkMisc.acceptPartialConnectivity) {
- nai.networkMisc.acceptPartialConnectivity = accept;
+ if (accept != nai.networkAgentConfig.acceptPartialConnectivity) {
+ nai.networkAgentConfig.acceptPartialConnectivity = accept;
}
// TODO: Use the current design or save the user choice into IpMemoryStore.
@@ -3615,17 +3601,32 @@
enforceSettingsPermission();
}
- // getNetworkAgentInfoForNetwork is thread-safe
- final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(mNetwork);
- if (nai == null) return;
-
- // nai.networkMonitor() is thread-safe
- final NetworkMonitorManager nm = nai.networkMonitor();
+ final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork);
if (nm == null) return;
nm.notifyCaptivePortalAppFinished(response);
}
@Override
+ public void appRequest(final int request) {
+ final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork);
+ if (nm == null) return;
+
+ if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
+ nm.forceReevaluation(Binder.getCallingUid());
+ }
+ }
+
+ @Nullable
+ private NetworkMonitorManager getNetworkMonitorManager(final Network network) {
+ // getNetworkAgentInfoForNetwork is thread-safe
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai == null) return null;
+
+ // nai.networkMonitor() is thread-safe
+ return nai.networkMonitor();
+ }
+
+ @Override
public void logEvent(int eventId, String packageName) {
enforceSettingsPermission();
@@ -3727,7 +3728,7 @@
action = ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY;
// Don't bother the user with a high-priority notification if the network was not
// explicitly selected by the user.
- highPriority = nai.networkMisc.explicitlySelected;
+ highPriority = nai.networkAgentConfig.explicitlySelected;
break;
default:
Slog.wtf(TAG, "Unknown notification type " + type);
@@ -3760,14 +3761,15 @@
// automatically connects to a network that has partial Internet access, the user will
// always be able to use it, either because they've already chosen "don't ask again" or
// because we have prompt them.
- if (nai.partialConnectivity && !nai.networkMisc.acceptPartialConnectivity) {
+ if (nai.partialConnectivity && !nai.networkAgentConfig.acceptPartialConnectivity) {
return true;
}
// If a network has no Internet access, only prompt if the network was explicitly selected
// and if the user has not already told us to use the network regardless of whether it
// validated or not.
- if (nai.networkMisc.explicitlySelected && !nai.networkMisc.acceptUnvalidated) {
+ if (nai.networkAgentConfig.explicitlySelected
+ && !nai.networkAgentConfig.acceptUnvalidated) {
return true;
}
@@ -3855,12 +3857,12 @@
handleApplyDefaultProxy((ProxyInfo)msg.obj);
break;
}
- case EVENT_REGISTER_NETWORK_FACTORY: {
- handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj);
+ case EVENT_REGISTER_NETWORK_PROVIDER: {
+ handleRegisterNetworkProvider((NetworkProviderInfo) msg.obj);
break;
}
- case EVENT_UNREGISTER_NETWORK_FACTORY: {
- handleUnregisterNetworkFactory((Messenger)msg.obj);
+ case EVENT_UNREGISTER_NETWORK_PROVIDER: {
+ handleUnregisterNetworkProvider((Messenger) msg.obj);
break;
}
case EVENT_REGISTER_NETWORK_AGENT: {
@@ -4905,7 +4907,7 @@
}
};
- private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos = new HashMap<>();
+ private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
@@ -4913,18 +4915,18 @@
@GuardedBy("mUidToNetworkRequestCount")
private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
- private static class NetworkFactoryInfo {
+ private static class NetworkProviderInfo {
public final String name;
public final Messenger messenger;
private final AsyncChannel mAsyncChannel;
private final IBinder.DeathRecipient mDeathRecipient;
- public final int factorySerialNumber;
+ public final int providerId;
- NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
- int factorySerialNumber, IBinder.DeathRecipient deathRecipient) {
+ NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
+ int providerId, IBinder.DeathRecipient deathRecipient) {
this.name = name;
this.messenger = messenger;
- this.factorySerialNumber = factorySerialNumber;
+ this.providerId = providerId;
mAsyncChannel = asyncChannel;
mDeathRecipient = deathRecipient;
@@ -4942,17 +4944,17 @@
messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj));
} catch (RemoteException e) {
// Remote process died. Ignore; the death recipient will remove this
- // NetworkFactoryInfo from mNetworkFactoryInfos.
+ // NetworkProviderInfo from mNetworkProviderInfos.
}
}
- void requestNetwork(NetworkRequest request, int score, int servingSerialNumber) {
+ void requestNetwork(NetworkRequest request, int score, int servingProviderId) {
if (isLegacyNetworkFactory()) {
mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
- servingSerialNumber, request);
+ servingProviderId, request);
} else {
sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score,
- servingSerialNumber, request);
+ servingProviderId, request);
}
}
@@ -5365,45 +5367,45 @@
@Override
public int registerNetworkFactory(Messenger messenger, String name) {
enforceNetworkFactoryPermission();
- NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel(),
+ NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(),
nextNetworkProviderId(), null /* deathRecipient */);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
- return nfi.factorySerialNumber;
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
+ return npi.providerId;
}
- private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
- if (mNetworkFactoryInfos.containsKey(nfi.messenger)) {
+ private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
+ if (mNetworkProviderInfos.containsKey(npi.messenger)) {
// Avoid creating duplicates. even if an app makes a direct AIDL call.
// This will never happen if an app calls ConnectivityManager#registerNetworkProvider,
// as that will throw if a duplicate provider is registered.
- Slog.e(TAG, "Attempt to register existing NetworkFactoryInfo "
- + mNetworkFactoryInfos.get(nfi.messenger).name);
+ Slog.e(TAG, "Attempt to register existing NetworkProviderInfo "
+ + mNetworkProviderInfos.get(npi.messenger).name);
return;
}
- if (DBG) log("Got NetworkFactory Messenger for " + nfi.name);
- mNetworkFactoryInfos.put(nfi.messenger, nfi);
- nfi.connect(mContext, mTrackerHandler);
- if (!nfi.isLegacyNetworkFactory()) {
+ if (DBG) log("Got NetworkProvider Messenger for " + npi.name);
+ mNetworkProviderInfos.put(npi.messenger, npi);
+ npi.connect(mContext, mTrackerHandler);
+ if (!npi.isLegacyNetworkFactory()) {
// Legacy NetworkFactories get their requests when their AsyncChannel connects.
- sendAllRequestsToFactory(nfi);
+ sendAllRequestsToProvider(npi);
}
}
@Override
public int registerNetworkProvider(Messenger messenger, String name) {
enforceNetworkFactoryPermission();
- NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger,
+ NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger,
null /* asyncChannel */, nextNetworkProviderId(),
() -> unregisterNetworkProvider(messenger));
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
- return nfi.factorySerialNumber;
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
+ return npi.providerId;
}
@Override
public void unregisterNetworkProvider(Messenger messenger) {
enforceNetworkFactoryPermission();
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_FACTORY, messenger));
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger));
}
@Override
@@ -5411,13 +5413,13 @@
unregisterNetworkProvider(messenger);
}
- private void handleUnregisterNetworkFactory(Messenger messenger) {
- NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger);
- if (nfi == null) {
- loge("Failed to find Messenger in unregisterNetworkFactory");
+ private void handleUnregisterNetworkProvider(Messenger messenger) {
+ NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger);
+ if (npi == null) {
+ loge("Failed to find Messenger in unregisterNetworkProvider");
return;
}
- if (DBG) log("unregisterNetworkFactory for " + nfi.name);
+ if (DBG) log("unregisterNetworkProvider for " + npi.name);
}
@Override
@@ -5487,11 +5489,14 @@
// changes that would conflict throughout the automerger graph. Having this method temporarily
// helps with the process of going through with all these dependent changes across the entire
// tree.
- public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
+ /**
+ * Register a new agent. {@see #registerNetworkAgent} below.
+ */
+ public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int currentScore, NetworkMisc networkMisc) {
+ int currentScore, NetworkAgentConfig networkAgentConfig) {
return registerNetworkAgent(messenger, networkInfo, linkProperties, networkCapabilities,
- currentScore, networkMisc, NetworkFactory.SerialNumber.NONE);
+ currentScore, networkAgentConfig, NetworkProvider.ID_NONE);
}
/**
@@ -5506,12 +5511,13 @@
* later : see {@link #updateCapabilities}.
* @param currentScore the initial score of the network. See
* {@link NetworkAgentInfo#getCurrentScore}.
- * @param networkMisc metadata about the network. This is never updated.
- * @param factorySerialNumber the serial number of the factory owning this NetworkAgent.
+ * @param networkAgentConfig metadata about the network. This is never updated.
+ * @param providerId the ID of the provider owning this NetworkAgent.
+ * @return the network created for this agent.
*/
- public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
+ public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int currentScore, NetworkMisc networkMisc, int factorySerialNumber) {
+ int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) {
enforceNetworkFactoryPermission();
LinkProperties lp = new LinkProperties(linkProperties);
@@ -5523,8 +5529,8 @@
ns.putIntExtension(NetworkScore.LEGACY_SCORE, currentScore);
final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
- ns, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd,
- mDnsResolver, mNMS, factorySerialNumber);
+ ns, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), this,
+ mNetd, mDnsResolver, mNMS, providerId);
// Make sure the network capabilities reflect what the agent info says.
nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
final String extraInfo = networkInfo.getExtraInfo();
@@ -5542,7 +5548,7 @@
// If the network disconnects or sends any other event before that, messages are deferred by
// NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the
// registration.
- return nai.network.netId;
+ return nai.network;
}
private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
@@ -5803,7 +5809,7 @@
// Once a NetworkAgent is connected, complain if some immutable capabilities are removed.
// Don't complain for VPNs since they're not driven by requests and there is no risk of
// causing a connect/teardown loop.
- // TODO: remove this altogether and make it the responsibility of the NetworkFactories to
+ // TODO: remove this altogether and make it the responsibility of the NetworkProviders to
// avoid connect/teardown loops.
if (nai.everConnected &&
!nai.isVPN() &&
@@ -5945,7 +5951,7 @@
LinkProperties lp) {
if (nc == null || lp == null) return false;
return nai.isVPN()
- && !nai.networkMisc.allowBypass
+ && !nai.networkAgentConfig.allowBypass
&& nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID
&& lp.getInterfaceName() != null
&& (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute());
@@ -6043,13 +6049,13 @@
if (VDBG || DDBG){
log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
}
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- nfi.requestNetwork(networkRequest, score, serial);
+ for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ npi.requestNetwork(networkRequest, score, serial);
}
}
/** Sends all current NetworkRequests to the specified factory. */
- private void sendAllRequestsToFactory(NetworkFactoryInfo nfi) {
+ private void sendAllRequestsToProvider(NetworkProviderInfo npi) {
ensureRunningOnConnectivityServiceThread();
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
if (nri.request.isListen()) continue;
@@ -6061,9 +6067,9 @@
serial = nai.factorySerialNumber;
} else {
score = 0;
- serial = NetworkFactory.SerialNumber.NONE;
+ serial = NetworkProvider.ID_NONE;
}
- nfi.requestNetwork(nri.request, score, serial);
+ npi.requestNetwork(nri.request, score, serial);
}
}
@@ -6344,11 +6350,11 @@
Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request);
}
addedRequests.add(nri);
- // Tell NetworkFactories about the new score, so they can stop
+ // Tell NetworkProviders about the new score, so they can stop
// trying to connect if they know they cannot match it.
// TODO - this could get expensive if we have a lot of requests for this
// network. Think about if there is a way to reduce this. Push
- // netid->request mapping to each factory?
+ // netid->request mapping to each provider?
sendUpdatedScoreToFactories(nri.request, newSatisfier);
if (isDefaultRequest(nri)) {
isNewDefault = true;
@@ -6377,7 +6383,7 @@
} else {
Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
newNetwork.name() +
- " without updating mSatisfier or factories!");
+ " without updating mSatisfier or providers!");
}
// TODO: Technically, sending CALLBACK_LOST here is
// incorrect if there is a replacement network currently
@@ -6636,7 +6642,7 @@
// command must be sent after updating LinkProperties to maximize chances of
// NetworkMonitor seeing the correct LinkProperties when starting.
// TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
- if (networkAgent.networkMisc.acceptPartialConnectivity) {
+ if (networkAgent.networkAgentConfig.acceptPartialConnectivity) {
networkAgent.networkMonitor().setAcceptPartialConnectivity();
}
networkAgent.networkMonitor().notifyNetworkConnected(
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index 7909e30..c60460f 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -44,9 +44,9 @@
private static final String TAG = "DynamicSystemService";
private static final String NO_SERVICE_ERROR = "no gsiservice";
private static final int GSID_ROUGH_TIMEOUT_MS = 8192;
- private static final String PATH_DEFAULT = "/data/gsi";
+ private static final String PATH_DEFAULT = "/data/gsi/";
private Context mContext;
- private String mInstallPath;
+ private String mInstallPath, mDsuSlot;
private volatile IGsiService mGsiService;
DynamicSystemService(Context context) {
@@ -115,7 +115,7 @@
}
@Override
- public boolean startInstallation() throws RemoteException {
+ public boolean startInstallation(String dsuSlot) throws RemoteException {
IGsiService service = getGsiService();
// priority from high to low: sysprop -> sdcard -> /data
String path = SystemProperties.get("os.aot.path");
@@ -129,16 +129,17 @@
if (!Environment.MEDIA_MOUNTED.equals(volume.getState())) continue;
File sdCard = volume.getPathFile();
if (sdCard.isDirectory()) {
- path = sdCard.getPath();
+ path = new File(sdCard, dsuSlot).getPath();
break;
}
}
if (path.isEmpty()) {
- path = PATH_DEFAULT;
+ path = PATH_DEFAULT + dsuSlot;
}
Slog.i(TAG, "startInstallation -> " + path);
}
mInstallPath = path;
+ mDsuSlot = dsuSlot;
if (service.openInstall(path) != 0) {
Slog.i(TAG, "Failed to open " + path);
return false;
@@ -203,7 +204,7 @@
public boolean setEnable(boolean enable, boolean oneShot) throws RemoteException {
IGsiService gsiService = getGsiService();
if (enable) {
- return gsiService.enableGsi(oneShot) == 0;
+ return gsiService.enableGsi(oneShot, mDsuSlot) == 0;
} else {
return gsiService.disableGsi();
}
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index 70569db..5179fa7 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -38,11 +38,13 @@
import android.view.IGraphicsStatsCallback;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FastPrintWriter;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -78,6 +80,8 @@
private static final int SAVE_BUFFER = 1;
private static final int DELETE_OLD = 2;
+ private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever.
+
// This isn't static because we need this to happen after registerNativeMethods, however
// the class is loaded (and thus static ctor happens) before that occurs.
private final int ASHMEM_SIZE = nGetAshmemSize();
@@ -121,6 +125,7 @@
return true;
}
});
+ nativeInit();
}
/**
@@ -186,6 +191,86 @@
return pfd;
}
+ // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period
+ // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the
+ // current day.
+ // This method is invoked from native code only.
+ @SuppressWarnings({"UnusedDeclaration"})
+ private long pullGraphicsStats(boolean lastFullDay) throws RemoteException {
+ int uid = Binder.getCallingUid();
+
+ // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method.
+ // TODO: remove exception for statsd daemon after required permissions are granted. statsd
+ // TODO: should have these permissions granted by data/etc/platform.xml, but it does not.
+ if (uid != AID_STATSD) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new FastPrintWriter(sw);
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
+ pw.flush();
+ throw new RemoteException(sw.toString());
+ }
+ }
+
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return pullGraphicsStatsImpl(lastFullDay);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ private long pullGraphicsStatsImpl(boolean lastFullDay) {
+ long targetDay;
+ if (lastFullDay) {
+ // Get stats from yesterday. Stats stay constant, because the day is over.
+ targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis();
+ } else {
+ // Get stats from today. Stats may change as more apps are run today.
+ targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
+ }
+
+ // Find active buffers for targetDay.
+ ArrayList<HistoricalBuffer> buffers;
+ synchronized (mLock) {
+ buffers = new ArrayList<>(mActive.size());
+ for (int i = 0; i < mActive.size(); i++) {
+ ActiveBuffer buffer = mActive.get(i);
+ if (buffer.mInfo.startTime == targetDay) {
+ try {
+ buffers.add(new HistoricalBuffer(buffer));
+ } catch (IOException ex) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ // Dump active and historic buffers for targetDay in a serialized
+ // GraphicsStatsServiceDumpProto proto.
+ long dump = nCreateDump(-1, true);
+ try {
+ synchronized (mFileAccessLock) {
+ HashSet<File> skipList = dumpActiveLocked(dump, buffers);
+ buffers.clear();
+ String subPath = String.format("%d", targetDay);
+ File dateDir = new File(mGraphicsStatsDir, subPath);
+ if (dateDir.exists()) {
+ for (File pkg : dateDir.listFiles()) {
+ for (File version : pkg.listFiles()) {
+ File data = new File(version, "total");
+ if (skipList.contains(data)) {
+ continue;
+ }
+ nAddToDump(dump, data.getAbsolutePath());
+ }
+ }
+ }
+ }
+ } finally {
+ return nFinishDumpInMemory(dump);
+ }
+ }
+
private ParcelFileDescriptor getPfd(MemoryFile file) {
try {
if (!file.getFileDescriptor().valid()) {
@@ -379,12 +464,21 @@
}
}
+ @Override
+ protected void finalize() throws Throwable {
+ nativeDestructor();
+ }
+
+ private native void nativeInit();
+ private static native void nativeDestructor();
+
private static native int nGetAshmemSize();
private static native long nCreateDump(int outFd, boolean isProto);
private static native void nAddToDump(long dump, String path, String packageName,
long versionCode, long startTime, long endTime, byte[] data);
private static native void nAddToDump(long dump, String path);
private static native void nFinishDump(long dump);
+ private static native long nFinishDumpInMemory(long dump);
private static native void nSaveBuffer(String path, String packageName, long versionCode,
long startTime, long endTime, byte[] data);
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 32128d5..dc393d1 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -69,7 +69,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
import android.stats.location.LocationStatsEnums;
@@ -84,7 +83,6 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -105,6 +103,7 @@
import com.android.server.location.MockProvider;
import com.android.server.location.MockableLocationProvider;
import com.android.server.location.PassiveProvider;
+import com.android.server.location.UserInfoStore;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import java.io.ByteArrayOutputStream;
@@ -194,6 +193,7 @@
private final Object mLock = new Object();
private final Context mContext;
private final Handler mHandler;
+ private final UserInfoStore mUserInfoStore;
private final LocationSettingsStore mSettingsStore;
private final LocationUsageLogger mLocationUsageLogger;
@@ -203,7 +203,6 @@
private PackageManager mPackageManager;
private PowerManager mPowerManager;
private ActivityManager mActivityManager;
- private UserManager mUserManager;
private GeofenceManager mGeofenceManager;
private LocationFudger mLocationFudger;
@@ -237,10 +236,6 @@
private final HashMap<String, Location> mLastLocationCoarseInterval =
new HashMap<>();
- // current active user on the device
- private int mCurrentUserId;
- private int[] mCurrentUserProfiles;
-
@GuardedBy("mLock")
@PowerManager.LocationPowerSaveMode
private int mBatterySaverMode;
@@ -248,12 +243,10 @@
private LocationManagerService(Context context) {
mContext = context;
mHandler = FgThread.getHandler();
+ mUserInfoStore = new UserInfoStore(mContext);
mSettingsStore = new LocationSettingsStore(mContext, mHandler);
mLocationUsageLogger = new LocationUsageLogger();
- mCurrentUserId = UserHandle.USER_NULL;
- mCurrentUserProfiles = new int[]{UserHandle.USER_NULL};
-
// set up passive provider - we do this early because it has no dependencies on system
// services or external code that isn't ready yet, and because this allows the variable to
// be final. other more complex providers are initialized later, when system services are
@@ -277,6 +270,7 @@
}
private void onSystemReady() {
+ mUserInfoStore.onSystemReady();
mSettingsStore.onSystemReady();
synchronized (mLock) {
@@ -284,7 +278,6 @@
mAppOps = mContext.getSystemService(AppOpsManager.class);
mPowerManager = mContext.getSystemService(PowerManager.class);
mActivityManager = mContext.getSystemService(ActivityManager.class);
- mUserManager = mContext.getSystemService(UserManager.class);
mLocationFudger = new LocationFudger(mContext, mHandler);
mGeofenceManager = new GeofenceManager(mContext, mSettingsStore);
@@ -372,10 +365,13 @@
}
}.register(mContext, mHandler.getLooper(), true);
+ mUserInfoStore.addListener((oldUserId, newUserId) -> {
+ synchronized (mLock) {
+ onUserChangedLocked(oldUserId, newUserId);
+ }
+ });
+
IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
- intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
- intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
@@ -388,14 +384,6 @@
}
synchronized (mLock) {
switch (action) {
- case Intent.ACTION_USER_SWITCHED:
- onUserChangedLocked(
- intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- break;
- case Intent.ACTION_MANAGED_PROFILE_ADDED:
- case Intent.ACTION_MANAGED_PROFILE_REMOVED:
- onUserProfilesChangedLocked();
- break;
case Intent.ACTION_SCREEN_ON:
case Intent.ACTION_SCREEN_OFF:
onScreenStateChangedLocked();
@@ -405,11 +393,10 @@
}
}, UserHandle.ALL, intentFilter, null, mHandler);
- // switching the user from null to system here performs the bulk of the initialization
+ // switching the user from null to current here performs the bulk of the initialization
// work. the user being changed will cause a reload of all user specific settings, which
// causes initialization, and propagates changes until a steady state is reached
- mCurrentUserId = UserHandle.USER_NULL;
- onUserChangedLocked(ActivityManager.getCurrentUser());
+ onUserChangedLocked(UserHandle.USER_NULL, mUserInfoStore.getCurrentUserId());
}
}
@@ -551,16 +538,6 @@
}
@GuardedBy("mLock")
- private void onUserProfilesChangedLocked() {
- mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
- }
-
- @GuardedBy("mLock")
- private boolean isCurrentProfileLocked(int userId) {
- return ArrayUtils.contains(mCurrentUserProfiles, userId);
- }
-
- @GuardedBy("mLock")
private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) {
PackageManager pm = mContext.getPackageManager();
String systemPackageName = mContext.getPackageName();
@@ -568,7 +545,7 @@
List<ResolveInfo> rInfos = pm.queryIntentServicesAsUser(
new Intent(FUSED_LOCATION_SERVICE_ACTION),
- PackageManager.GET_META_DATA, mCurrentUserId);
+ PackageManager.GET_META_DATA, mUserInfoStore.getCurrentUserId());
for (ResolveInfo rInfo : rInfos) {
String packageName = rInfo.serviceInfo.packageName;
@@ -752,27 +729,18 @@
}
@GuardedBy("mLock")
- private void onUserChangedLocked(int userId) {
- if (mCurrentUserId == userId) {
- return;
- }
-
+ private void onUserChangedLocked(int oldUserId, int newUserId) {
if (D) {
- Log.d(TAG, "foreground user is changing to " + userId);
+ Log.d(TAG, "foreground user is changing to " + newUserId);
}
- int oldUserId = mCurrentUserId;
- mCurrentUserId = userId;
- onUserProfilesChangedLocked();
-
- // let providers know the current user has changed
for (LocationProviderManager manager : mProviderManagers) {
// update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
mSettingsStore.setLocationProviderAllowed(manager.getName(),
- manager.isUseable(mCurrentUserId), mCurrentUserId);
+ manager.isUseable(newUserId), newUserId);
manager.onUseableChangedLocked(oldUserId);
- manager.onUseableChangedLocked(mCurrentUserId);
+ manager.onUseableChangedLocked(newUserId);
}
}
@@ -786,8 +754,12 @@
// acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
protected final MockableLocationProvider mProvider;
+ // useable state for parent user ids, no entry implies false. location state is only kept
+ // for parent user ids, the location state for a profile user id is assumed to be the same
+ // as for the parent. if querying this structure, ensure that the user id being used is a
+ // parent id or the results may be incorrect.
@GuardedBy("mLock")
- private final SparseArray<Boolean> mUseable; // combined state for each user id
+ private final SparseArray<Boolean> mUseable;
private LocationProviderManager(String name) {
mName = name;
@@ -795,6 +767,9 @@
// initialize last since this lets our reference escape
mProvider = new MockableLocationProvider(mContext, mLock, this);
+
+ // we can assume all users start with unuseable location state since the initial state
+ // of all providers is disabled. no need to initialize mUseable further.
}
public String getName() {
@@ -868,29 +843,6 @@
mProvider.sendExtraCommand(uid, pid, command, extras);
}
- public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
- synchronized (mLock) {
- pw.print(mName + " provider");
- if (mProvider.isMock()) {
- pw.print(" [mock]");
- }
- pw.println(":");
-
- pw.increaseIndent();
-
- pw.println("useable=" + isUseable(mCurrentUserId));
- if (!isUseable(mCurrentUserId)) {
- pw.println("enabled=" + mProvider.getState().enabled);
- }
-
- pw.println("properties=" + mProvider.getState().properties);
- }
-
- mProvider.dump(fd, pw, args);
-
- pw.decreaseIndent();
- }
-
@GuardedBy("mLock")
@Override
public void onReportLocation(Location location) {
@@ -927,18 +879,20 @@
// it would be more correct to call this for all users, but we know this can
// only affect the current user since providers are disabled for non-current
// users
- onUseableChangedLocked(mCurrentUserId);
+ onUseableChangedLocked(mUserInfoStore.getCurrentUserId());
}
}
- @GuardedBy("mLock")
public boolean isUseable() {
- return isUseable(mCurrentUserId);
+ return isUseable(mUserInfoStore.getCurrentUserId());
}
- @GuardedBy("mLock")
public boolean isUseable(int userId) {
synchronized (mLock) {
+ // normalize user id to always refer to parent since profile state is always the
+ // same as parent state
+ userId = mUserInfoStore.getParentUserId(userId);
+
return mUseable.get(userId, Boolean.FALSE);
}
}
@@ -950,15 +904,20 @@
return;
}
+ // normalize user id to always refer to parent since profile state is always the same
+ // as parent state
+ userId = mUserInfoStore.getParentUserId(userId);
+
// if any property that contributes to "useability" here changes state, it MUST result
// in a direct or indrect call to onUseableChangedLocked. this allows the provider to
// guarantee that it will always eventually reach the correct state.
- boolean useable = isCurrentProfileLocked(userId)
+ boolean useable = (userId == mUserInfoStore.getCurrentUserId())
&& mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled;
if (useable == isUseable(userId)) {
return;
}
+
mUseable.put(userId, useable);
if (D) {
@@ -986,6 +945,30 @@
updateProviderUseableLocked(this);
}
+
+ public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+ synchronized (mLock) {
+ pw.print(mName + " provider");
+ if (mProvider.isMock()) {
+ pw.print(" [mock]");
+ }
+ pw.println(":");
+
+ pw.increaseIndent();
+
+ boolean useable = isUseable();
+ pw.println("useable=" + useable);
+ if (!useable) {
+ pw.println("enabled=" + mProvider.getState().enabled);
+ }
+
+ pw.println("properties=" + mProvider.getState().properties);
+ }
+
+ mProvider.dump(fd, pw, args);
+
+ pw.decreaseIndent();
+ }
}
class PassiveLocationProviderManager extends LocationProviderManager {
@@ -1626,7 +1609,7 @@
ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
if (records != null) {
for (UpdateRecord record : records) {
- if (!isCurrentProfileLocked(
+ if (!mUserInfoStore.isCurrentUserOrProfile(
UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
continue;
}
@@ -1691,7 +1674,7 @@
// initialize the low power mode to true and set to false if any of the records requires
providerRequest.setLowPowerMode(true);
for (UpdateRecord record : records) {
- if (!isCurrentProfileLocked(
+ if (!mUserInfoStore.isCurrentUserOrProfile(
UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
continue;
}
@@ -1750,7 +1733,7 @@
// TODO: overflow
long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2;
for (UpdateRecord record : records) {
- if (isCurrentProfileLocked(
+ if (mUserInfoStore.isCurrentUserOrProfile(
UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
LocationRequest locationRequest = record.mRequest;
@@ -2243,8 +2226,8 @@
if (manager == null) return null;
// only the current user or location providers may get location this way
- if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage(
- packageName)) {
+ if (!mUserInfoStore.isCurrentUserOrProfile(UserHandle.getUserId(uid))
+ && !isProviderPackage(packageName)) {
return null;
}
@@ -2773,12 +2756,11 @@
}
int receiverUserId = UserHandle.getUserId(receiver.mCallerIdentity.mUid);
- if (!isCurrentProfileLocked(receiverUserId)
+ if (!mUserInfoStore.isCurrentUserOrProfile(receiverUserId)
&& !isProviderPackage(receiver.mCallerIdentity.mPackageName)) {
if (D) {
Log.d(TAG, "skipping loc update for background user " + receiverUserId +
- " (current user: " + mCurrentUserId + ", app: " +
- receiver.mCallerIdentity.mPackageName + ")");
+ " (app: " + receiver.mCallerIdentity.mPackageName + ")");
}
continue;
}
@@ -3028,9 +3010,17 @@
+ TimeUtils.logTimeOfDay(System.currentTimeMillis()));
ipw.println(", Current Elapsed Time: "
+ TimeUtils.formatDuration(SystemClock.elapsedRealtime()));
- ipw.println("Current user: " + mCurrentUserId + " " + Arrays.toString(
- mCurrentUserProfiles));
- ipw.println("Location Mode: " + isLocationEnabledForUser(mCurrentUserId));
+
+ ipw.println("User Info:");
+ ipw.increaseIndent();
+ mUserInfoStore.dump(fd, ipw, args);
+ ipw.decreaseIndent();
+
+ ipw.println("Location Settings:");
+ ipw.increaseIndent();
+ mSettingsStore.dump(fd, ipw, args);
+ ipw.decreaseIndent();
+
ipw.println("Battery Saver Location Mode: "
+ locationPowerSaveModeToString(mBatterySaverMode));
@@ -3096,21 +3086,14 @@
mLocationFudger.dump(fd, ipw, args);
ipw.decreaseIndent();
}
- }
- ipw.println("Location Settings:");
- ipw.increaseIndent();
- mSettingsStore.dump(fd, ipw, args);
- ipw.decreaseIndent();
+ ipw.println("Location Providers:");
+ ipw.increaseIndent();
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.dump(fd, ipw, args);
+ }
+ ipw.decreaseIndent();
- ipw.println("Location Providers:");
- ipw.increaseIndent();
- for (LocationProviderManager manager : mProviderManagers) {
- manager.dump(fd, ipw, args);
- }
- ipw.decreaseIndent();
-
- synchronized (mLock) {
if (mGnssManagerService != null) {
ipw.println("GNSS:");
ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index b26ef92..d1d1cb3 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -523,7 +523,7 @@
@Override
public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
- int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
+ int filterType = NetworkScoreManager.SCORE_FILTER_NONE;
if (cookie instanceof Integer) {
filterType = (Integer) cookie;
}
@@ -547,17 +547,17 @@
private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
int filterType) {
switch (filterType) {
- case NetworkScoreManager.CACHE_FILTER_NONE:
+ case NetworkScoreManager.SCORE_FILTER_NONE:
return scoredNetworkList;
- case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
+ case NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK:
if (mCurrentNetworkFilter == null) {
mCurrentNetworkFilter =
new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
}
return mCurrentNetworkFilter.apply(scoredNetworkList);
- case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
+ case NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS:
if (mScanResultsFilter == null) {
mScanResultsFilter = new ScanResultsScoreCacheFilter(
new ScanResultsSupplier(mContext));
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
index d20936c..7894788 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
@@ -154,17 +154,20 @@
private void onPollNetworkTimeUnderWakeLock(int event) {
// Force an NTP fix when outdated
- if (mTime.getCacheAge() >= mPollingIntervalMs) {
+ NtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult();
+ if (cachedNtpResult == null || cachedNtpResult.getAgeMillis() >= mPollingIntervalMs) {
if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");
mTime.forceRefresh();
+ cachedNtpResult = mTime.getCachedTimeResult();
}
- if (mTime.getCacheAge() < mPollingIntervalMs) {
+ if (cachedNtpResult != null && cachedNtpResult.getAgeMillis() < mPollingIntervalMs) {
// Obtained fresh fix; schedule next normal update
resetAlarm(mPollingIntervalMs);
// Suggest the time to the time detector. It may choose use it to set the system clock.
- TimestampedValue<Long> timeSignal = mTime.getCachedNtpTimeSignal();
+ TimestampedValue<Long> timeSignal = new TimestampedValue<>(
+ cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis());
NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateServiceImpl. event=" + event);
mTimeDetector.suggestNetworkTime(timeSuggestion);
@@ -275,8 +278,11 @@
TimeUtils.formatDuration(mPollingIntervalShorterMs, pw);
pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax);
pw.println("\nTryAgainCounter: " + mTryAgainCounter);
- pw.println("NTP cache age: " + mTime.getCacheAge());
- pw.println("NTP cache certainty: " + mTime.getCacheCertainty());
+ NtpTrustedTime.TimeResult ntpResult = mTime.getCachedTimeResult();
+ pw.println("NTP cache result: " + ntpResult);
+ if (ntpResult != null) {
+ pw.println("NTP result age: " + ntpResult.getAgeMillis());
+ }
pw.println();
}
}
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index b9b7bf7..4a1820a 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -22,10 +22,10 @@
import android.database.ContentObserver;
import android.net.NetworkStack;
import android.net.Uri;
-import android.net.nsd.DnsSdTxtRecord;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.net.util.nsd.DnsSdTxtRecord;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 190fff1..21fa9f9 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -46,4 +46,7 @@
/** Update the OEM unlock enabled bit, bypassing user restriction checks. */
void forceOemUnlockEnabled(boolean enabled);
+
+ /** Retrieves the UID that can access the persistent data partition. */
+ int getAllowedUid();
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 73c8520..00d8b0f 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -680,6 +680,11 @@
writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
}
+ @Override
+ public int getAllowedUid() {
+ return mAllowedUid;
+ }
+
private void writeInternal(byte[] data, long offset, int dataLength) {
checkArgument(data == null || data.length > 0, "data must be null or non-empty");
checkArgument(
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index db54214..396b977 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2855,8 +2855,10 @@
*/
@Override
public void startCheckpoint(int numTries) throws RemoteException {
- // Only the system process is permitted to start checkpoints
- if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+ // Only the root, system_server and shell processes are permitted to start checkpoints
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID
+ && callingUid != Process.SHELL_UID) {
throw new SecurityException("no permission to start filesystem checkpoint");
}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index c5409f8..b1584fe 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -18,18 +18,26 @@
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.annotation.SystemApi.Process;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.IBinder;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.os.UserManager;
import com.android.server.pm.UserManagerService;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -57,15 +65,17 @@
*
* {@hide}
*/
+@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
public abstract class SystemService {
+ /** @hide */
// TODO(b/133242016) STOPSHIP: change to false before R ships
protected static final boolean DEBUG_USER = true;
/*
- * Boot Phases
+ * The earliest boot phase the system send to system services on boot.
*/
- public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // maybe should be a dependency?
+ public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100;
/**
* After receiving this boot phase, services can obtain lock settings data.
@@ -98,13 +108,70 @@
* After receiving this boot phase, services can allow user interaction with the device.
* This phase occurs when boot has completed and the home application has started.
* System services may prefer to listen to this phase rather than registering a
- * broadcast receiver for ACTION_BOOT_COMPLETED to reduce overall latency.
+ * broadcast receiver for {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED}
+ * to reduce overall latency.
*/
public static final int PHASE_BOOT_COMPLETED = 1000;
+ /** @hide */
+ @IntDef(flag = true, prefix = { "PHASE_" }, value = {
+ PHASE_WAIT_FOR_DEFAULT_DISPLAY,
+ PHASE_LOCK_SETTINGS_READY,
+ PHASE_SYSTEM_SERVICES_READY,
+ PHASE_DEVICE_SPECIFIC_SERVICES_READY,
+ PHASE_ACTIVITY_MANAGER_READY,
+ PHASE_THIRD_PARTY_APPS_CAN_START,
+ PHASE_BOOT_COMPLETED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BootPhase {}
+
private final Context mContext;
/**
+ * Class representing user in question in the lifecycle callbacks.
+ * @hide
+ */
+ @SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
+ public static final class TargetUser {
+ @NonNull
+ private final UserInfo mUserInfo;
+
+ /** @hide */
+ public TargetUser(@NonNull UserInfo userInfo) {
+ mUserInfo = userInfo;
+ }
+
+ /**
+ * @return The information about the user. <b>NOTE: </b> this is a "live" object
+ * referenced by {@link UserManagerService} and hence should not be modified.
+ *
+ * @hide
+ */
+ @NonNull
+ public UserInfo getUserInfo() {
+ return mUserInfo;
+ }
+
+ /**
+ * @return the target {@link UserHandle}.
+ */
+ @NonNull
+ public UserHandle getUserHandle() {
+ return mUserInfo.getUserHandle();
+ }
+
+ /**
+ * @return the integer user id
+ *
+ * @hide
+ */
+ public int getUserIdentifier() {
+ return mUserInfo.id;
+ }
+ }
+
+ /**
* Initializes the system service.
* <p>
* Subclasses must define a single argument constructor that accepts the context
@@ -113,13 +180,14 @@
*
* @param context The system server context.
*/
- public SystemService(Context context) {
+ public SystemService(@NonNull Context context) {
mContext = context;
}
/**
* Gets the system context.
*/
+ @NonNull
public final Context getContext() {
return mContext;
}
@@ -128,6 +196,8 @@
* Get the system UI context. This context is to be used for displaying UI. It is themable,
* which means resources can be overridden at runtime. Do not use to retrieve properties that
* configure the behavior of the device that is not UX related.
+ *
+ * @hide
*/
public final Context getUiContext() {
// This has already been set up by the time any SystemServices are created.
@@ -137,15 +207,16 @@
/**
* Returns true if the system is running in safe mode.
* TODO: we should define in which phase this becomes valid
+ *
+ * @hide
*/
public final boolean isSafeMode() {
return getManager().isSafeMode();
}
/**
- * Called when the dependencies listed in the @Service class-annotation are available
- * and after the chosen start phase.
- * When this method returns, the service should be published.
+ * Called when the system service should publish a binder service using
+ * {@link #publishBinderService(String, IBinder).}
*/
public abstract void onStart();
@@ -155,7 +226,7 @@
*
* @param phase The current boot phase.
*/
- public void onBootPhase(int phase) {}
+ public void onBootPhase(@BootPhase int phase) {}
/**
* Checks if the service should be available for the given user.
@@ -163,12 +234,14 @@
* <p>By default returns {@code true}, but subclasses should extend for optimization, if they
* don't support some types (like headless system user).
*/
- public boolean isSupported(@NonNull UserInfo userInfo) {
+ public boolean isSupportedUser(@NonNull TargetUser user) {
return true;
}
/**
- * Helper method used to dump which users are {@link #onStartUser(UserInfo) supported}.
+ * Helper method used to dump which users are {@link #onStartUser(TargetUser) supported}.
+ *
+ * @hide
*/
protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) {
final List<UserInfo> allUsers = UserManager.get(mContext).getUsers();
@@ -187,34 +260,59 @@
}
/**
- * @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default
- * calls this method).
+ * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onStartUser(@UserIdInt int userId) {}
/**
- * Called when a new user is starting, for system services to initialize any per-user
- * state they maintain for running users.
+ * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead
+ * (which by default calls this method).
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
- *
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @hide
*/
+ @Deprecated
public void onStartUser(@NonNull UserInfo userInfo) {
onStartUser(userInfo.id);
}
/**
- * @deprecated subclasses should extend {@link #onUnlockUser(UserInfo)} instead (which by
+ * Called when a new user is starting, for system services to initialize any per-user
+ * state they maintain for running users.
+ *
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
+ *
+ * @param user target user
+ */
+ public void onStartUser(@NonNull TargetUser user) {
+ onStartUser(user.getUserInfo());
+ }
+
+ /**
+ * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by
* default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onUnlockUser(@UserIdInt int userId) {}
/**
+ * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by
+ * default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onUnlockUser(@NonNull UserInfo userInfo) {
+ onUnlockUser(userInfo.id);
+ }
+
+ /**
* Called when an existing user is in the process of being unlocked. This
* means the credential-encrypted storage for that user is now available,
* and encryption-aware component filtering is no longer in effect.
@@ -226,90 +324,127 @@
* {@link UserManager#isUserUnlockingOrUnlocked(int)} to handle both of
* these states.
* <p>
- * This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
+ * This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
*
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @param user target user
*/
- public void onUnlockUser(@NonNull UserInfo userInfo) {
- onUnlockUser(userInfo.id);
+ public void onUnlockUser(@NonNull TargetUser user) {
+ onUnlockUser(user.getUserInfo());
}
/**
- * @deprecated subclasses should extend {@link #onSwitchUser(UserInfo, UserInfo)} instead
+ * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead
* (which by default calls this method).
+ *
+ * @hide
*/
@Deprecated
- public void onSwitchUser(@UserIdInt int userId) {}
+ public void onSwitchUser(@UserIdInt int toUserId) {}
+
+ /**
+ * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onSwitchUser(@Nullable UserInfo from, @NonNull UserInfo to) {
+ onSwitchUser(to.id);
+ }
/**
* Called when switching to a different foreground user, for system services that have
* special behavior for whichever user is currently in the foreground. This is called
* before any application processes are aware of the new user.
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} either
- * of the users ({@code from} or {@code to}).
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * either of the users ({@code from} or {@code to}).
*
* <b>NOTE: </b> both {@code from} and {@code to} are "live" objects
* referenced by {@link UserManagerService} and hence should not be modified.
*
- * @param from The information about the user being switched from.
- * @param to The information about the user being switched from to.
+ * @param from the user switching from
+ * @param to the user switching to
*/
- public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) {
- onSwitchUser(to.id);
+ public void onSwitchUser(@Nullable TargetUser from, @NonNull TargetUser to) {
+ onSwitchUser((from == null ? null : from.getUserInfo()), to.getUserInfo());
}
/**
- * @deprecated subclasses should extend {@link #onStopUser(UserInfo)} instead (which by default
- * calls this method).
+ * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onStopUser(@UserIdInt int userId) {}
/**
+ * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onStopUser(@NonNull UserInfo user) {
+ onStopUser(user.id);
+
+ }
+
+ /**
* Called when an existing user is stopping, for system services to finalize any per-user
* state they maintain for running users. This is called prior to sending the SHUTDOWN
* broadcast to the user; it is a good place to stop making use of any resources of that
* user (such as binding to a service running in the user).
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
*
* <p>NOTE: This is the last callback where the callee may access the target user's CE storage.
*
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @param user target user
*/
- public void onStopUser(@NonNull UserInfo userInfo) {
- onStopUser(userInfo.id);
+ public void onStopUser(@NonNull TargetUser user) {
+ onStopUser(user.getUserInfo());
}
/**
- * @deprecated subclasses should extend {@link #onCleanupUser(UserInfo)} instead (which by
+ * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by
* default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onCleanupUser(@UserIdInt int userId) {}
/**
+ * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by
+ * default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onCleanupUser(@NonNull UserInfo user) {
+ onCleanupUser(user.id);
+ }
+
+ /**
* Called when an existing user is stopping, for system services to finalize any per-user
* state they maintain for running users. This is called after all application process
* teardown of the user is complete.
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
*
* <p>NOTE: When this callback is called, the CE storage for the target user may not be
- * accessible already. Use {@link #onStopUser(UserInfo)} instead if you need to access the CE
+ * accessible already. Use {@link #onStopUser(TargetUser)} instead if you need to access the CE
* storage.
*
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @param user target user
*/
- public void onCleanupUser(@NonNull UserInfo userInfo) {
- onCleanupUser(userInfo.id);
+ public void onCleanupUser(@NonNull TargetUser user) {
+ onCleanupUser(user.getUserInfo());
}
/**
@@ -318,7 +453,7 @@
* @param name the name of the new service
* @param service the service object
*/
- protected final void publishBinderService(String name, IBinder service) {
+ protected final void publishBinderService(@NonNull String name, @NonNull IBinder service) {
publishBinderService(name, service, false);
}
@@ -330,7 +465,7 @@
* @param allowIsolated set to true to allow isolated sandboxed processes
* to access this service
*/
- protected final void publishBinderService(String name, IBinder service,
+ protected final void publishBinderService(@NonNull String name, @NonNull IBinder service,
boolean allowIsolated) {
publishBinderService(name, service, allowIsolated, DUMP_FLAG_PRIORITY_DEFAULT);
}
@@ -343,6 +478,8 @@
* @param allowIsolated set to true to allow isolated sandboxed processes
* to access this service
* @param dumpPriority supported dump priority levels as a bitmask
+ *
+ * @hide
*/
protected final void publishBinderService(String name, IBinder service,
boolean allowIsolated, int dumpPriority) {
@@ -351,6 +488,8 @@
/**
* Get a binder service by its name.
+ *
+ * @hide
*/
protected final IBinder getBinderService(String name) {
return ServiceManager.getService(name);
@@ -358,6 +497,8 @@
/**
* Publish the service so it is only accessible to the system process.
+ *
+ * @hide
*/
protected final <T> void publishLocalService(Class<T> type, T service) {
LocalServices.addService(type, service);
@@ -365,6 +506,8 @@
/**
* Get a local service by interface.
+ *
+ * @hide
*/
protected final <T> T getLocalService(Class<T> type) {
return LocalServices.getService(type);
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index c715798..e7f7846 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -25,10 +25,14 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManagerInternal;
+import android.util.ArrayMap;
import android.util.Slog;
+import com.android.server.SystemService.TargetUser;
import com.android.server.utils.TimingsTraceAndSlog;
+import dalvik.system.PathClassLoader;
+
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -62,6 +66,9 @@
// Services that should receive lifecycle events.
private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
+ // Map of paths to PathClassLoader, so we don't load the same path multiple times.
+ private final ArrayMap<String, PathClassLoader> mLoadedPaths = new ArrayMap<>();
+
private int mCurrentPhase = -1;
private UserManagerInternal mUserManagerInternal;
@@ -75,20 +82,46 @@
*
* @return The service instance.
*/
- @SuppressWarnings("unchecked")
public SystemService startService(String className) {
- final Class<SystemService> serviceClass;
+ final Class<SystemService> serviceClass = loadClassFromLoader(className,
+ this.getClass().getClassLoader());
+ return startService(serviceClass);
+ }
+
+ /**
+ * Starts a service by class name and a path that specifies the jar where the service lives.
+ *
+ * @return The service instance.
+ */
+ public SystemService startServiceFromJar(String className, String path) {
+ PathClassLoader pathClassLoader = mLoadedPaths.get(path);
+ if (pathClassLoader == null) {
+ // NB: the parent class loader should always be the system server class loader.
+ // Changing it has implications that require discussion with the mainline team.
+ pathClassLoader = new PathClassLoader(path, this.getClass().getClassLoader());
+ mLoadedPaths.put(path, pathClassLoader);
+ }
+ final Class<SystemService> serviceClass = loadClassFromLoader(className, pathClassLoader);
+ return startService(serviceClass);
+ }
+
+ /*
+ * Loads and initializes a class from the given classLoader. Returns the class.
+ */
+ @SuppressWarnings("unchecked")
+ private static Class<SystemService> loadClassFromLoader(String className,
+ ClassLoader classLoader) {
try {
- serviceClass = (Class<SystemService>)Class.forName(className);
+ return (Class<SystemService>) Class.forName(className, true, classLoader);
} catch (ClassNotFoundException ex) {
- Slog.i(TAG, "Starting " + className);
throw new RuntimeException("Failed to create service " + className
- + ": service class not found, usually indicates that the caller should "
+ + " from class loader " + classLoader.toString() + ": service class not "
+ + "found, usually indicates that the caller should "
+ "have called PackageManager.hasSystemFeature() to check whether the "
+ "feature is available on this device before trying to start the "
- + "services that implement it", ex);
+ + "services that implement it. Also ensure that the correct path for the "
+ + "classloader is supplied, if applicable.", ex);
}
- return startService(serviceClass);
}
/**
@@ -264,26 +297,26 @@
@UserIdInt int curUserId, @UserIdInt int prevUserId) {
t.traceBegin("ssm." + onWhat + "User-" + curUserId);
Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId);
- final UserInfo curUserInfo = getUserInfo(curUserId);
- final UserInfo prevUserInfo = prevUserId == UserHandle.USER_NULL ? null
- : getUserInfo(prevUserId);
+ final TargetUser curUser = new TargetUser(getUserInfo(curUserId));
+ final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null
+ : new TargetUser(getUserInfo(prevUserId));
final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
final String serviceName = service.getClass().getName();
- boolean supported = service.isSupported(curUserInfo);
+ boolean supported = service.isSupportedUser(curUser);
// Must check if either curUser or prevUser is supported (for example, if switching from
// unsupported to supported, we still need to notify the services)
- if (!supported && prevUserInfo != null) {
- supported = service.isSupported(prevUserInfo);
+ if (!supported && prevUser != null) {
+ supported = service.isSupportedUser(prevUser);
}
if (!supported) {
if (DEBUG) {
Slog.d(TAG, "Skipping " + onWhat + "User-" + curUserId + " on service "
+ serviceName + " because it's not supported (curUser: "
- + curUserInfo + ", prevUser:" + prevUserInfo + ")");
+ + curUser + ", prevUser:" + prevUser + ")");
} else {
Slog.i(TAG, "Skipping " + onWhat + "User-" + curUserId + " on "
+ serviceName);
@@ -295,25 +328,25 @@
try {
switch (onWhat) {
case SWITCH:
- service.onSwitchUser(prevUserInfo, curUserInfo);
+ service.onSwitchUser(prevUser, curUser);
break;
case START:
- service.onStartUser(curUserInfo);
+ service.onStartUser(curUser);
break;
case UNLOCK:
- service.onUnlockUser(curUserInfo);
+ service.onUnlockUser(curUser);
break;
case STOP:
- service.onStopUser(curUserInfo);
+ service.onStopUser(curUser);
break;
case CLEANUP:
- service.onCleanupUser(curUserInfo);
+ service.onCleanupUser(curUser);
break;
default:
throw new IllegalArgumentException(onWhat + " what?");
}
} catch (Exception ex) {
- Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUserInfo
+ Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUser
+ " to service " + serviceName, ex);
}
warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 3e6ccb5..4f03a8e 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -46,6 +46,7 @@
import android.telephony.Annotation.DataFailureCause;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SrvccState;
+import android.telephony.BarringInfo;
import android.telephony.CallAttributes;
import android.telephony.CallQuality;
import android.telephony.CellIdentity;
@@ -83,7 +84,6 @@
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.ITelephonyRegistry;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -255,6 +255,8 @@
private int[] mCallPreciseDisconnectCause;
+ private List<BarringInfo> mBarringInfo = null;
+
private boolean mCarrierNetworkChangeState = false;
private PhoneCapability mPhoneCapability = null;
@@ -437,6 +439,7 @@
cutListToSize(mCellInfo, mNumPhones);
cutListToSize(mImsReasonInfo, mNumPhones);
cutListToSize(mPreciseDataConnectionStates, mNumPhones);
+ cutListToSize(mBarringInfo, mNumPhones);
return;
}
@@ -468,6 +471,7 @@
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
+ mBarringInfo.add(i, new BarringInfo());
}
}
@@ -525,6 +529,7 @@
mEmergencyNumberList = new HashMap<>();
mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
+ mBarringInfo = new ArrayList<>();
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
@@ -552,6 +557,7 @@
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
+ mBarringInfo.add(i, new BarringInfo());
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -994,6 +1000,19 @@
remove(r.binder);
}
}
+ if ((events & PhoneStateListener.LISTEN_BARRING_INFO) != 0) {
+ BarringInfo barringInfo = mBarringInfo.get(phoneId);
+ BarringInfo biNoLocation = barringInfo != null
+ ? barringInfo.createLocationInfoSanitizedCopy() : null;
+ if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
+ try {
+ r.callback.onBarringInfoChanged(
+ checkFineLocationAccess(r, Build.VERSION_CODES.R)
+ ? barringInfo : biNoLocation);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
} else {
@@ -2103,6 +2122,52 @@
}
}
+ /**
+ * Send a notification of changes to barring status to PhoneStateListener registrants.
+ *
+ * @param phoneId the phoneId
+ * @param subId the subId
+ * @param barringInfo a structure containing the complete updated barring info.
+ */
+ public void notifyBarringInfoChanged(int phoneId, int subId, @NonNull BarringInfo barringInfo) {
+ if (!checkNotifyPermission("notifyBarringInfo()")) {
+ return;
+ }
+ if (barringInfo == null) {
+ log("Received null BarringInfo for subId=" + subId + ", phoneId=" + phoneId);
+ mBarringInfo.set(phoneId, new BarringInfo());
+ return;
+ }
+
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ mBarringInfo.set(phoneId, barringInfo);
+ // Barring info is non-null
+ BarringInfo biNoLocation = barringInfo.createLocationInfoSanitizedCopy();
+ if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_BARRING_INFO)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ if (DBG_LOC) {
+ log("notifyBarringInfo: mBarringInfo="
+ + barringInfo + " r=" + r);
+ }
+ r.callback.onBarringInfoChanged(
+ checkFineLocationAccess(r, Build.VERSION_CODES.R)
+ ? barringInfo : biNoLocation);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -2143,6 +2208,7 @@
pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
+ pw.println("mBarringInfo=" + mBarringInfo.get(i));
pw.decreaseIndent();
}
pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
@@ -2194,6 +2260,17 @@
private static final String PHONE_CONSTANTS_STATE_KEY = "state";
private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription";
+ /**
+ * Broadcast Action: The phone's signal strength has changed. The intent will have the
+ * following extra values:
+ * phoneName - A string version of the phone name.
+ * asu - A numeric value for the signal strength.
+ * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
+ * The following special values are defined:
+ * 0 means "-113 dBm or less".31 means "-51 dBm or greater".
+ */
+ public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
+
private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
long ident = Binder.clearCallingIdentity();
try {
@@ -2228,7 +2305,7 @@
Binder.restoreCallingIdentity(ident);
}
- Intent intent = new Intent(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
+ Intent intent = new Intent(ACTION_SIGNAL_STRENGTH_CHANGED);
Bundle data = new Bundle();
fillInSignalStrengthNotifierBundle(signalStrength, data);
intent.putExtras(data);
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index c27b0da..ed3bab9 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -218,7 +218,7 @@
// Has to be in TestNetworkAgent to ensure all teardown codepaths properly clean up
// resources, even for binder death or unwanted calls.
synchronized (mTestNetworkTracker) {
- mTestNetworkTracker.remove(netId);
+ mTestNetworkTracker.remove(network.netId);
}
}
}
@@ -337,7 +337,7 @@
callingUid,
binder);
- mTestNetworkTracker.put(agent.netId, agent);
+ mTestNetworkTracker.put(agent.network.netId, agent);
}
} catch (SocketException e) {
throw new UncheckedIOException(e);
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 27e0d52..5a56a9f 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -61,7 +61,6 @@
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.DebugUtils;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.StatsLog;
@@ -164,8 +163,7 @@
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
private int mRingIntensity;
- private SparseArray<Pair<VibrationEffect, VibrationAttributes>> mAlwaysOnEffects =
- new SparseArray<>();
+ private SparseArray<Vibration> mAlwaysOnEffects = new SparseArray<>();
static native boolean vibratorExists();
static native void vibratorInit();
@@ -461,6 +459,10 @@
Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY),
true, mSettingObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
+ true, mSettingObserver, UserHandle.USER_ALL);
+
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -508,7 +510,8 @@
}
@Override // Binder call
- public boolean setAlwaysOnEffect(int id, VibrationEffect effect, VibrationAttributes attrs) {
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
+ VibrationAttributes attrs) {
if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
}
@@ -518,8 +521,8 @@
}
if (effect == null) {
synchronized (mLock) {
- mAlwaysOnEffects.delete(id);
- vibratorAlwaysOnDisable(id);
+ mAlwaysOnEffects.delete(alwaysOnId);
+ vibratorAlwaysOnDisable(alwaysOnId);
}
} else {
if (!verifyVibrationEffect(effect)) {
@@ -529,13 +532,11 @@
Slog.e(TAG, "Only prebaked effects supported for always-on.");
return false;
}
- if (attrs == null) {
- attrs = new VibrationAttributes.Builder()
- .build();
- }
+ attrs = fixupVibrationAttributes(attrs);
synchronized (mLock) {
- mAlwaysOnEffects.put(id, Pair.create(effect, attrs));
- updateAlwaysOnLocked(id, effect, attrs);
+ Vibration vib = new Vibration(null, effect, attrs, uid, opPkg, null);
+ mAlwaysOnEffects.put(alwaysOnId, vib);
+ updateAlwaysOnLocked(alwaysOnId, vib);
}
}
return true;
@@ -575,6 +576,23 @@
return true;
}
+ private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) {
+ if (attrs == null) {
+ attrs = DEFAULT_ATTRIBUTES;
+ }
+ if (shouldBypassDnd(attrs)) {
+ if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
+ final int flags = attrs.getFlags()
+ & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+ attrs = new VibrationAttributes.Builder(attrs).replaceFlags(flags).build();
+ }
+ }
+
+ return attrs;
+ }
+
private static long[] getLongIntArray(Resources r, int resid) {
int[] ar = r.getIntArray(resid);
if (ar == null) {
@@ -604,19 +622,7 @@
return;
}
- if (attrs == null) {
- attrs = DEFAULT_ATTRIBUTES;
- }
-
- if (shouldBypassDnd(attrs)) {
- if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
- final int flags = attrs.getFlags()
- & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
- attrs = new VibrationAttributes.Builder(attrs).replaceFlags(flags).build();
- }
- }
+ attrs = fixupVibrationAttributes(attrs);
// If our current vibration is longer than the new vibration and is the same amplitude,
// then just let the current one finish.
@@ -777,29 +783,8 @@
private void startVibrationLocked(final Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
- if (!isAllowedToVibrateLocked(vib)) {
- return;
- }
-
final int intensity = getCurrentIntensityLocked(vib);
- if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
- return;
- }
-
- if (vib.isRingtone() && !shouldVibrateForRingtone()) {
- if (DEBUG) {
- Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
- }
- return;
- }
-
- final int mode = getAppOpMode(vib);
- if (mode != AppOpsManager.MODE_ALLOWED) {
- if (mode == AppOpsManager.MODE_ERRORED) {
- // We might be getting calls from within system_server, so we don't actually
- // want to throw a SecurityException here.
- Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
- }
+ if (!shouldVibrate(vib, intensity)) {
return;
}
applyVibrationIntensityScalingLocked(vib, intensity);
@@ -958,6 +943,35 @@
return mode;
}
+ private boolean shouldVibrate(Vibration vib, int intensity) {
+ if (!isAllowedToVibrateLocked(vib)) {
+ return false;
+ }
+
+ if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ return false;
+ }
+
+ if (vib.isRingtone() && !shouldVibrateForRingtone()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
+ }
+ return false;
+ }
+
+ final int mode = getAppOpMode(vib);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (mode == AppOpsManager.MODE_ERRORED) {
+ // We might be getting calls from within system_server, so we don't actually
+ // want to throw a SecurityException here.
+ Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
@GuardedBy("mLock")
private void reportFinishVibrationLocked() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
@@ -1069,14 +1083,12 @@
mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT);
}
- private void updateAlwaysOnLocked(int id, VibrationEffect effect, VibrationAttributes attrs) {
- // TODO: Check DND and LowPower settings
- final Vibration vib = new Vibration(null, effect, attrs, 0, null, null);
+ private void updateAlwaysOnLocked(int id, Vibration vib) {
final int intensity = getCurrentIntensityLocked(vib);
- if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ if (!shouldVibrate(vib, intensity)) {
vibratorAlwaysOnDisable(id);
} else {
- final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+ final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
final int strength = intensityToEffectStrength(intensity);
vibratorAlwaysOnEnable(id, prebaked.getId(), strength);
}
@@ -1085,8 +1097,8 @@
private void updateAlwaysOnLocked() {
for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
int id = mAlwaysOnEffects.keyAt(i);
- Pair<VibrationEffect, VibrationAttributes> pair = mAlwaysOnEffects.valueAt(i);
- updateAlwaysOnLocked(id, pair.first, pair.second);
+ Vibration vib = mAlwaysOnEffects.valueAt(i);
+ updateAlwaysOnLocked(id, vib);
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index a372fca0..debc2a1 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2129,16 +2129,6 @@
}
@Override
- public void removeAccount(IAccountManagerResponse response, Account account,
- boolean expectActivityLaunch) {
- removeAccountAsUser(
- response,
- account,
- expectActivityLaunch,
- UserHandle.getCallingUserId());
- }
-
- @Override
public void removeAccountAsUser(IAccountManagerResponse response, Account account,
boolean expectActivityLaunch, int userId) {
final int callingUid = Binder.getCallingUid();
@@ -4454,12 +4444,6 @@
@Override
@NonNull
- public Account[] getAccounts(String type, String opPackageName) {
- return getAccountsAsUser(type, UserHandle.getCallingUserId(), opPackageName);
- }
-
- @Override
- @NonNull
public Account[] getAccountsForPackage(String packageName, int uid, String opPackageName) {
int callingUid = Binder.getCallingUid();
if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a58bd9b..e2a036a 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2068,7 +2068,7 @@
+ " type=" + resolvedType + " callingUid=" + callingUid);
userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
- ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service",
+ ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE, "service",
callingPackage);
ServiceMap smap = getServiceMapLocked(userId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c21adb0..883e7c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2555,7 +2555,7 @@
Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
Process.THREAD_GROUP_SYSTEM);
Process.setThreadGroupAndCpuset(
- mOomAdjuster.mAppCompact.mCompactionThread.getThreadId(),
+ mOomAdjuster.mCachedAppOptimizer.mCachedAppOptimizerThread.getThreadId(),
Process.THREAD_GROUP_SYSTEM);
} catch (Exception e) {
Slog.w(TAG, "Setting background thread cpuset failed");
@@ -5304,7 +5304,7 @@
String data, Bundle extras, boolean ordered,
boolean sticky, int sendingUser) {
synchronized (ActivityManagerService.this) {
- mOomAdjuster.mAppCompact.compactAllSystem();
+ mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
}
}
@@ -9000,7 +9000,7 @@
final long timeSinceLastIdle = now - mLastIdleTime;
// Compact all non-zygote processes to freshen up the page cache.
- mOomAdjuster.mAppCompact.compactAllSystem();
+ mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
final long lowRamSinceLastIdle = getLowRamTimeSinceIdle(now);
mLastIdleTime = now;
@@ -9108,13 +9108,14 @@
final Resources res = mContext.getResources();
mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
com.android.internal.R.string.config_appsNotReportingCrashes));
- mUserController.mUserSwitchUiEnabled = !res.getBoolean(
+ final boolean userSwitchUiEnabled = !res.getBoolean(
com.android.internal.R.bool.config_customUserSwitchUi);
- mUserController.mMaxRunningUsers = res.getInteger(
+ final int maxRunningUsers = res.getInteger(
com.android.internal.R.integer.config_multiuserMaxRunningUsers);
- mUserController.mDelayUserDataLocking = res.getBoolean(
+ final boolean delayUserDataLocking = res.getBoolean(
com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
-
+ mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers,
+ delayUserDataLocking);
mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
mPssDeferralTime = pssDeferralMs;
}
@@ -10020,7 +10021,7 @@
synchronized(this) {
mConstants.dump(pw);
- mOomAdjuster.dumpAppCompactorSettings(pw);
+ mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
@@ -10425,7 +10426,7 @@
} else if ("settings".equals(cmd)) {
synchronized (this) {
mConstants.dump(pw);
- mOomAdjuster.dumpAppCompactorSettings(pw);
+ mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
}
} else if ("services".equals(cmd) || "s".equals(cmd)) {
if (dumpClient) {
@@ -16408,6 +16409,22 @@
}
@Override
+ public boolean updateMccMncConfiguration(String mcc, String mnc) {
+ int mccInt, mncInt;
+ try {
+ mccInt = Integer.parseInt(mcc);
+ mncInt = Integer.parseInt(mnc);
+ } catch (NumberFormatException | StringIndexOutOfBoundsException ex) {
+ Slog.e(TAG, "Error parsing mcc: " + mcc + " mnc: " + mnc + ". ex=" + ex);
+ return false;
+ }
+ Configuration config = new Configuration();
+ config.mcc = mccInt;
+ config.mnc = mncInt == 0 ? Configuration.MNC_ZERO : mncInt;
+ return mActivityTaskManager.updateConfiguration(config);
+ }
+
+ @Override
public int getLaunchedFromUid(IBinder activityToken) {
return mActivityTaskManager.getLaunchedFromUid(activityToken);
}
@@ -17926,7 +17943,31 @@
@Override
public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
- return mUserController.stopUser(userId, force, callback, null /* keyEvictedCallback */);
+ return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false,
+ /* callback= */ callback, /* keyEvictedCallback= */ null);
+ }
+
+ /**
+ * Stops user but allow delayed locking. Delayed locking keeps user unlocked even after
+ * stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true.
+ *
+ * <p>When delayed locking is not enabled through the overlay, this call becomes the same
+ * with {@link #stopUser(int, boolean, IStopUserCallback)} call.
+ *
+ * @param userId User id to stop.
+ * @param force Force stop the user even if the user is related with system user or current
+ * user.
+ * @param callback Callback called when user has stopped.
+ *
+ * @return {@link ActivityManager#USER_OP_SUCCESS} when user is stopped successfully. Returns
+ * other {@code ActivityManager#USER_OP_*} codes for failure.
+ *
+ */
+ @Override
+ public int stopUserWithDelayedLocking(final int userId, boolean force,
+ final IStopUserCallback callback) {
+ return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ true,
+ /* callback= */ callback, /* keyEvictedCallback= */ null);
}
@Override
@@ -18332,7 +18373,7 @@
@Override
public int getMaxRunningUsers() {
- return mUserController.mMaxRunningUsers;
+ return mUserController.getMaxRunningUsers();
}
@Override
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
similarity index 97%
rename from services/core/java/com/android/server/am/AppCompactor.java
rename to services/core/java/com/android/server/am/CachedAppOptimizer.java
index b7e2065..3ca5ebc 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -51,7 +51,7 @@
import java.util.Random;
import java.util.Set;
-public final class AppCompactor {
+public final class CachedAppOptimizer {
// Flags stored in the DeviceConfig API.
@VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
@@ -122,7 +122,7 @@
* that will wipe out the cpuset assignment for system_server threads.
* Accordingly, this is in the AMS constructor.
*/
- final ServiceThread mCompactionThread;
+ final ServiceThread mCachedAppOptimizerThread;
private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
new ArrayList<ProcessRecord>();
@@ -214,15 +214,15 @@
private int mPersistentCompactionCount;
private int mBfgsCompactionCount;
- public AppCompactor(ActivityManagerService am) {
+ public CachedAppOptimizer(ActivityManagerService am) {
mAm = am;
- mCompactionThread = new ServiceThread("CompactionThread",
+ mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
THREAD_PRIORITY_FOREGROUND, true);
mProcStateThrottle = new HashSet<>();
}
@VisibleForTesting
- AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
+ CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
this(am);
mTestCallback = callback;
}
@@ -243,7 +243,7 @@
updateFullDeltaRssThrottle();
updateProcStateThrottle();
}
- Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(),
+ Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
Process.THREAD_GROUP_SYSTEM);
}
@@ -258,7 +258,7 @@
@GuardedBy("mAm")
void dump(PrintWriter pw) {
- pw.println("AppCompactor settings");
+ pw.println("CachedAppOptimizer settings");
synchronized (mPhenotypeFlagLock) {
pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction);
pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
@@ -300,7 +300,7 @@
app.reqCompactAction = COMPACT_PROCESS_SOME;
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
}
@@ -309,7 +309,7 @@
app.reqCompactAction = COMPACT_PROCESS_FULL;
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
}
@@ -362,8 +362,8 @@
private void updateUseCompaction() {
mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
- if (mUseCompaction && !mCompactionThread.isAlive()) {
- mCompactionThread.start();
+ if (mUseCompaction && !mCachedAppOptimizerThread.isAlive()) {
+ mCachedAppOptimizerThread.start();
mCompactionHandler = new MemCompactionHandler();
}
}
@@ -521,7 +521,7 @@
private final class MemCompactionHandler extends Handler {
private MemCompactionHandler() {
- super(mCompactionThread.getLooper());
+ super(mCachedAppOptimizerThread.getLooper());
}
@Override
diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
index ebfc2a0..549051d 100644
--- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
@@ -32,6 +32,7 @@
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.View;
@@ -81,8 +82,15 @@
.setImageDrawable(drawable);
}
- ((TextView) view.findViewById(R.id.user_loading))
- .setText(res.getString(R.string.car_loading_profile));
+ TextView msgView = view.findViewById(R.id.user_loading);
+ // TODO: use developer settings instead
+ if (Build.IS_DEBUGGABLE) {
+ // TODO: use specific string
+ msgView.setText(res.getString(R.string.car_loading_profile) + " user\n(from "
+ + mOldUser.id + " to " + mNewUser.id + ")");
+ } else {
+ msgView.setText(res.getString(R.string.car_loading_profile));
+ }
setView(view);
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 0fc885a..f86d6a7 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -122,9 +122,9 @@
PowerManagerInternal mLocalPowerManager;
/**
- * Service for compacting background apps.
+ * Service for optimizing resource usage from background apps.
*/
- AppCompactor mAppCompact;
+ CachedAppOptimizer mCachedAppOptimizer;
ActivityManagerConstants mConstants;
@@ -197,7 +197,7 @@
mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
mConstants = mService.mConstants;
- mAppCompact = new AppCompactor(mService);
+ mCachedAppOptimizer = new CachedAppOptimizer(mService);
mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
final int pid = msg.arg1;
@@ -224,7 +224,7 @@
}
void initSettings() {
- mAppCompact.init();
+ mCachedAppOptimizer.init();
}
/**
@@ -1978,7 +1978,7 @@
int changes = 0;
// don't compact during bootup
- if (mAppCompact.useCompaction() && mService.mBooted) {
+ if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
// Cached and prev/home compaction
if (app.curAdj != app.setAdj) {
// Perform a minor compaction when a perceptible app becomes the prev/home app
@@ -1987,26 +1987,26 @@
if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
(app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
app.curAdj == ProcessList.HOME_APP_ADJ)) {
- mAppCompact.compactAppSome(app);
+ mCachedAppOptimizer.compactAppSome(app);
} else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ
|| app.setAdj > ProcessList.CACHED_APP_MAX_ADJ)
&& app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
&& app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
- mAppCompact.compactAppFull(app);
+ mCachedAppOptimizer.compactAppFull(app);
}
} else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
&& app.setAdj < ProcessList.FOREGROUND_APP_ADJ
// Because these can fire independent of oom_adj/procstate changes, we need
// to throttle the actual dispatch of these requests in addition to the
// processing of the requests. As a result, there is throttling both here
- // and in AppCompactor.
- && mAppCompact.shouldCompactPersistent(app, now)) {
- mAppCompact.compactAppPersistent(app);
+ // and in CachedAppOptimizer.
+ && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
+ mCachedAppOptimizer.compactAppPersistent(app);
} else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
&& app.getCurProcState()
== ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
- && mAppCompact.shouldCompactBFGS(app, now)) {
- mAppCompact.compactAppBfgs(app);
+ && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
+ mCachedAppOptimizer.compactAppBfgs(app);
}
}
@@ -2439,7 +2439,7 @@
}
@GuardedBy("mService")
- void dumpAppCompactorSettings(PrintWriter pw) {
- mAppCompact.dump(pw);
+ void dumpCachedAppOptimizerSettings(PrintWriter pw) {
+ mCachedAppOptimizer.dump(pw);
}
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b9f2e76..b7f867d 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -647,11 +647,10 @@
// Get this after boot, and won't be changed until it's rebooted, as we don't
// want some apps enabled while some apps disabled
mAppDataIsolationEnabled =
- SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+ SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mAppDataIsolationWhitelistedApps = new ArrayList<>(
SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
-
if (sKillHandler == null) {
sKillThread = new ServiceThread(TAG + ":kill",
THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
@@ -1562,8 +1561,7 @@
throw e.rethrowAsRuntimeException();
}
int numGids = 3;
- if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER
- || mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+ if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
numGids++;
}
/*
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index eb1ab38..f3a2e70 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -16,12 +16,14 @@
package com.android.server.am;
+import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
import static android.app.ActivityManager.USER_OP_IS_CURRENT;
import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
@@ -91,7 +93,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -159,7 +160,8 @@
* <p>Note: Current and system user (and their related profiles) are never stopped when
* switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
*/
- int mMaxRunningUsers;
+ @GuardedBy("mLock")
+ private int mMaxRunningUsers;
// Lock for internal state.
private final Object mLock = new Object();
@@ -211,7 +213,8 @@
private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
= new RemoteCallbackList<>();
- boolean mUserSwitchUiEnabled = true;
+ @GuardedBy("mLock")
+ private boolean mUserSwitchUiEnabled = true;
/**
* Currently active user switch callbacks.
@@ -244,10 +247,11 @@
/**
* In this mode, user is always stopped when switched out but locking of user data is
* postponed until total number of unlocked users in the system reaches mMaxRunningUsers.
- * Once total number of unlocked users reach mMaxRunningUsers, least recentely used user
+ * Once total number of unlocked users reach mMaxRunningUsers, least recently used user
* will be locked.
*/
- boolean mDelayUserDataLocking;
+ @GuardedBy("mLock")
+ private boolean mDelayUserDataLocking;
/**
* Keep track of last active users for mDelayUserDataLocking.
* The latest stopped user is placed in front while the least recently stopped user in back.
@@ -273,6 +277,33 @@
updateStartedUserArrayLU();
}
+ void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
+ boolean delayUserDataLocking) {
+ synchronized (mLock) {
+ mUserSwitchUiEnabled = userSwitchUiEnabled;
+ mMaxRunningUsers = maxRunningUsers;
+ mDelayUserDataLocking = delayUserDataLocking;
+ }
+ }
+
+ private boolean isUserSwitchUiEnabled() {
+ synchronized (mLock) {
+ return mUserSwitchUiEnabled;
+ }
+ }
+
+ int getMaxRunningUsers() {
+ synchronized (mLock) {
+ return mMaxRunningUsers;
+ }
+ }
+
+ private boolean isDelayUserDataLockingEnabled() {
+ synchronized (mLock) {
+ return mDelayUserDataLocking;
+ }
+ }
+
void finishUserSwitch(UserState uss) {
// This call holds the AM lock so we post to the handler.
mHandler.post(() -> {
@@ -319,7 +350,11 @@
// Owner/System user and current user can't be stopped
continue;
}
- if (stopUsersLU(userId, false, null, null) == USER_OP_SUCCESS) {
+ // allowDelayedLocking set here as stopping user is done without any explicit request
+ // from outside.
+ if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
+ /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
+ == USER_OP_SUCCESS) {
iterator.remove();
}
}
@@ -565,8 +600,8 @@
// intialize it; it should be stopped right away as it's not really a "real" user.
// TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
// callback on SystemService instead.
- stopUser(userInfo.id, /* force= */ true, /* stopUserCallback= */ null,
- /* keyEvictedCallback= */ null);
+ stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
+ /* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
return;
}
@@ -609,7 +644,8 @@
}
int restartUser(final int userId, final boolean foreground) {
- return stopUser(userId, /* force */ true, null, new KeyEvictedCallback() {
+ return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false,
+ /* stopUserCallback= */ null, new KeyEvictedCallback() {
@Override
public void keyEvicted(@UserIdInt int userId) {
// Post to the same handler that this callback is called from to ensure the user
@@ -619,15 +655,16 @@
});
}
- int stopUser(final int userId, final boolean force, final IStopUserCallback stopUserCallback,
- KeyEvictedCallback keyEvictedCallback) {
+ int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
throw new IllegalArgumentException("Can't stop system user " + userId);
}
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
- return stopUsersLU(userId, force, stopUserCallback, keyEvictedCallback);
+ return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
+ keyEvictedCallback);
}
}
@@ -636,7 +673,7 @@
* {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
*/
@GuardedBy("mLock")
- private int stopUsersLU(final int userId, boolean force,
+ private int stopUsersLU(final int userId, boolean force, boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
if (userId == UserHandle.USER_SYSTEM) {
return USER_OP_ERROR_IS_SYSTEM;
@@ -655,7 +692,8 @@
if (force) {
Slog.i(TAG,
"Force stop user " + userId + ". Related users will not be stopped");
- stopSingleUserLU(userId, stopUserCallback, keyEvictedCallback);
+ stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback,
+ keyEvictedCallback);
return USER_OP_SUCCESS;
}
return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
@@ -663,21 +701,64 @@
}
if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
for (int userIdToStop : usersToStop) {
- stopSingleUserLU(userIdToStop,
+ stopSingleUserLU(userIdToStop, allowDelayedLocking,
userIdToStop == userId ? stopUserCallback : null,
userIdToStop == userId ? keyEvictedCallback : null);
}
return USER_OP_SUCCESS;
}
+ /**
+ * Stops a single User. This can also trigger locking user data out depending on device's
+ * config ({@code mDelayUserDataLocking}) and arguments.
+ * User will be unlocked when
+ * - {@code mDelayUserDataLocking} is not set.
+ * - {@code mDelayUserDataLocking} is set and {@code keyEvictedCallback} is non-null.
+ * -
+ *
+ * @param userId User Id to stop and lock the data.
+ * @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen
+ * later when number of unlocked users reaches
+ * {@code mMaxRunnngUsers}. Note that this is respected only when
+ * {@code mDelayUserDataLocking} is set and {@keyEvictedCallback} is
+ * null. Otherwise the user will be locked.
+ * @param stopUserCallback Callback to notify that user has stopped.
+ * @param keyEvictedCallback Callback to notify that user has been unlocked.
+ */
@GuardedBy("mLock")
- private void stopSingleUserLU(final int userId, final IStopUserCallback stopUserCallback,
+ private void stopSingleUserLU(final int userId, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback,
KeyEvictedCallback keyEvictedCallback) {
if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId);
final UserState uss = mStartedUsers.get(userId);
- if (uss == null) {
- // User is not started, nothing to do... but we do need to
- // callback if requested.
+ if (uss == null) { // User is not started
+ // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock
+ // the requested user as the client wants to stop and lock the user. On the other hand,
+ // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking
+ // is set as that means client wants to lock the user immediately.
+ // If mDelayUserDataLocking is not set, the user was already locked when it was stopped
+ // and no further action is necessary.
+ if (mDelayUserDataLocking) {
+ if (allowDelayedLocking && keyEvictedCallback != null) {
+ Slog.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it"
+ + " and lock user:" + userId, new RuntimeException());
+ allowDelayedLocking = false;
+ }
+ if (!allowDelayedLocking) {
+ if (mLastActiveUsers.remove(Integer.valueOf(userId))) {
+ // should lock the user, user is already gone
+ final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
+ if (keyEvictedCallback != null) {
+ keyEvictedCallbacks = new ArrayList<>(1);
+ keyEvictedCallbacks.add(keyEvictedCallback);
+ } else {
+ keyEvictedCallbacks = null;
+ }
+ dispatchUserLocking(userId, keyEvictedCallbacks);
+ }
+ }
+ }
+ // We do need to post the stopped callback even though user is already stopped.
if (stopUserCallback != null) {
mHandler.post(() -> {
try {
@@ -702,6 +783,7 @@
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
updateStartedUserArrayLU();
+ final boolean allowDelayyLockingCopied = allowDelayedLocking;
// Post to handler to obtain amLock
mHandler.post(() -> {
// We are going to broadcast ACTION_USER_STOPPING and then
@@ -716,7 +798,8 @@
@Override
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
- mHandler.post(() -> finishUserStopping(userId, uss));
+ mHandler.post(() -> finishUserStopping(userId, uss,
+ allowDelayyLockingCopied));
}
};
@@ -732,7 +815,8 @@
}
}
- void finishUserStopping(final int userId, final UserState uss) {
+ void finishUserStopping(final int userId, final UserState uss,
+ final boolean allowDelayedLocking) {
Slog.d(TAG, "UserController event: finishUserStopping(" + userId + ")");
// On to the next.
final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
@@ -744,7 +828,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- finishUserStopped(uss);
+ finishUserStopped(uss, allowDelayedLocking);
}
});
}
@@ -771,7 +855,7 @@
Binder.getCallingPid(), userId);
}
- void finishUserStopped(UserState uss) {
+ void finishUserStopped(UserState uss, boolean allowDelayedLocking) {
final int userId = uss.mHandle.getIdentifier();
Slog.d(TAG, "UserController event: finishUserStopped(" + userId + ")");
final boolean stopped;
@@ -790,7 +874,13 @@
mStartedUsers.remove(userId);
mUserLru.remove(Integer.valueOf(userId));
updateStartedUserArrayLU();
- userIdToLock = updateUserToLockLU(userId);
+ if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
+ Slog.wtf(TAG,
+ "Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
+ + userId + " callbacks:" + keyEvictedCallbacks);
+ allowDelayedLocking = false;
+ }
+ userIdToLock = updateUserToLockLU(userId, allowDelayedLocking);
if (userIdToLock == UserHandle.USER_NULL) {
lockUser = false;
}
@@ -824,31 +914,36 @@
if (!lockUser) {
return;
}
- final int userIdToLockF = userIdToLock;
- // Evict the user's credential encryption key. Performed on FgThread to make it
- // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
- // to prevent data corruption.
- FgThread.getHandler().post(() -> {
- synchronized (mLock) {
- if (mStartedUsers.get(userIdToLockF) != null) {
- Slog.w(TAG, "User was restarted, skipping key eviction");
- return;
- }
- }
- try {
- mInjector.getStorageManager().lockUserKey(userIdToLockF);
- } catch (RemoteException re) {
- throw re.rethrowAsRuntimeException();
- }
- if (userIdToLockF == userId) {
- for (final KeyEvictedCallback callback : keyEvictedCallbacks) {
- callback.keyEvicted(userId);
- }
- }
- });
+ dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
}
}
+ private void dispatchUserLocking(@UserIdInt int userId,
+ @Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
+ // Evict the user's credential encryption key. Performed on FgThread to make it
+ // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
+ // to prevent data corruption.
+ FgThread.getHandler().post(() -> {
+ synchronized (mLock) {
+ if (mStartedUsers.get(userId) != null) {
+ Slog.w(TAG, "User was restarted, skipping key eviction");
+ return;
+ }
+ }
+ try {
+ mInjector.getStorageManager().lockUserKey(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowAsRuntimeException();
+ }
+ if (keyEvictedCallbacks == null) {
+ return;
+ }
+ for (int i = 0; i < keyEvictedCallbacks.size(); i++) {
+ keyEvictedCallbacks.get(i).keyEvicted(userId);
+ }
+ });
+ }
+
/**
* For mDelayUserDataLocking mode, storage once unlocked is kept unlocked.
* Total number of unlocked user storage is limited by mMaxRunningUsers.
@@ -859,9 +954,9 @@
* @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
*/
@GuardedBy("mLock")
- private int updateUserToLockLU(@UserIdInt int userId) {
+ private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
int userIdToLock = userId;
- if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral()
+ if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral()
&& !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
mLastActiveUsers.remove((Integer) userId); // arg should be object, not index
mLastActiveUsers.add(0, userId);
@@ -943,7 +1038,8 @@
if (userInfo.isGuest() || userInfo.isEphemeral()) {
// This is a user to be stopped.
synchronized (mLock) {
- stopUsersLU(oldUserId, true, null, null);
+ stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
+ null, null);
}
}
}
@@ -975,7 +1071,7 @@
}
final int profilesToStartSize = profilesToStart.size();
int i = 0;
- for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) {
+ for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
startUser(profilesToStart.get(i).id, /* foreground= */ false);
}
if (i < profilesToStartSize) {
@@ -1092,7 +1188,7 @@
return false;
}
- if (foreground && mUserSwitchUiEnabled) {
+ if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
mInjector.getWindowManager().startFreezingScreen(
R.anim.screen_user_exit, R.anim.screen_user_enter);
@@ -1140,9 +1236,11 @@
if (foreground) {
// Make sure the old user is no longer considering the display to be on.
mInjector.reportGlobalUsageEventLocked(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
+ boolean userSwitchUiEnabled;
synchronized (mLock) {
mCurrentUserId = userId;
mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
+ userSwitchUiEnabled = mUserSwitchUiEnabled;
}
mInjector.updateUserConfiguration();
updateCurrentProfileIds();
@@ -1150,7 +1248,7 @@
mInjector.reportCurWakefulnessUsageEvent();
// Once the internal notion of the active user has switched, we lock the device
// with the option to show the user switcher on the keyguard.
- if (mUserSwitchUiEnabled) {
+ if (userSwitchUiEnabled) {
mInjector.getWindowManager().setSwitchingUser(true);
mInjector.getWindowManager().lockNow(null);
}
@@ -1389,10 +1487,12 @@
Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
return false;
}
+ boolean userSwitchUiEnabled;
synchronized (mLock) {
mTargetUserId = targetUserId;
+ userSwitchUiEnabled = mUserSwitchUiEnabled;
}
- if (mUserSwitchUiEnabled) {
+ if (userSwitchUiEnabled) {
UserInfo currentUserInfo = getUserInfo(currentUserId);
Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
@@ -1456,14 +1556,15 @@
}
// If running in background is disabled or mDelayUserDataLocking mode, stop the user.
boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND,
- oldUserId) || mDelayUserDataLocking;
+ oldUserId) || isDelayUserDataLockingEnabled();
if (!disallowRunInBg) {
return;
}
synchronized (mLock) {
if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
+ " and related users");
- stopUsersLU(oldUserId, false, null, null);
+ stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
+ null, null);
}
}
@@ -1549,7 +1650,7 @@
void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId);
- if (mUserSwitchUiEnabled) {
+ if (isUserSwitchUiEnabled()) {
mInjector.getWindowManager().stopFreezingScreen();
}
uss.switching = false;
@@ -1641,11 +1742,11 @@
if (callingUid != 0 && callingUid != SYSTEM_UID) {
final boolean allow;
+ final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, targetUserId);
if (mInjector.isCallerRecents(callingUid)
- && callingUserId == getCurrentUserId()
&& isSameProfileGroup(callingUserId, targetUserId)) {
- // If the caller is Recents and it is running in the current user, we then allow it
- // to access its profiles.
+ // If the caller is Recents and the caller has ownership of the profile group,
+ // we then allow it to access its profiles.
allow = true;
} else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
@@ -1654,6 +1755,9 @@
} else if (allowMode == ALLOW_FULL_ONLY) {
// We require full access, sucks to be you.
allow = false;
+ } else if (canInteractWithAcrossProfilesPermission(
+ allowMode, isSameProfileGroup, callingPid, callingUid)) {
+ allow = true;
} else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
// If the caller does not have either permission, they are always doomed.
@@ -1661,10 +1765,11 @@
} else if (allowMode == ALLOW_NON_FULL) {
// We are blanket allowing non-full access, you lucky caller!
allow = true;
- } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
+ } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE
+ || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
// We may or may not allow this depending on whether the two users are
// in the same profile.
- allow = isSameProfileGroup(callingUserId, targetUserId);
+ allow = isSameProfileGroup;
} else {
throw new IllegalArgumentException("Unknown mode: " + allowMode);
}
@@ -1690,6 +1795,11 @@
if (allowMode != ALLOW_FULL_ONLY) {
builder.append(" or ");
builder.append(INTERACT_ACROSS_USERS);
+ if (isSameProfileGroup
+ && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+ builder.append(" or ");
+ builder.append(INTERACT_ACROSS_PROFILES);
+ }
}
String msg = builder.toString();
Slog.w(TAG, msg);
@@ -1710,6 +1820,19 @@
return targetUserId;
}
+ private boolean canInteractWithAcrossProfilesPermission(
+ int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid) {
+ if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+ return false;
+ }
+ if (!isSameProfileGroup) {
+ return false;
+ }
+ return mInjector.checkComponentPermission(
+ INTERACT_ACROSS_PROFILES, callingPid, callingUid, /*owningUid= */-1,
+ /*exported= */true) == PackageManager.PERMISSION_GRANTED;
+ }
+
int unsafeConvertIncomingUser(@UserIdInt int userId) {
return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
? getCurrentUserId(): userId;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a7593c7..e3b761e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1759,8 +1759,9 @@
? opNames.toArray(new String[opNames.size()]) : null;
// Must not hold the appops lock
- mHistoricalRegistry.getHistoricalOps(uid, packageName, featureId, opNamesArray, filter,
- beginTimeMillis, endTimeMillis, flags, callback);
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+ mHistoricalRegistry, uid, packageName, featureId, opNamesArray, filter,
+ beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
}
@Override
@@ -1778,8 +1779,9 @@
? opNames.toArray(new String[opNames.size()]) : null;
// Must not hold the appops lock
- mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, featureId, opNamesArray,
- filter, beginTimeMillis, endTimeMillis, flags, callback);
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+ mHistoricalRegistry, uid, packageName, featureId, opNamesArray,
+ filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
}
@Override
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bc4dc8f..eedeeea 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1700,6 +1700,14 @@
}
}
+ /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */
+ public @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+ @NonNull AudioAttributes attributes) {
+ Objects.requireNonNull(attributes);
+ enforceModifyAudioRoutingPermission();
+ return AudioSystem.getDevicesForAttributes(attributes);
+ }
+
/** @see AudioManager#adjustVolume(int, int) */
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
@@ -2185,8 +2193,8 @@
}
private void enforceModifyAudioRoutingPermission() {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission");
}
@@ -6419,7 +6427,7 @@
return false;
}
boolean suppress = false;
- if (resolvedStream == DEFAULT_VOL_STREAM_NO_PLAYBACK && mController != null) {
+ if (resolvedStream != AudioSystem.STREAM_MUSIC && mController != null) {
final long now = SystemClock.uptimeMillis();
if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
// ui will become visible
diff --git a/services/core/java/com/android/server/backup/PeopleBackupHelper.java b/services/core/java/com/android/server/backup/PeopleBackupHelper.java
new file mode 100644
index 0000000..e58a051
--- /dev/null
+++ b/services/core/java/com/android/server/backup/PeopleBackupHelper.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import android.app.backup.BlobBackupHelper;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.people.PeopleServiceInternal;
+
+class PeopleBackupHelper extends BlobBackupHelper {
+
+ private static final String TAG = PeopleBackupHelper.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ // Current schema of the backup state blob.
+ private static final int STATE_VERSION = 1;
+
+ // Key under which conversation infos state blob is committed to backup.
+ private static final String KEY_CONVERSATIONS = "people_conversation_infos";
+
+ private final int mUserId;
+
+ PeopleBackupHelper(int userId) {
+ super(STATE_VERSION, KEY_CONVERSATIONS);
+ mUserId = userId;
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ if (!KEY_CONVERSATIONS.equals(key)) {
+ Slog.w(TAG, "Unexpected backup key " + key);
+ return new byte[0];
+ }
+ PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+ if (DEBUG) {
+ Slog.d(TAG, "Handling backup of " + key);
+ }
+ return ps.backupConversationInfos(mUserId);
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ if (!KEY_CONVERSATIONS.equals(key)) {
+ Slog.w(TAG, "Unexpected restore key " + key);
+ return;
+ }
+ PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+ if (DEBUG) {
+ Slog.d(TAG, "Handling restore of " + key);
+ }
+ ps.restoreConversationInfos(mUserId, key, payload);
+ }
+}
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 35e8f56..1f4563b 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -55,6 +55,7 @@
private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
private static final String ACCOUNT_MANAGER_HELPER = "account_manager";
private static final String SLICES_HELPER = "slices";
+ private static final String PEOPLE_HELPER = "people";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -99,6 +100,7 @@
addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
addHelper(SLICES_HELPER, new SliceBackupHelper(this));
+ addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index e1a9f3b..8dd3242 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1400,7 +1400,8 @@
if (mCurrentAuthSession.mTokenEscrow != null) {
mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow);
}
- mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+ mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(
+ Utils.getAuthenticationTypeForResult(reason));
break;
case BiometricPrompt.DISMISSED_REASON_NEGATIVE:
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 19f5358..389763b 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
@@ -210,4 +211,30 @@
}
return biometricManagerCode;
}
+
+ /**
+ * Converts a {@link BiometricPrompt} dismissal reason to an authentication type at the level of
+ * granularity supported by {@link BiometricPrompt.AuthenticationResult}.
+ *
+ * @param reason The reason that the {@link BiometricPrompt} was dismissed. Must be one of:
+ * {@link BiometricPrompt#DISMISSED_REASON_CREDENTIAL_CONFIRMED},
+ * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRMED}, or
+ * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED}
+ * @return An integer representing the authentication type for {@link
+ * BiometricPrompt.AuthenticationResult}.
+ * @throws IllegalArgumentException if given an invalid dismissal reason.
+ */
+ public static @AuthenticationResultType int getAuthenticationTypeForResult(int reason) {
+ switch (reason) {
+ case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED:
+ return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL;
+
+ case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED:
+ case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED:
+ return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC;
+
+ default:
+ throw new IllegalArgumentException("Unsupported dismissal reason: " + reason);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index f15d999..8d26176 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -367,6 +367,8 @@
CompatConfig config = new CompatConfig(androidBuildClassifier, context);
config.initConfigFromLib(Environment.buildPath(
Environment.getRootDirectory(), "etc", "compatconfig"));
+ config.initConfigFromLib(Environment.buildPath(
+ Environment.getRootDirectory(), "system_ext", "etc", "compatconfig"));
return config;
}
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 1b1c546..5010e46 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -16,6 +16,9 @@
package com.android.server.connectivity;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -24,6 +27,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -91,7 +95,10 @@
boolean visible = (simReadyOrUnknown || isCdma()) // we only check the sim state for GSM
&& hasService()
&& mDataState == TelephonyManager.DATA_CONNECTED;
- int networkType = mServiceState.getDataNetworkType();
+ NetworkRegistrationInfo regInfo =
+ mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+ int networkType = regInfo == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN
+ : regInfo.getAccessNetworkTechnology();
if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible",
networkType, visible ? "" : "not "));
try {
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 6fa999c..04c792a 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -26,6 +26,7 @@
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
@@ -46,19 +47,18 @@
import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.Uri;
import android.os.BestClock;
import android.os.Handler;
import android.os.SystemClock;
-import android.net.Uri;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.TelephonyManager;
-import android.util.DataUnit;
import android.util.DebugUtils;
-import android.util.Pair;
import android.util.Range;
import android.util.Slog;
@@ -74,7 +74,6 @@
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
-import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -185,7 +184,6 @@
// Track information on mobile networks as they come and go.
class MultipathTracker {
final Network network;
- final int subId;
final String subscriberId;
private long mQuota;
@@ -198,13 +196,14 @@
public MultipathTracker(Network network, NetworkCapabilities nc) {
this.network = network;
this.mNetworkCapabilities = new NetworkCapabilities(nc);
- try {
- subId = Integer.parseInt(
- ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString());
- } catch (ClassCastException | NullPointerException | NumberFormatException e) {
+ NetworkSpecifier specifier = nc.getNetworkSpecifier();
+ int subId = INVALID_SUBSCRIPTION_ID;
+ if (specifier instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+ } else {
throw new IllegalStateException(String.format(
- "Can't get subId from mobile network %s (%s): %s",
- network, nc, e.getMessage()));
+ "Can't get subId from mobile network %s (%s)",
+ network, nc));
}
TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index aea6d8d..f636d67 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -116,7 +116,8 @@
&& !lp.hasIpv4Address();
// If the network tells us it doesn't use clat, respect that.
- final boolean skip464xlat = (nai.netMisc() != null) && nai.netMisc().skip464xlat;
+ final boolean skip464xlat = (nai.netAgentConfig() != null)
+ && nai.netAgentConfig().skip464xlat;
return supported && connected && isIpv6OnlyNetwork && !skip464xlat;
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 5e085ca..c1ab551 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -23,9 +23,9 @@
import android.net.INetworkMonitor;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
import android.net.NetworkMonitorManager;
import android.net.NetworkRequest;
import android.net.NetworkScore;
@@ -127,7 +127,7 @@
// This should only be modified by ConnectivityService, via setNetworkCapabilities().
// TODO: make this private with a getter.
public NetworkCapabilities networkCapabilities;
- public final NetworkMisc networkMisc;
+ public final NetworkAgentConfig networkAgentConfig;
// Indicates if netd has been told to create this Network. From this point on the appropriate
// routing rules are setup and routes are added so packets can begin flowing over the Network.
// This is a sticky bit; once set it is never cleared.
@@ -261,7 +261,7 @@
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, @NonNull NetworkScore ns, Context context,
- Handler handler, NetworkMisc misc, ConnectivityService connService, INetd netd,
+ Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) {
this.messenger = messenger;
asyncChannel = ac;
@@ -274,7 +274,7 @@
mConnService = connService;
mContext = context;
mHandler = handler;
- networkMisc = misc;
+ networkAgentConfig = config;
this.factorySerialNumber = factorySerialNumber;
}
@@ -309,8 +309,8 @@
return mConnService;
}
- public NetworkMisc netMisc() {
- return networkMisc;
+ public NetworkAgentConfig netAgentConfig() {
+ return networkAgentConfig;
}
public Handler handler() {
@@ -487,7 +487,8 @@
// selected and we're trying to see what its score could be. This ensures that we don't tear
// down an explicitly selected network before the user gets a chance to prefer it when
// a higher-scoring network (e.g., Ethernet) is available.
- if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
+ if (networkAgentConfig.explicitlySelected
+ && (networkAgentConfig.acceptUnvalidated || pretendValidated)) {
return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
}
@@ -533,7 +534,8 @@
synchronized (this) {
// Network objects are outwardly immutable so there is no point in duplicating.
// Duplicating also precludes sharing socket factories and connection pools.
- final String subscriberId = (networkMisc != null) ? networkMisc.subscriberId : null;
+ final String subscriberId = (networkAgentConfig != null)
+ ? networkAgentConfig.subscriberId : null;
return new NetworkState(new NetworkInfo(networkInfo),
new LinkProperties(linkProperties),
new NetworkCapabilities(networkCapabilities), network, subscriberId, null);
@@ -641,13 +643,13 @@
+ "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} "
+ "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} "
+ "created{" + created + "} lingering{" + isLingering() + "} "
- + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
- + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+ + "explicitlySelected{" + networkAgentConfig.explicitlySelected + "} "
+ + "acceptUnvalidated{" + networkAgentConfig.acceptUnvalidated + "} "
+ "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
+ "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
+ "captivePortalValidationPending{" + captivePortalValidationPending + "} "
+ "partialConnectivity{" + partialConnectivity + "} "
- + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} "
+ + "acceptPartialConnectivity{" + networkAgentConfig.acceptPartialConnectivity + "} "
+ "clat{" + clatd + "} "
+ "}";
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 2179518..2c41557 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -28,7 +28,7 @@
import android.content.Intent;
import android.content.res.Resources;
import android.net.NetworkSpecifier;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.net.wifi.WifiInfo;
import android.os.UserHandle;
import android.telephony.SubscriptionManager;
@@ -223,14 +223,8 @@
// name has been added to it
NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier();
int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- if (specifier instanceof StringNetworkSpecifier) {
- try {
- subId = Integer.parseInt(
- ((StringNetworkSpecifier) specifier).specifier);
- } catch (NumberFormatException e) {
- Slog.e(TAG, "NumberFormatException on "
- + ((StringNetworkSpecifier) specifier).specifier);
- }
+ if (specifier instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
}
details = mTelephonyManager.createForSubscriptionId(subId)
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6b70e5f..1d2a905 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -53,11 +53,11 @@
import android.net.LocalSocketAddress;
import android.net.Network;
import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
import android.net.RouteInfo;
import android.net.UidRange;
import android.net.VpnService;
@@ -848,7 +848,7 @@
}
public int getNetId() {
- return mNetworkAgent != null ? mNetworkAgent.netId : NETID_UNSET;
+ return mNetworkAgent != null ? mNetworkAgent.network.netId : NETID_UNSET;
}
private LinkProperties makeLinkProperties() {
@@ -914,7 +914,7 @@
* has certain changes, in which case this method would just return {@code false}.
*/
private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
- // NetworkMisc cannot be updated without registering a new NetworkAgent.
+ // NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
if (oldConfig.allowBypass != mConfig.allowBypass) {
Log.i(TAG, "Handover not possible due to changes to allowBypass");
return false;
@@ -947,8 +947,8 @@
mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null);
- NetworkMisc networkMisc = new NetworkMisc();
- networkMisc.allowBypass = mConfig.allowBypass && !mLockdown;
+ NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+ networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown;
mNetworkCapabilities.setEstablishingVpnAppUid(Binder.getCallingUid());
mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle,
@@ -957,8 +957,8 @@
try {
mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */,
mNetworkInfo, mNetworkCapabilities, lp,
- ConnectivityConstants.VPN_DEFAULT_SCORE, networkMisc,
- NetworkFactory.SerialNumber.VPN) {
+ ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig,
+ NetworkProvider.ID_VPN) {
@Override
public void unwanted() {
// We are user controlled, not driven by NetworkRequest.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d7ae5b5..e39d6d5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1305,21 +1305,13 @@
}
private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId) {
- synchronized (mSyncRoot) {
- final IBinder token = getDisplayToken(displayId);
- if (token == null) {
- return null;
- }
- final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId);
- if (logicalDisplay == null) {
- return null;
- }
-
- final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
- return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(),
- displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
- false /* useIdentityTransform */, 0 /* rotation */);
+ final IBinder token = getDisplayToken(displayId);
+ if (token == null) {
+ return null;
}
+ return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(
+ token, new Rect(), 0 /* width */, 0 /* height */,
+ false /* useIdentityTransform */, 0 /* rotation */);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 8498dcb..decb1f9 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -73,7 +73,7 @@
private static final int GLOBAL_ID = -1;
// The tolerance within which we consider something approximately equals.
- private static final float EPSILON = 0.001f;
+ private static final float EPSILON = 0.01f;
private final Object mLock = new Object();
private final Context mContext;
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index c58000f..8206fef 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -41,7 +41,6 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -713,8 +712,8 @@
}
/**
- * Gets a list of all supported users (i.e., those that pass the {@link #isSupported(UserInfo)}
- * check).
+ * Gets a list of all supported users (i.e., those that pass the
+ * {@link #isSupportedUser(TargetUser)}check).
*/
@NonNull
protected List<UserInfo> getSupportedUsers() {
@@ -723,7 +722,7 @@
final List<UserInfo> supportedUsers = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
final UserInfo userInfo = allUsers[i];
- if (isSupported(userInfo)) {
+ if (isSupportedUser(new TargetUser(userInfo))) {
supportedUsers.add(userInfo);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index c40e8af..9c42152 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -68,8 +68,21 @@
* @param autofillId {@link AutofillId} of currently focused field.
* @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object.
*/
- public abstract void onCreateInlineSuggestionsRequest(ComponentName componentName,
- AutofillId autofillId, IInlineSuggestionsRequestCallback cb);
+ public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
+ ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback cb);
+
+ /**
+ * Force switch to the enabled input method by {@code imeId} for current user. If the input
+ * method with {@code imeId} is not enabled or not installed, do nothing.
+ *
+ * @param imeId The input method ID to be switched to.
+ * @param userId The user ID to be queried.
+ * @return {@code true} if the current input method was successfully switched to the input
+ * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
+ * to be switched.
+ */
+ public abstract boolean switchToInputMethod(String imeId, @UserIdInt int userId);
/**
* Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
@@ -95,8 +108,14 @@
}
@Override
- public void onCreateInlineSuggestionsRequest(ComponentName componentName,
- AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ public void onCreateInlineSuggestionsRequest(int userId,
+ ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback cb) {
+ }
+
+ @Override
+ public boolean switchToInputMethod(String imeId, int userId) {
+ return false;
}
};
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8b6b614..0bf65bd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -111,6 +111,7 @@
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionInspector;
@@ -142,6 +143,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodClient;
@@ -1790,15 +1792,18 @@
}
@GuardedBy("mMethodMap")
- private void onCreateInlineSuggestionsRequestLocked(ComponentName componentName,
- AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
-
+ private void onCreateInlineSuggestionsRequestLocked(@UserIdInt int userId,
+ ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback callback) {
final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
try {
- if (imi != null && imi.isInlineSuggestionsEnabled() && mCurMethod != null) {
+ if (userId == mSettings.getCurrentUserId() && imi != null
+ && imi.isInlineSuggestionsEnabled() && mCurMethod != null) {
executeOrSendMessage(mCurMethod,
mCaller.obtainMessageOOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod,
- componentName, autofillId, callback));
+ componentName, autofillId,
+ new InlineSuggestionsRequestCallbackDecorator(callback,
+ imi.getPackageName())));
} else {
callback.onInlineSuggestionsUnsupported();
}
@@ -1808,6 +1813,42 @@
}
/**
+ * The decorator which validates the host package name in the
+ * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name.
+ */
+ private static final class InlineSuggestionsRequestCallbackDecorator
+ extends IInlineSuggestionsRequestCallback.Stub {
+ @NonNull
+ private final IInlineSuggestionsRequestCallback mCallback;
+ @NonNull
+ private final String mImePackageName;
+
+ InlineSuggestionsRequestCallbackDecorator(
+ @NonNull IInlineSuggestionsRequestCallback callback,
+ @NonNull String imePackageName) {
+ mCallback = callback;
+ mImePackageName = imePackageName;
+ }
+
+ @Override
+ public void onInlineSuggestionsUnsupported() throws RemoteException {
+ mCallback.onInlineSuggestionsUnsupported();
+ }
+
+ @Override
+ public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+ IInlineSuggestionsResponseCallback callback) throws RemoteException {
+ if (!mImePackageName.equals(request.getHostPackageName())) {
+ throw new SecurityException(
+ "Host package name in the provide request=[" + request.getHostPackageName()
+ + "] doesn't match the IME package name=[" + mImePackageName
+ + "].");
+ }
+ mCallback.onInlineSuggestionsRequest(request, callback);
+ }
+ }
+
+ /**
* @param imiId if null, returns enabled subtypes for the current imi
* @return enabled subtypes of the specified imi
*/
@@ -4471,10 +4512,43 @@
}
}
- private void onCreateInlineSuggestionsRequest(ComponentName componentName,
- AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
+ private void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
+ ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback callback) {
synchronized (mMethodMap) {
- onCreateInlineSuggestionsRequestLocked(componentName, autofillId, callback);
+ onCreateInlineSuggestionsRequestLocked(userId, componentName, autofillId, callback);
+ }
+ }
+
+ private boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+ synchronized (mMethodMap) {
+ if (userId == mSettings.getCurrentUserId()) {
+ if (!mMethodMap.containsKey(imeId)
+ || !mSettings.getEnabledInputMethodListLocked()
+ .contains(mMethodMap.get(imeId))) {
+ return false; // IME is not is found or not enabled.
+ }
+ setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
+ return true;
+ }
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+ final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+ new ArrayMap<>();
+ AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+ queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
+ methodMap, methodList);
+ final InputMethodSettings settings = new InputMethodSettings(
+ mContext.getResources(), mContext.getContentResolver(), methodMap,
+ userId, false);
+ if (!methodMap.containsKey(imeId)
+ || !settings.getEnabledInputMethodListLocked()
+ .contains(methodMap.get(imeId))) {
+ return false; // IME is not is found or not enabled.
+ }
+ settings.putSelectedInputMethod(imeId);
+ settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ return true;
}
}
@@ -4510,9 +4584,14 @@
}
@Override
- public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ public void onCreateInlineSuggestionsRequest(int userId, ComponentName componentName,
AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
- mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+ mService.onCreateInlineSuggestionsRequest(userId, componentName, autofillId, cb);
+ }
+
+ @Override
+ public boolean switchToInputMethod(String imeId, int userId) {
+ return mService.switchToInputMethod(imeId, userId);
}
}
@@ -5065,31 +5144,7 @@
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
}
- boolean failedToSelectUnknownIme = false;
- if (userId == mSettings.getCurrentUserId()) {
- if (mMethodMap.containsKey(imeId)) {
- setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
- } else {
- failedToSelectUnknownIme = true;
- }
- } else {
- final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
- final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
- final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
- new ArrayMap<>();
- AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
- queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- methodMap, methodList);
- final InputMethodSettings settings = new InputMethodSettings(
- mContext.getResources(), mContext.getContentResolver(), methodMap,
- userId, false);
- if (methodMap.containsKey(imeId)) {
- settings.putSelectedInputMethod(imeId);
- settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
- } else {
- failedToSelectUnknownIme = true;
- }
- }
+ boolean failedToSelectUnknownIme = !switchToInputMethod(imeId, userId);
if (failedToSelectUnknownIme) {
error.print("Unknown input method ");
error.print(imeId);
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 1f9379c..f09795f 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -191,8 +191,9 @@
}
@Override
- public void onCreateInlineSuggestionsRequest(ComponentName componentName,
- AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ public void onCreateInlineSuggestionsRequest(int userId,
+ ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback cb) {
try {
//TODO(b/137800469): support multi client IMEs.
cb.onInlineSuggestionsUnsupported();
@@ -200,6 +201,12 @@
Slog.w(TAG, "Failed to call onInlineSuggestionsUnsupported.", e);
}
}
+
+ @Override
+ public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+ reportNotSupported();
+ return false;
+ }
});
}
diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
index 17a4b9c..d763bae 100644
--- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java
+++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
@@ -24,6 +24,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.integrity.model.RuleMetadata;
+import com.android.server.integrity.parser.RandomAccessObject;
import com.android.server.integrity.parser.RuleBinaryParser;
import com.android.server.integrity.parser.RuleIndexRange;
import com.android.server.integrity.parser.RuleIndexingController;
@@ -64,10 +65,8 @@
// update rules atomically.
private final File mStagingDir;
- @Nullable
- private RuleMetadata mRuleMetadataCache;
- @Nullable
- private RuleIndexingController mRuleIndexingController;
+ @Nullable private RuleMetadata mRuleMetadataCache;
+ @Nullable private RuleIndexingController mRuleIndexingController;
/** Get the singleton instance of this class. */
public static synchronized IntegrityFileManager getInstance() {
@@ -132,9 +131,9 @@
}
try (FileOutputStream ruleFileOutputStream =
- new FileOutputStream(new File(mStagingDir, RULES_FILE));
- FileOutputStream indexingFileOutputStream =
- new FileOutputStream(new File(mStagingDir, INDEXING_FILE))) {
+ new FileOutputStream(new File(mStagingDir, RULES_FILE));
+ FileOutputStream indexingFileOutputStream =
+ new FileOutputStream(new File(mStagingDir, INDEXING_FILE))) {
mRuleSerializer.serialize(
rules, Optional.empty(), ruleFileOutputStream, indexingFileOutputStream);
}
@@ -164,11 +163,10 @@
}
// Read the rules based on the index information when available.
- try (FileInputStream inputStream =
- new FileInputStream(new File(mRulesDir, RULES_FILE))) {
- List<Rule> rules = mRuleParser.parse(inputStream, ruleReadingIndexes);
- return rules;
- }
+ File ruleFile = new File(mRulesDir, RULES_FILE);
+ List<Rule> rules =
+ mRuleParser.parse(RandomAccessObject.ofFile(ruleFile), ruleReadingIndexes);
+ return rules;
}
}
diff --git a/services/core/java/com/android/server/integrity/model/BitInputStream.java b/services/core/java/com/android/server/integrity/model/BitInputStream.java
index e768fe6..e7cc81e 100644
--- a/services/core/java/com/android/server/integrity/model/BitInputStream.java
+++ b/services/core/java/com/android/server/integrity/model/BitInputStream.java
@@ -19,26 +19,21 @@
import java.io.IOException;
import java.io.InputStream;
-/** A wrapper class for reading a stream of bits. */
+/** A wrapper class for reading a stream of bits.
+ *
+ * <p>Note: this class reads from underlying stream byte-by-byte. It is advised to apply buffering
+ * to underlying streams.
+ */
public class BitInputStream {
- private long mBitPointer;
- private boolean mReadFromStream;
+ private long mBitsRead;
- private byte[] mRuleBytes;
- private InputStream mRuleInputStream;
+ private InputStream mInputStream;
- private byte mCurrentRuleByte;
+ private byte mCurrentByte;
- public BitInputStream(byte[] ruleBytes) {
- this.mRuleBytes = ruleBytes;
- this.mBitPointer = 0;
- this.mReadFromStream = false;
- }
-
- public BitInputStream(InputStream ruleInputStream) {
- this.mRuleInputStream = ruleInputStream;
- this.mReadFromStream = true;
+ public BitInputStream(InputStream inputStream) {
+ mInputStream = inputStream;
}
/**
@@ -52,15 +47,15 @@
int count = 0;
while (count++ < numOfBits) {
- if (mBitPointer % 8 == 0) {
- mCurrentRuleByte = getNextByte();
+ if (mBitsRead % 8 == 0) {
+ mCurrentByte = getNextByte();
}
- int offset = 7 - (int) (mBitPointer % 8);
+ int offset = 7 - (int) (mBitsRead % 8);
component <<= 1;
- component |= (mCurrentRuleByte >>> offset) & 1;
+ component |= (mCurrentByte >>> offset) & 1;
- mBitPointer++;
+ mBitsRead++;
}
return component;
@@ -68,22 +63,10 @@
/** Check if there are bits left in the stream. */
public boolean hasNext() throws IOException {
- if (mReadFromStream) {
- return mRuleInputStream.available() > 0;
- } else {
- return mBitPointer / 8 < mRuleBytes.length;
- }
+ return mInputStream.available() > 0;
}
private byte getNextByte() throws IOException {
- if (mReadFromStream) {
- return (byte) mRuleInputStream.read();
- } else {
- int idx = (int) (mBitPointer / 8);
- if (idx >= mRuleBytes.length) {
- throw new IllegalArgumentException(String.format("Invalid byte index: %d", idx));
- }
- return mRuleBytes[idx];
- }
+ return (byte) mInputStream.read();
}
}
diff --git a/services/core/java/com/android/server/integrity/model/BitOutputStream.java b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
index b8ea041..7d1bb3f 100644
--- a/services/core/java/com/android/server/integrity/model/BitOutputStream.java
+++ b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
@@ -16,17 +16,26 @@
package com.android.server.integrity.model;
-import java.util.BitSet;
+import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
/** A wrapper class for writing a stream of bits. */
public class BitOutputStream {
- private BitSet mBitSet;
- private int mIndex;
+ private static final int BUFFER_SIZE = 4 * 1024;
- public BitOutputStream() {
- mBitSet = new BitSet();
- mIndex = 0;
+ private int mNextBitIndex;
+
+ private final OutputStream mOutputStream;
+ private final byte[] mBuffer;
+
+ public BitOutputStream(OutputStream outputStream) {
+ mBuffer = new byte[BUFFER_SIZE];
+ mNextBitIndex = 0;
+ mOutputStream = outputStream;
}
/**
@@ -35,15 +44,17 @@
* @param numOfBits The number of bits used to represent the value.
* @param value The value to convert to bits.
*/
- public void setNext(int numOfBits, int value) {
+ public void setNext(int numOfBits, int value) throws IOException {
if (numOfBits <= 0) {
return;
}
- int offset = 1 << (numOfBits - 1);
+
+ // optional: we can do some clever size checking to "OR" an entire segment of bits instead
+ // of setting bits one by one, but it is probably not worth it.
+ int nextBitMask = 1 << (numOfBits - 1);
while (numOfBits-- > 0) {
- mBitSet.set(mIndex, (value & offset) != 0);
- offset >>>= 1;
- mIndex++;
+ setNext((value & nextBitMask) != 0);
+ nextBitMask >>>= 1;
}
}
@@ -52,35 +63,43 @@
*
* @param value The value to set the bit to.
*/
- public void setNext(boolean value) {
- mBitSet.set(mIndex, value);
- mIndex++;
+ public void setNext(boolean value) throws IOException {
+ int byteToWrite = mNextBitIndex / BYTE_BITS;
+ if (byteToWrite == BUFFER_SIZE) {
+ mOutputStream.write(mBuffer);
+ reset();
+ byteToWrite = 0;
+ }
+ if (value) {
+ mBuffer[byteToWrite] |= 1 << (BYTE_BITS - 1 - (mNextBitIndex % BYTE_BITS));
+ }
+ mNextBitIndex++;
}
/** Set the next bit in the stream to true. */
- public void setNext() {
+ public void setNext() throws IOException {
setNext(/* value= */ true);
}
- /** Convert BitSet in big-endian to ByteArray in big-endian. */
- public byte[] toByteArray() {
- int bitSetSize = mBitSet.length();
- int numOfBytes = bitSetSize / 8;
- if (bitSetSize % 8 != 0) {
- numOfBytes++;
+ /**
+ * Flush the data written to the underlying {@link java.io.OutputStream}. Any unfinished bytes
+ * will be padded with 0.
+ */
+ public void flush() throws IOException {
+ int endByte = mNextBitIndex / BYTE_BITS;
+ if (mNextBitIndex % BYTE_BITS != 0) {
+ // If next bit is not the first bit of a byte, then mNextBitIndex / BYTE_BITS would be
+ // the byte that includes already written bits. We need to increment it so this byte
+ // gets written.
+ endByte++;
}
- byte[] bytes = new byte[numOfBytes];
- for (int i = 0; i < mBitSet.length(); i++) {
- if (mBitSet.get(i)) {
- bytes[i / 8] |= 1 << (7 - (i % 8));
- }
- }
- return bytes;
+ mOutputStream.write(mBuffer, 0, endByte);
+ reset();
}
- /** Clear the stream. */
- public void clear() {
- mBitSet.clear();
- mIndex = 0;
+ /** Reset this output stream to start state. */
+ private void reset() {
+ mNextBitIndex = 0;
+ Arrays.fill(mBuffer, (byte) 0);
}
}
diff --git a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java
deleted file mode 100644
index e555e3e..0000000
--- a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.model;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * An input stream that tracks the total number read bytes since construction and allows moving
- * fast forward to a certain byte any time during the execution.
- *
- * This class is used for efficient reading of rules based on the rule indexing.
- */
-public class BitTrackedInputStream extends BitInputStream {
-
- private static int sReadBitsCount;
-
- /** Constructor with byte array. */
- public BitTrackedInputStream(byte[] inputStream) {
- super(inputStream);
- sReadBitsCount = 0;
- }
-
- /** Constructor with input stream. */
- public BitTrackedInputStream(InputStream inputStream) {
- super(inputStream);
- sReadBitsCount = 0;
- }
-
- /** Obtains an integer value of the next {@code numOfBits}. */
- @Override
- public int getNext(int numOfBits) throws IOException {
- sReadBitsCount += numOfBits;
- return super.getNext(numOfBits);
- }
-
- /** Returns the current cursor position showing the number of bits that are read. */
- public int getReadBitsCount() {
- return sReadBitsCount;
- }
-
- /**
- * Sets the cursor to the specified byte location.
- *
- * Note that the integer parameter specifies the location in bytes -- not bits.
- */
- public void setCursorToByteLocation(int byteLocation) throws IOException {
- int bitCountToRead = byteLocation * 8 - sReadBitsCount;
- if (bitCountToRead < 0) {
- throw new IllegalStateException("The byte position is already read.");
- }
- super.getNext(bitCountToRead);
- sReadBitsCount = byteLocation * 8;
- }
-}
diff --git a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
index f575599..ceed054 100644
--- a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
+++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
@@ -23,31 +23,41 @@
* An output stream that tracks the total number written bytes since construction and allows
* querying this value any time during the execution.
*
- * This class is used for constructing the rule indexing.
+ * <p>This class is used for constructing the rule indexing.
*/
-public class ByteTrackedOutputStream {
+public class ByteTrackedOutputStream extends OutputStream {
- private static int sWrittenBytesCount;
- private static OutputStream sOutputStream;
+ private int mWrittenBytesCount;
+ private final OutputStream mOutputStream;
public ByteTrackedOutputStream(OutputStream outputStream) {
- sWrittenBytesCount = 0;
- sOutputStream = outputStream;
+ mWrittenBytesCount = 0;
+ mOutputStream = outputStream;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ mWrittenBytesCount++;
+ mOutputStream.write(b);
}
/**
- * Writes the given bytes into the output stream provided in constructor and updates the
- * total number of written bytes.
+ * Writes the given bytes into the output stream provided in constructor and updates the total
+ * number of written bytes.
*/
+ @Override
public void write(byte[] bytes) throws IOException {
- sWrittenBytesCount += bytes.length;
- sOutputStream.write(bytes);
+ write(bytes, 0, bytes.length);
}
- /**
- * Returns the total number of bytes written into the output stream at the requested time.
- */
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ mWrittenBytesCount += len;
+ mOutputStream.write(b, off, len);
+ }
+
+ /** Returns the total number of bytes written into the output stream at the requested time. */
public int getWrittenBytesCount() {
- return sWrittenBytesCount;
+ return mWrittenBytesCount;
}
}
diff --git a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
index 6ec2d5f..c389963 100644
--- a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
+++ b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
@@ -23,13 +23,14 @@
* components.
*/
public final class ComponentBitSize {
- public static final int FORMAT_VERSION_BITS = 5;
+ public static final int FORMAT_VERSION_BITS = 8;
+
public static final int EFFECT_BITS = 3;
public static final int KEY_BITS = 4;
public static final int OPERATOR_BITS = 3;
public static final int CONNECTOR_BITS = 2;
public static final int SEPARATOR_BITS = 2;
- public static final int VALUE_SIZE_BITS = 6;
+ public static final int VALUE_SIZE_BITS = 8;
public static final int IS_HASHED_BITS = 1;
public static final int ATOMIC_FORMULA_START = 0;
@@ -38,4 +39,6 @@
public static final int DEFAULT_FORMAT_VERSION = 1;
public static final int SIGNAL_BIT = 1;
+
+ public static final int BYTE_BITS = 8;
}
diff --git a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
index 52df89870..d21febb 100644
--- a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
+++ b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
@@ -18,9 +18,9 @@
/** A helper class containing special indexing file constants. */
public final class IndexingFileConstants {
- // The parsing time seems acceptable for this block size based on the tests in
- // go/ic-rule-file-format.
- public static final int INDEXING_BLOCK_SIZE = 100;
+ // We empirically experimented with different block sizes and identified that 250 is in the
+ // optimal range of efficient computation.
+ public static final int INDEXING_BLOCK_SIZE = 250;
public static final String START_INDEXING_KEY = "START_KEY";
public static final String END_INDEXING_KEY = "END_KEY";
diff --git a/services/core/java/com/android/server/integrity/parser/LimitInputStream.java b/services/core/java/com/android/server/integrity/parser/LimitInputStream.java
new file mode 100644
index 0000000..a91bbb7
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/LimitInputStream.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** An {@link InputStream} that basically truncates another {@link InputStream} */
+public class LimitInputStream extends FilterInputStream {
+ private int mReadBytes;
+ private final int mLimit;
+
+ public LimitInputStream(InputStream in, int limit) {
+ super(in);
+ if (limit < 0) {
+ throw new IllegalArgumentException("limit " + limit + " cannot be negative");
+ }
+ mReadBytes = 0;
+ mLimit = limit;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return Math.min(super.available(), mLimit - mReadBytes);
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (mReadBytes == mLimit) {
+ return -1;
+ }
+ mReadBytes++;
+ return super.read();
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (len <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return -1;
+ }
+ int result = super.read(b, off, Math.min(len, available));
+ mReadBytes += result;
+ return result;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ if (n <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return 0;
+ }
+ int bytesToSkip = (int) Math.min(available, n);
+ long bytesSkipped = super.skip(bytesToSkip);
+ mReadBytes += (int) bytesSkipped;
+ return bytesSkipped;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java
new file mode 100644
index 0000000..206e6a1
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** A wrapper around {@link RandomAccessObject} to turn it into a {@link InputStream}. */
+public class RandomAccessInputStream extends InputStream {
+
+ private final RandomAccessObject mRandomAccessObject;
+
+ private int mPosition;
+
+ public RandomAccessInputStream(RandomAccessObject object) throws IOException {
+ mRandomAccessObject = object;
+ mPosition = 0;
+ }
+
+ /** Returns the position of the file pointer. */
+ public int getPosition() {
+ return mPosition;
+ }
+
+ /** See {@link RandomAccessObject#seek(int)} */
+ public void seek(int position) throws IOException {
+ mRandomAccessObject.seek(position);
+ mPosition = position;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mRandomAccessObject.length() - mPosition;
+ }
+
+ @Override
+ public void close() throws IOException {
+ mRandomAccessObject.close();
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (available() <= 0) {
+ return -1;
+ }
+ mPosition++;
+ return mRandomAccessObject.read();
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (len <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return -1;
+ }
+ int result = mRandomAccessObject.read(b, off, Math.min(len, available));
+ mPosition += result;
+ return result;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ if (n <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return 0;
+ }
+ int skipAmount = (int) Math.min(available, n);
+ mPosition += skipAmount;
+ mRandomAccessObject.seek(mPosition);
+ return skipAmount;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java
new file mode 100644
index 0000000..d9b2e38
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+
+/** An interface for random access objects like RandomAccessFile or byte arrays. */
+public abstract class RandomAccessObject {
+
+ /** See {@link RandomAccessFile#seek(long)}. */
+ public abstract void seek(int position) throws IOException;
+
+ /** See {@link RandomAccessFile#read()}. */
+ public abstract int read() throws IOException;
+
+ /** See {@link RandomAccessFile#read(byte[], int, int)}. */
+ public abstract int read(byte[] bytes, int off, int len) throws IOException;
+
+ /** See {@link RandomAccessFile#close()}. */
+ public abstract void close() throws IOException;
+
+ /** See {@link java.io.RandomAccessFile#length()}. */
+ public abstract int length();
+
+ /** Static constructor from a file. */
+ public static RandomAccessObject ofFile(File file) throws IOException {
+ return new RandomAccessFileObject(file);
+ }
+
+ /** Static constructor from a byte array. */
+ public static RandomAccessObject ofBytes(byte[] bytes) {
+ return new RandomAccessByteArrayObject(bytes);
+ }
+
+ private static class RandomAccessFileObject extends RandomAccessObject {
+ private final RandomAccessFile mRandomAccessFile;
+ // We cache the length since File.length() invokes file IO.
+ private final int mLength;
+
+ RandomAccessFileObject(File file) throws IOException {
+ long length = file.length();
+ if (length > Integer.MAX_VALUE) {
+ throw new IOException("Unsupported file size (too big) " + length);
+ }
+
+ mRandomAccessFile = new RandomAccessFile(file, /* mode= */ "r");
+ mLength = (int) length;
+ }
+
+ @Override
+ public void seek(int position) throws IOException {
+ mRandomAccessFile.seek(position);
+ }
+
+ @Override
+ public int read() throws IOException {
+ return mRandomAccessFile.read();
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ return mRandomAccessFile.read(bytes, off, len);
+ }
+
+ @Override
+ public void close() throws IOException {
+ mRandomAccessFile.close();
+ }
+
+ @Override
+ public int length() {
+ return mLength;
+ }
+ }
+
+ private static class RandomAccessByteArrayObject extends RandomAccessObject {
+
+ private final ByteBuffer mBytes;
+
+ RandomAccessByteArrayObject(byte[] bytes) {
+ mBytes = ByteBuffer.wrap(bytes);
+ }
+
+ @Override
+ public void seek(int position) throws IOException {
+ mBytes.position(position);
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (!mBytes.hasRemaining()) {
+ return -1;
+ }
+
+ return mBytes.get() & 0xFF;
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ int bytesToCopy = Math.min(len, mBytes.remaining());
+ if (bytesToCopy <= 0) {
+ return 0;
+ }
+ mBytes.get(bytes, off, len);
+ return bytesToCopy;
+ }
+
+ @Override
+ public void close() throws IOException {}
+
+ @Override
+ public int length() {
+ return mBytes.capacity();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
index e744326..90954ff 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
@@ -17,6 +17,7 @@
package com.android.server.integrity.parser;
import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
+import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS;
import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
@@ -37,10 +38,10 @@
import android.content.integrity.Formula;
import android.content.integrity.Rule;
-import com.android.server.integrity.model.BitTrackedInputStream;
+import com.android.server.integrity.model.BitInputStream;
+import java.io.BufferedInputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -50,45 +51,42 @@
@Override
public List<Rule> parse(byte[] ruleBytes) throws RuleParseException {
- try {
- BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(ruleBytes);
- return parseRules(bitTrackedInputStream, /* indexRanges= */ Collections.emptyList());
- } catch (Exception e) {
- throw new RuleParseException(e.getMessage(), e);
- }
+ return parse(RandomAccessObject.ofBytes(ruleBytes), Collections.emptyList());
}
@Override
- public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges)
+ public List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> indexRanges)
throws RuleParseException {
- try {
- BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(inputStream);
- return parseRules(bitTrackedInputStream, indexRanges);
+ try (RandomAccessInputStream randomAccessInputStream =
+ new RandomAccessInputStream(randomAccessObject)) {
+ return parseRules(randomAccessInputStream, indexRanges);
} catch (Exception e) {
throw new RuleParseException(e.getMessage(), e);
}
}
private List<Rule> parseRules(
- BitTrackedInputStream bitTrackedInputStream,
+ RandomAccessInputStream randomAccessInputStream,
List<RuleIndexRange> indexRanges)
throws IOException {
// Read the rule binary file format version.
- bitTrackedInputStream.getNext(FORMAT_VERSION_BITS);
+ randomAccessInputStream.skip(FORMAT_VERSION_BITS / BYTE_BITS);
return indexRanges.isEmpty()
- ? parseAllRules(bitTrackedInputStream)
- : parseIndexedRules(bitTrackedInputStream, indexRanges);
+ ? parseAllRules(randomAccessInputStream)
+ : parseIndexedRules(randomAccessInputStream, indexRanges);
}
- private List<Rule> parseAllRules(BitTrackedInputStream bitTrackedInputStream)
+ private List<Rule> parseAllRules(RandomAccessInputStream randomAccessInputStream)
throws IOException {
List<Rule> parsedRules = new ArrayList<>();
- while (bitTrackedInputStream.hasNext()) {
- if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) {
- parsedRules.add(parseRule(bitTrackedInputStream));
+ BitInputStream inputStream =
+ new BitInputStream(new BufferedInputStream(randomAccessInputStream));
+ while (inputStream.hasNext()) {
+ if (inputStream.getNext(SIGNAL_BIT) == 1) {
+ parsedRules.add(parseRule(inputStream));
}
}
@@ -96,19 +94,25 @@
}
private List<Rule> parseIndexedRules(
- BitTrackedInputStream bitTrackedInputStream, List<RuleIndexRange> indexRanges)
+ RandomAccessInputStream randomAccessInputStream,
+ List<RuleIndexRange> indexRanges)
throws IOException {
List<Rule> parsedRules = new ArrayList<>();
for (RuleIndexRange range : indexRanges) {
- // Skip the rules that are not in the range.
- bitTrackedInputStream.setCursorToByteLocation(range.getStartIndex());
+ randomAccessInputStream.seek(range.getStartIndex());
- // Read the rules until we reach the end index.
- while (bitTrackedInputStream.hasNext()
- && bitTrackedInputStream.getReadBitsCount() < range.getEndIndex()) {
- if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) {
- parsedRules.add(parseRule(bitTrackedInputStream));
+ BitInputStream inputStream =
+ new BitInputStream(
+ new BufferedInputStream(
+ new LimitInputStream(
+ randomAccessInputStream,
+ range.getEndIndex() - range.getStartIndex())));
+
+ // Read the rules until we reach the end index. available() here is not reliable.
+ while (inputStream.hasNext()) {
+ if (inputStream.getNext(SIGNAL_BIT) == 1) {
+ parsedRules.add(parseRule(inputStream));
}
}
}
@@ -116,24 +120,24 @@
return parsedRules;
}
- private Rule parseRule(BitTrackedInputStream bitTrackedInputStream) throws IOException {
- Formula formula = parseFormula(bitTrackedInputStream);
- int effect = bitTrackedInputStream.getNext(EFFECT_BITS);
+ private Rule parseRule(BitInputStream bitInputStream) throws IOException {
+ Formula formula = parseFormula(bitInputStream);
+ int effect = bitInputStream.getNext(EFFECT_BITS);
- if (bitTrackedInputStream.getNext(SIGNAL_BIT) != 1) {
+ if (bitInputStream.getNext(SIGNAL_BIT) != 1) {
throw new IllegalArgumentException("A rule must end with a '1' bit.");
}
return new Rule(formula, effect);
}
- private Formula parseFormula(BitTrackedInputStream bitTrackedInputStream) throws IOException {
- int separator = bitTrackedInputStream.getNext(SEPARATOR_BITS);
+ private Formula parseFormula(BitInputStream bitInputStream) throws IOException {
+ int separator = bitInputStream.getNext(SEPARATOR_BITS);
switch (separator) {
case ATOMIC_FORMULA_START:
- return parseAtomicFormula(bitTrackedInputStream);
+ return parseAtomicFormula(bitInputStream);
case COMPOUND_FORMULA_START:
- return parseCompoundFormula(bitTrackedInputStream);
+ return parseCompoundFormula(bitInputStream);
case COMPOUND_FORMULA_END:
return null;
default:
@@ -142,40 +146,37 @@
}
}
- private CompoundFormula parseCompoundFormula(BitTrackedInputStream bitTrackedInputStream)
- throws IOException {
- int connector = bitTrackedInputStream.getNext(CONNECTOR_BITS);
+ private CompoundFormula parseCompoundFormula(BitInputStream bitInputStream) throws IOException {
+ int connector = bitInputStream.getNext(CONNECTOR_BITS);
List<Formula> formulas = new ArrayList<>();
- Formula parsedFormula = parseFormula(bitTrackedInputStream);
+ Formula parsedFormula = parseFormula(bitInputStream);
while (parsedFormula != null) {
formulas.add(parsedFormula);
- parsedFormula = parseFormula(bitTrackedInputStream);
+ parsedFormula = parseFormula(bitInputStream);
}
return new CompoundFormula(connector, formulas);
}
- private AtomicFormula parseAtomicFormula(BitTrackedInputStream bitTrackedInputStream)
- throws IOException {
- int key = bitTrackedInputStream.getNext(KEY_BITS);
- int operator = bitTrackedInputStream.getNext(OPERATOR_BITS);
+ private AtomicFormula parseAtomicFormula(BitInputStream bitInputStream) throws IOException {
+ int key = bitInputStream.getNext(KEY_BITS);
+ int operator = bitInputStream.getNext(OPERATOR_BITS);
switch (key) {
case AtomicFormula.PACKAGE_NAME:
case AtomicFormula.APP_CERTIFICATE:
case AtomicFormula.INSTALLER_NAME:
case AtomicFormula.INSTALLER_CERTIFICATE:
- boolean isHashedValue = bitTrackedInputStream.getNext(IS_HASHED_BITS) == 1;
- int valueSize = bitTrackedInputStream.getNext(VALUE_SIZE_BITS);
- String stringValue = getStringValue(bitTrackedInputStream, valueSize,
- isHashedValue);
+ boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1;
+ int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS);
+ String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue);
return new AtomicFormula.StringAtomicFormula(key, stringValue, isHashedValue);
case AtomicFormula.VERSION_CODE:
- int intValue = getIntValue(bitTrackedInputStream);
+ int intValue = getIntValue(bitInputStream);
return new AtomicFormula.IntAtomicFormula(key, operator, intValue);
case AtomicFormula.PRE_INSTALLED:
- boolean booleanValue = getBooleanValue(bitTrackedInputStream);
+ boolean booleanValue = getBooleanValue(bitInputStream);
return new AtomicFormula.BooleanAtomicFormula(key, booleanValue);
default:
throw new IllegalArgumentException(String.format("Unknown key: %d", key));
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
index 8c8450e..595a035 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
@@ -23,28 +23,33 @@
* RuleIndexingController}.
*/
public class RuleIndexRange {
- private static int sStartIndex;
- private static int sEndIndex;
+ private int mStartIndex;
+ private int mEndIndex;
/** Constructor with start and end indexes. */
public RuleIndexRange(int startIndex, int endIndex) {
- this.sStartIndex = startIndex;
- this.sEndIndex = endIndex;
+ this.mStartIndex = startIndex;
+ this.mEndIndex = endIndex;
}
/** Returns the startIndex. */
public int getStartIndex() {
- return sStartIndex;
+ return mStartIndex;
}
/** Returns the end index. */
public int getEndIndex() {
- return sEndIndex;
+ return mEndIndex;
}
@Override
public boolean equals(@Nullable Object object) {
- return sStartIndex == ((RuleIndexRange) object).getStartIndex()
- && sEndIndex == ((RuleIndexRange) object).getEndIndex();
+ return mStartIndex == ((RuleIndexRange) object).getStartIndex()
+ && mEndIndex == ((RuleIndexRange) object).getEndIndex();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Range{%d, %d}", mStartIndex, mEndIndex);
}
}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
index c971322..03392ab 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
@@ -56,7 +56,7 @@
* read and evaluated.
*/
public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
- ArrayList<RuleIndexRange> indexRanges = new ArrayList();
+ List<RuleIndexRange> indexRanges = new ArrayList<>();
// Add the range for package name indexes rules.
indexRanges.add(
@@ -102,7 +102,7 @@
.collect(Collectors.toCollection(TreeSet::new));
String minIndex = keyTreeSet.floor(searchedKey);
- String maxIndex = keyTreeSet.ceiling(searchedKey);
+ String maxIndex = keyTreeSet.higher(searchedKey);
return new RuleIndexRange(
indexMap.get(minIndex == null ? START_INDEXING_KEY : minIndex),
diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java
index a8e9f61..126dacc 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleParser.java
@@ -18,7 +18,6 @@
import android.content.integrity.Rule;
-import java.io.InputStream;
import java.util.List;
/** A helper class to parse rules into the {@link Rule} model. */
@@ -28,6 +27,6 @@
List<Rule> parse(byte[] ruleBytes) throws RuleParseException;
/** Parse rules from an input stream. */
- List<Rule> parse(InputStream inputStream, List<RuleIndexRange> ruleIndexRanges)
+ List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> ruleIndexRanges)
throws RuleParseException;
}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
index 497be84..53b0c2e 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
@@ -26,7 +26,6 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -62,11 +61,13 @@
}
@Override
- public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges)
+ public List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> indexRanges)
throws RuleParseException {
try {
XmlPullParser xmlPullParser = Xml.newPullParser();
- xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name());
+ xmlPullParser.setInput(
+ new RandomAccessInputStream(randomAccessObject),
+ StandardCharsets.UTF_8.name());
return parseRules(xmlPullParser);
} catch (Exception e) {
throw new RuleParseException(e.getMessage(), e);
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index f964d4c..6afadba 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -48,10 +48,11 @@
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.TreeMap;
+import java.util.stream.Collectors;
/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
public class RuleBinarySerializer implements RuleSerializer {
@@ -79,67 +80,69 @@
throws RuleSerializeException {
try {
// Determine the indexing groups and the order of the rules within each indexed group.
- Map<Integer, TreeMap<String, List<Rule>>> indexedRules =
+ Map<Integer, Map<String, List<Rule>>> indexedRules =
RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
+ // Serialize the rules.
ByteTrackedOutputStream ruleFileByteTrackedOutputStream =
new ByteTrackedOutputStream(rulesFileOutputStream);
-
serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream);
-
- Map<String, Integer> packageNameIndexes =
- serializeRuleList(indexedRules.get(PACKAGE_NAME_INDEXED),
+ LinkedHashMap<String, Integer> packageNameIndexes =
+ serializeRuleList(
+ indexedRules.get(PACKAGE_NAME_INDEXED),
ruleFileByteTrackedOutputStream);
- indexingFileOutputStream.write(
- serializeIndexes(packageNameIndexes, /* isIndexed= */true));
-
- Map<String, Integer> appCertificateIndexes =
- serializeRuleList(indexedRules.get(APP_CERTIFICATE_INDEXED),
+ LinkedHashMap<String, Integer> appCertificateIndexes =
+ serializeRuleList(
+ indexedRules.get(APP_CERTIFICATE_INDEXED),
ruleFileByteTrackedOutputStream);
- indexingFileOutputStream.write(
- serializeIndexes(appCertificateIndexes, /* isIndexed= */true));
+ LinkedHashMap<String, Integer> unindexedRulesIndexes =
+ serializeRuleList(
+ indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream);
- Map<String, Integer> unindexedRulesIndexes =
- serializeRuleList(indexedRules.get(NOT_INDEXED),
- ruleFileByteTrackedOutputStream);
- indexingFileOutputStream.write(
- serializeIndexes(unindexedRulesIndexes, /* isIndexed= */false));
+ // Serialize their indexes.
+ BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream);
+ serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true);
+ serializeIndexGroup(
+ appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true);
+ serializeIndexGroup(
+ unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false);
+ indexingBitOutputStream.flush();
} catch (Exception e) {
throw new RuleSerializeException(e.getMessage(), e);
}
}
- private void serializeRuleFileMetadata(Optional<Integer> formatVersion,
- ByteTrackedOutputStream outputStream)
+ private void serializeRuleFileMetadata(
+ Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream)
throws IOException {
int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
- BitOutputStream bitOutputStream = new BitOutputStream();
+ BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
- outputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
}
- private Map<String, Integer> serializeRuleList(TreeMap<String, List<Rule>> rulesMap,
- ByteTrackedOutputStream outputStream)
+ private LinkedHashMap<String, Integer> serializeRuleList(
+ Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream)
throws IOException {
- Preconditions.checkArgument(rulesMap != null,
- "serializeRuleList should never be called with null rule list.");
+ Preconditions.checkArgument(
+ rulesMap != null, "serializeRuleList should never be called with null rule list.");
- BitOutputStream bitOutputStream = new BitOutputStream();
- Map<String, Integer> indexMapping = new TreeMap();
- int indexTracker = 0;
-
+ BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
+ LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap();
indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
- for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) {
+
+ List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList());
+ int indexTracker = 0;
+ for (String key : sortedKeys) {
if (indexTracker >= INDEXING_BLOCK_SIZE) {
- indexMapping.put(entry.getKey(), outputStream.getWrittenBytesCount());
+ indexMapping.put(key, outputStream.getWrittenBytesCount());
indexTracker = 0;
}
- for (Rule rule : entry.getValue()) {
- bitOutputStream.clear();
+ for (Rule rule : rulesMap.get(key)) {
serializeRule(rule, bitOutputStream);
- outputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
indexTracker++;
}
}
@@ -148,7 +151,7 @@
return indexMapping;
}
- private void serializeRule(Rule rule, BitOutputStream bitOutputStream) {
+ private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException {
if (rule == null) {
throw new IllegalArgumentException("Null rule can not be serialized");
}
@@ -163,7 +166,8 @@
bitOutputStream.setNext();
}
- private void serializeFormula(Formula formula, BitOutputStream bitOutputStream) {
+ private void serializeFormula(Formula formula, BitOutputStream bitOutputStream)
+ throws IOException {
if (formula instanceof AtomicFormula) {
serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
} else if (formula instanceof CompoundFormula) {
@@ -175,7 +179,7 @@
}
private void serializeCompoundFormula(
- CompoundFormula compoundFormula, BitOutputStream bitOutputStream) {
+ CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException {
if (compoundFormula == null) {
throw new IllegalArgumentException("Null compound formula can not be serialized");
}
@@ -189,7 +193,7 @@
}
private void serializeAtomicFormula(
- AtomicFormula atomicFormula, BitOutputStream bitOutputStream) {
+ AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException {
if (atomicFormula == null) {
throw new IllegalArgumentException("Null atomic formula can not be serialized");
}
@@ -220,12 +224,13 @@
}
}
- private byte[] serializeIndexes(Map<String, Integer> indexes, boolean isIndexed) {
- BitOutputStream bitOutputStream = new BitOutputStream();
-
+ private void serializeIndexGroup(
+ LinkedHashMap<String, Integer> indexes,
+ BitOutputStream bitOutputStream,
+ boolean isIndexed)
+ throws IOException {
// Output the starting location of this indexing group.
- serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */false,
- bitOutputStream);
+ serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream);
serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream);
// If the group is indexed, output the locations of the indexes.
@@ -233,8 +238,8 @@
for (Map.Entry<String, Integer> entry : indexes.entrySet()) {
if (!entry.getKey().equals(START_INDEXING_KEY)
&& !entry.getKey().equals(END_INDEXING_KEY)) {
- serializeStringValue(entry.getKey(), /* isHashedValue= */false,
- bitOutputStream);
+ serializeStringValue(
+ entry.getKey(), /* isHashedValue= */ false, bitOutputStream);
serializeIntValue(entry.getValue(), bitOutputStream);
}
}
@@ -243,12 +248,11 @@
// Output the end location of this indexing group.
serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream);
serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream);
-
- return bitOutputStream.toByteArray();
}
private void serializeStringValue(
- String value, boolean isHashedValue, BitOutputStream bitOutputStream) {
+ String value, boolean isHashedValue, BitOutputStream bitOutputStream)
+ throws IOException {
if (value == null) {
throw new IllegalArgumentException("String value can not be null.");
}
@@ -261,11 +265,12 @@
}
}
- private void serializeIntValue(int value, BitOutputStream bitOutputStream) {
+ private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException {
bitOutputStream.setNext(/* numOfBits= */ 32, value);
}
- private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream) {
+ private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream)
+ throws IOException {
bitOutputStream.setNext(value);
}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
index dd871e2..2cbd4ede 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
@@ -28,6 +28,8 @@
static final int PACKAGE_NAME_INDEXED = 1;
static final int APP_CERTIFICATE_INDEXED = 2;
+ static final String DEFAULT_RULE_KEY = "N/A";
+
/** Represents which indexed file the rule should be located. */
@IntDef(
value = {
@@ -45,7 +47,7 @@
/** Constructor without a ruleKey for {@code NOT_INDEXED}. */
RuleIndexingDetails(@IndexType int indexType) {
this.mIndexType = indexType;
- this.mRuleKey = null;
+ this.mRuleKey = DEFAULT_RULE_KEY;
}
/** Constructor with a ruleKey for indexed rules. */
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
index cbc365e..7d9a901 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
@@ -30,30 +30,27 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.TreeMap;
/** A helper class for identifying the indexing type and key of a given rule. */
class RuleIndexingDetailsIdentifier {
- private static final String DEFAULT_RULE_KEY = "N/A";
-
/**
* Splits a given rule list into three indexing categories. Each rule category is returned as a
* TreeMap that is sorted by their indexing keys -- where keys correspond to package name for
* PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for
* NOT_INDEXED rules.
*/
- public static Map<Integer, TreeMap<String, List<Rule>>> splitRulesIntoIndexBuckets(
+ public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets(
List<Rule> rules) {
if (rules == null) {
throw new IllegalArgumentException(
"Index buckets cannot be created for null rule list.");
}
- Map<Integer, TreeMap<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
- typeOrganizedRuleMap.put(NOT_INDEXED, new TreeMap());
- typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new TreeMap());
- typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new TreeMap());
+ Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
+ typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap());
+ typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>());
+ typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>());
// Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
// entries sorted by their index key.
@@ -66,21 +63,14 @@
String.format("Malformed rule identified. [%s]", rule.toString()));
}
- String ruleKey =
- indexingDetails.getIndexType() != NOT_INDEXED
- ? indexingDetails.getRuleKey()
- : DEFAULT_RULE_KEY;
+ int ruleIndexType = indexingDetails.getIndexType();
+ String ruleKey = indexingDetails.getRuleKey();
- if (!typeOrganizedRuleMap.get(indexingDetails.getIndexType()).containsKey(ruleKey)) {
- typeOrganizedRuleMap
- .get(indexingDetails.getIndexType())
- .put(ruleKey, new ArrayList());
+ if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) {
+ typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList());
}
- typeOrganizedRuleMap
- .get(indexingDetails.getIndexType())
- .get(ruleKey)
- .add(rule);
+ typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule);
}
return typeOrganizedRuleMap;
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
index 4194432..8f164e6 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
@@ -35,7 +35,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.TreeMap;
+import java.util.stream.Collectors;
/** A helper class to serialize rules from the {@link Rule} model to Xml representation. */
public class RuleXmlSerializer implements RuleSerializer {
@@ -90,7 +90,7 @@
throws RuleSerializeException {
try {
// Determine the indexing groups and the order of the rules within each indexed group.
- Map<Integer, TreeMap<String, List<Rule>>> indexedRules =
+ Map<Integer, Map<String, List<Rule>>> indexedRules =
RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
// Write the XML formatted rules in order.
@@ -107,11 +107,12 @@
}
}
- private void serializeRuleList(TreeMap<String, List<Rule>> rulesMap,
- XmlSerializer xmlSerializer)
+ private void serializeRuleList(Map<String, List<Rule>> rulesMap, XmlSerializer xmlSerializer)
throws IOException {
- for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) {
- for (Rule rule : entry.getValue()) {
+ List<String> sortedKeyList =
+ rulesMap.keySet().stream().sorted().collect(Collectors.toList());
+ for (String key : sortedKeyList) {
+ for (Rule rule : rulesMap.get(key)) {
serializeRule(rule, xmlSerializer);
}
}
diff --git a/services/core/java/com/android/server/location/NtpTimeHelper.java b/services/core/java/com/android/server/location/NtpTimeHelper.java
index 67841ac..d2296ea 100644
--- a/services/core/java/com/android/server/location/NtpTimeHelper.java
+++ b/services/core/java/com/android/server/location/NtpTimeHelper.java
@@ -130,7 +130,8 @@
// force refresh NTP cache when outdated
boolean refreshSuccess = true;
- if (mNtpTime.getCacheAge() >= NTP_INTERVAL) {
+ NtpTrustedTime.TimeResult ntpResult = mNtpTime.getCachedTimeResult();
+ if (ntpResult == null || ntpResult.getAgeMillis() >= NTP_INTERVAL) {
// Blocking network operation.
refreshSuccess = mNtpTime.forceRefresh();
}
@@ -140,17 +141,17 @@
// only update when NTP time is fresh
// If refreshSuccess is false, cacheAge does not drop down.
- if (mNtpTime.getCacheAge() < NTP_INTERVAL) {
- long time = mNtpTime.getCachedNtpTime();
- long timeReference = mNtpTime.getCachedNtpTimeReference();
- long certainty = mNtpTime.getCacheCertainty();
+ ntpResult = mNtpTime.getCachedTimeResult();
+ if (ntpResult != null && ntpResult.getAgeMillis() < NTP_INTERVAL) {
+ long time = ntpResult.getTimeMillis();
+ long timeReference = ntpResult.getElapsedRealtimeMillis();
+ long certainty = ntpResult.getCertaintyMillis();
if (DEBUG) {
long now = System.currentTimeMillis();
Log.d(TAG, "NTP server returned: "
- + time + " (" + new Date(time)
- + ") reference: " + timeReference
- + " certainty: " + certainty
+ + time + " (" + new Date(time) + ")"
+ + " ntpResult: " + ntpResult
+ " system time offset: " + (time - now));
}
diff --git a/services/core/java/com/android/server/location/UserInfoStore.java b/services/core/java/com/android/server/location/UserInfoStore.java
new file mode 100644
index 0000000..f282ed2
--- /dev/null
+++ b/services/core/java/com/android/server/location/UserInfoStore.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides accessors and listeners for all user info.
+ */
+public class UserInfoStore {
+
+ /**
+ * Listener for current user changes.
+ */
+ public interface UserChangedListener {
+ /**
+ * Called when the current user changes.
+ */
+ void onUserChanged(@UserIdInt int oldUserId, @UserIdInt int newUserId);
+ }
+
+ private final Context mContext;
+ private final CopyOnWriteArrayList<UserChangedListener> mListeners;
+
+ @GuardedBy("this")
+ @Nullable
+ private UserManager mUserManager;
+
+ @GuardedBy("this")
+ @UserIdInt
+ private int mCurrentUserId;
+
+ @GuardedBy("this")
+ @UserIdInt
+ private int mCachedParentUserId;
+ @GuardedBy("this")
+ private int[] mCachedProfileUserIds;
+
+ public UserInfoStore(Context context) {
+ mContext = context;
+ mListeners = new CopyOnWriteArrayList<>();
+
+ mCurrentUserId = UserHandle.USER_NULL;
+ mCachedParentUserId = UserHandle.USER_NULL;
+ mCachedProfileUserIds = new int[]{UserHandle.USER_NULL};
+ }
+
+ /** Called when system is ready. */
+ public synchronized void onSystemReady() {
+ if (mUserManager != null) {
+ return;
+ }
+
+ mUserManager = mContext.getSystemService(UserManager.class);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+ intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ switch (action) {
+ case Intent.ACTION_USER_SWITCHED:
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL);
+ if (userId != UserHandle.USER_NULL) {
+ onUserChanged(userId);
+ }
+ break;
+ case Intent.ACTION_MANAGED_PROFILE_ADDED:
+ case Intent.ACTION_MANAGED_PROFILE_REMOVED:
+ onUserProfilesChanged();
+ break;
+ }
+ }
+ }, UserHandle.ALL, intentFilter, null, FgThread.getHandler());
+
+ mCurrentUserId = ActivityManager.getCurrentUser();
+ }
+
+ /**
+ * Adds a listener for user changed events.
+ */
+ public void addListener(UserChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener for user changed events.
+ */
+ public void removeListener(UserChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ private void onUserChanged(@UserIdInt int newUserId) {
+ int oldUserId;
+ synchronized (this) {
+ if (newUserId == mCurrentUserId) {
+ return;
+ }
+
+ oldUserId = mCurrentUserId;
+ mCurrentUserId = newUserId;
+ }
+
+ for (UserChangedListener listener : mListeners) {
+ listener.onUserChanged(oldUserId, newUserId);
+ }
+ }
+
+ private synchronized void onUserProfilesChanged() {
+ // this intent is only sent to the current user
+ if (mCachedParentUserId == mCurrentUserId) {
+ mCachedParentUserId = UserHandle.USER_NULL;
+ mCachedProfileUserIds = new int[]{UserHandle.USER_NULL};
+ }
+ }
+
+ /**
+ * Returns the user id of the current user.
+ */
+ @UserIdInt
+ public synchronized int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ /**
+ * Returns true if the given user id is either the current user or a profile of the current
+ * user.
+ */
+ public synchronized boolean isCurrentUserOrProfile(@UserIdInt int userId) {
+ return userId == mCurrentUserId || ArrayUtils.contains(
+ getProfileUserIdsForParentUser(mCurrentUserId), userId);
+ }
+
+ /**
+ * Returns the parent user id of the given user id, or the user id itself if the user id either
+ * is a parent or has no profiles.
+ */
+ @UserIdInt
+ public synchronized int getParentUserId(@UserIdInt int userId) {
+ int parentUserId;
+ if (userId == mCachedParentUserId || ArrayUtils.contains(mCachedProfileUserIds, userId)) {
+ parentUserId = mCachedParentUserId;
+ } else {
+ Preconditions.checkState(mUserManager != null);
+
+ long identity = Binder.clearCallingIdentity();
+ try {
+ UserInfo userInfo = mUserManager.getProfileParent(userId);
+ if (userInfo != null) {
+ parentUserId = userInfo.id;
+ } else {
+ // getProfileParent() returns null if the userId is already the parent...
+ parentUserId = userId;
+ }
+
+ // force profiles into cache
+ getProfileUserIdsForParentUser(parentUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ return parentUserId;
+ }
+
+ @GuardedBy("this")
+ private int[] getProfileUserIdsForParentUser(@UserIdInt int parentUserId) {
+ Preconditions.checkState(mUserManager != null);
+
+ // only assert on debug builds as this is a more expensive check
+ if (Build.IS_DEBUGGABLE) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ Preconditions.checkArgument(mUserManager.getProfileParent(parentUserId) == null);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ if (parentUserId != mCachedParentUserId) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mCachedParentUserId = parentUserId;
+ mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ return mCachedProfileUserIds;
+ }
+
+ /**
+ * Dump info for debugging.
+ */
+ public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Current User: " + mCurrentUserId + " " + Arrays.toString(
+ getProfileUserIdsForParentUser(mCurrentUserId)));
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index d2e54f9..46ea9d1 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -25,11 +25,13 @@
import android.os.ServiceManager;
import android.os.UserManager;
import android.util.Slog;
+import android.util.StatsLog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.RebootEscrowListener;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -109,20 +111,50 @@
}
void loadRebootEscrowDataIfAvailable() {
- IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
- if (rebootEscrow == null) {
+ List<UserInfo> users = mUserManager.getUsers();
+ List<UserInfo> rebootEscrowUsers = new ArrayList<>();
+ for (UserInfo user : users) {
+ if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) {
+ rebootEscrowUsers.add(user);
+ }
+ }
+
+ if (rebootEscrowUsers.isEmpty()) {
return;
}
- final SecretKeySpec escrowKey;
+ SecretKeySpec escrowKey = getAndClearRebootEscrowKey();
+ if (escrowKey == null) {
+ Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
+ for (UserInfo user : users) {
+ mStorage.removeRebootEscrow(user.id);
+ }
+ StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false);
+ return;
+ }
+
+ boolean allUsersUnlocked = true;
+ for (UserInfo user : rebootEscrowUsers) {
+ allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey);
+ }
+ StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, allUsersUnlocked);
+ }
+
+ private SecretKeySpec getAndClearRebootEscrowKey() {
+ IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+ if (rebootEscrow == null) {
+ return null;
+ }
+
try {
byte[] escrowKeyBytes = rebootEscrow.retrieveKey();
if (escrowKeyBytes == null) {
- return;
+ Slog.w(TAG, "Had reboot escrow data for users, but could not retrieve key");
+ return null;
} else if (escrowKeyBytes.length != 32) {
Slog.e(TAG, "IRebootEscrow returned key of incorrect size "
+ escrowKeyBytes.length);
- return;
+ return null;
}
// Make sure we didn't get the null key.
@@ -132,29 +164,22 @@
}
if (zero == 0) {
Slog.w(TAG, "IRebootEscrow returned an all-zeroes key");
- return;
+ return null;
}
// Overwrite the existing key with the null key
rebootEscrow.storeKey(new byte[32]);
- escrowKey = RebootEscrowData.fromKeyBytes(escrowKeyBytes);
+ return RebootEscrowData.fromKeyBytes(escrowKeyBytes);
} catch (RemoteException e) {
Slog.w(TAG, "Could not retrieve escrow data");
- return;
- }
-
- List<UserInfo> users = mUserManager.getUsers();
- for (UserInfo user : users) {
- if (mCallbacks.isUserSecure(user.id)) {
- restoreRebootEscrowForUser(user.id, escrowKey);
- }
+ return null;
}
}
- private void restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) {
+ private boolean restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) {
if (!mStorage.hasRebootEscrow(userId)) {
- return;
+ return false;
}
try {
@@ -165,9 +190,11 @@
mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
escrowData.getSyntheticPassword(), userId);
+ return true;
} catch (IOException e) {
Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
}
+ return false;
}
void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 29338ba0..52750f3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.security.Scrypt;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
@@ -163,16 +164,28 @@
}
private void syncKeys() throws RemoteException {
+ int generation = mPlatformKeyManager.getGenerationId(mUserId);
if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
// Application keys for the user will not be available for sync.
Log.w(TAG, "Credentials are not set for user " + mUserId);
- int generation = mPlatformKeyManager.getGenerationId(mUserId);
- mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
+ if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
+ || mUserId != UserHandle.USER_SYSTEM) {
+ // Only invalidate keys with legacy protection param.
+ mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
+ }
return;
}
if (isCustomLockScreen()) {
- Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId);
- mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
+ Log.w(TAG, "Unsupported credential type " + mCredentialType + " for user " + mUserId);
+ // Keys will be synced when user starts using non custom screen lock.
+ if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
+ || mUserId != UserHandle.USER_SYSTEM) {
+ mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
+ }
+ return;
+ }
+ if (mPlatformKeyManager.isDeviceLocked(mUserId) && mUserId == UserHandle.USER_SYSTEM) {
+ Log.w(TAG, "Can't sync keys for locked user " + mUserId);
return;
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 0ad6c2a..0761cde 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -67,8 +67,9 @@
* @hide
*/
public class PlatformKeyManager {
- private static final String TAG = "PlatformKeyManager";
+ static final int MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED = 1000000;
+ private static final String TAG = "PlatformKeyManager";
private static final String KEY_ALGORITHM = "AES";
private static final int KEY_SIZE_BITS = 256;
private static final String KEY_ALIAS_PREFIX =
@@ -131,14 +132,14 @@
/**
* Returns {@code true} if the platform key is available. A platform key won't be available if
- * the user has not set up a lock screen.
+ * device is locked.
*
* @param userId The ID of the user to whose lock screen the platform key must be bound.
*
* @hide
*/
- public boolean isAvailable(int userId) {
- return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
+ public boolean isDeviceLocked(int userId) {
+ return mContext.getSystemService(KeyguardManager.class).isDeviceLocked(userId);
}
/**
@@ -169,7 +170,6 @@
* @param userId The ID of the user to whose lock screen the platform key must be bound.
* @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
* @throws KeyStoreException if there is an error in AndroidKeyStore.
- * @throws InsecureUserException if the user does not have a lock screen set.
* @throws IOException if there was an issue with local database update.
* @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
*
@@ -177,13 +177,8 @@
*/
@VisibleForTesting
void regenerate(int userId)
- throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException,
+ throws NoSuchAlgorithmException, KeyStoreException, IOException,
RemoteException {
- if (!isAvailable(userId)) {
- throw new InsecureUserException(String.format(
- Locale.US, "%d does not have a lock screen set.", userId));
- }
-
int generationId = getGenerationId(userId);
int nextId;
if (generationId == -1) {
@@ -192,6 +187,7 @@
invalidatePlatformKey(userId, generationId);
nextId = generationId + 1;
}
+ generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
generateAndLoadKey(userId, nextId);
}
@@ -203,7 +199,6 @@
* @throws KeyStoreException if there was an AndroidKeyStore error.
* @throws UnrecoverableKeyException if the key could not be recovered.
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
- * @throws InsecureUserException if the user does not have a lock screen set.
* @throws IOException if there was an issue with local database update.
* @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
*
@@ -211,7 +206,7 @@
*/
public PlatformEncryptionKey getEncryptKey(int userId)
throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
- InsecureUserException, IOException, RemoteException {
+ IOException, RemoteException {
init(userId);
try {
// Try to see if the decryption key is still accessible before using the encryption key.
@@ -234,12 +229,11 @@
* @throws KeyStoreException if there was an AndroidKeyStore error.
* @throws UnrecoverableKeyException if the key could not be recovered.
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
- * @throws InsecureUserException if the user does not have a lock screen set.
*
* @hide
*/
private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException,
- UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+ UnrecoverableKeyException, NoSuchAlgorithmException {
int generationId = getGenerationId(userId);
String alias = getEncryptAlias(userId, generationId);
if (!isKeyLoaded(userId, generationId)) {
@@ -258,7 +252,6 @@
* @throws KeyStoreException if there was an AndroidKeyStore error.
* @throws UnrecoverableKeyException if the key could not be recovered.
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
- * @throws InsecureUserException if the user does not have a lock screen set.
* @throws IOException if there was an issue with local database update.
* @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
*
@@ -266,7 +259,7 @@
*/
public PlatformDecryptionKey getDecryptKey(int userId)
throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
- InsecureUserException, IOException, RemoteException {
+ IOException, RemoteException {
init(userId);
try {
PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId);
@@ -288,12 +281,11 @@
* @throws KeyStoreException if there was an AndroidKeyStore error.
* @throws UnrecoverableKeyException if the key could not be recovered.
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
- * @throws InsecureUserException if the user does not have a lock screen set.
*
* @hide
*/
private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException,
- UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+ UnrecoverableKeyException, NoSuchAlgorithmException {
int generationId = getGenerationId(userId);
String alias = getDecryptAlias(userId, generationId);
if (!isKeyLoaded(userId, generationId)) {
@@ -340,13 +332,8 @@
* @hide
*/
void init(int userId)
- throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException,
+ throws KeyStoreException, NoSuchAlgorithmException, IOException,
RemoteException {
- if (!isAvailable(userId)) {
- throw new InsecureUserException(String.format(
- Locale.US, "%d does not have a lock screen set.", userId));
- }
-
int generationId = getGenerationId(userId);
if (isKeyLoaded(userId, generationId)) {
Log.i(TAG, String.format(
@@ -363,6 +350,7 @@
generationId++;
}
+ generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
generateAndLoadKey(userId, generationId);
}
@@ -440,12 +428,16 @@
KeyProtection.Builder decryptionKeyProtection =
new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
- .setUserAuthenticationRequired(true)
- .setUserAuthenticationValidityDurationSeconds(
- USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
- if (userId != UserHandle.USER_SYSTEM) {
+ // Skip UserAuthenticationRequired for main user
+ if (userId == UserHandle.USER_SYSTEM) {
+ decryptionKeyProtection.setUnlockedDeviceRequired(true);
+ } else {
+ // With setUnlockedDeviceRequired, KeyStore thinks that device is locked .
+ decryptionKeyProtection.setUserAuthenticationRequired(true);
+ decryptionKeyProtection.setUserAuthenticationValidityDurationSeconds(
+ USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS);
// Bind decryption key to secondary profile lock screen secret.
long secureUserId = getGateKeeperService().getSecureUserId(userId);
// TODO(b/124095438): Propagate this failure instead of silently failing.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 383d5cf..6d97ed7 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -19,7 +19,6 @@
import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
-import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
@@ -46,7 +45,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
-import com.android.internal.util.Preconditions;
import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
@@ -76,8 +74,9 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import javax.crypto.AEADBadTagException;
@@ -89,13 +88,14 @@
*/
public class RecoverableKeyStoreManager {
private static final String TAG = "RecoverableKeyStoreMgr";
+ private static final long SYNC_DELAY_MILLIS = 2000;
private static RecoverableKeyStoreManager mInstance;
private final Context mContext;
private final RecoverableKeyStoreDb mDatabase;
private final RecoverySessionStorage mRecoverySessionStorage;
- private final ExecutorService mExecutorService;
+ private final ScheduledExecutorService mExecutorService;
private final RecoverySnapshotListenersStorage mListenersStorage;
private final RecoverableKeyGenerator mRecoverableKeyGenerator;
private final RecoverySnapshotStorage mSnapshotStorage;
@@ -136,7 +136,7 @@
context.getApplicationContext(),
db,
new RecoverySessionStorage(),
- Executors.newSingleThreadExecutor(),
+ Executors.newScheduledThreadPool(1),
snapshotStorage,
new RecoverySnapshotListenersStorage(),
platformKeyManager,
@@ -152,7 +152,7 @@
Context context,
RecoverableKeyStoreDb recoverableKeyStoreDb,
RecoverySessionStorage recoverySessionStorage,
- ExecutorService executorService,
+ ScheduledExecutorService executorService,
RecoverySnapshotStorage snapshotStorage,
RecoverySnapshotListenersStorage listenersStorage,
PlatformKeyManager platformKeyManager,
@@ -724,8 +724,6 @@
throw new RuntimeException(e);
} catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
- } catch (InsecureUserException e) {
- throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
}
try {
@@ -793,8 +791,6 @@
throw new RuntimeException(e);
} catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
- } catch (InsecureUserException e) {
- throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
}
try {
@@ -915,7 +911,7 @@
int storedHashType, @NonNull byte[] credential, int userId) {
// So as not to block the critical path unlocking the phone, defer to another thread.
try {
- mExecutorService.execute(KeySyncTask.newInstance(
+ mExecutorService.schedule(KeySyncTask.newInstance(
mContext,
mDatabase,
mSnapshotStorage,
@@ -923,7 +919,10 @@
userId,
storedHashType,
credential,
- /*credentialUpdated=*/ false));
+ /*credentialUpdated=*/ false),
+ SYNC_DELAY_MILLIS,
+ TimeUnit.MILLISECONDS
+ );
} catch (NoSuchAlgorithmException e) {
Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
} catch (KeyStoreException e) {
@@ -947,7 +946,7 @@
int userId) {
// So as not to block the critical path unlocking the phone, defer to another thread.
try {
- mExecutorService.execute(KeySyncTask.newInstance(
+ mExecutorService.schedule(KeySyncTask.newInstance(
mContext,
mDatabase,
mSnapshotStorage,
@@ -955,7 +954,10 @@
userId,
storedHashType,
credential,
- /*credentialUpdated=*/ true));
+ /*credentialUpdated=*/ true),
+ SYNC_DELAY_MILLIS,
+ TimeUnit.MILLISECONDS
+ );
} catch (NoSuchAlgorithmException e) {
Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
} catch (KeyStoreException e) {
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
index 1d39177..b0bccb8 100644
--- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -172,13 +172,14 @@
*/
public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
synchronized (mLock) {
- int userId = UserHandle.getUserId(mediaButtonSessionUid);
+ int userId = UserHandle.getUserHandleForUid(mediaButtonSessionUid).getIdentifier();
for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
break;
}
int uid = mSortedAudioPlaybackClientUids.get(i);
- if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
+ if (userId == UserHandle.getUserHandleForUid(uid).getIdentifier()
+ && !isPlaybackActive(uid)) {
// Clean up unnecessary UIDs.
// It doesn't need to be managed profile aware because it's just to prevent
// the list from increasing indefinitely. The media button session updating
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
new file mode 100644
index 0000000..b67335a
--- /dev/null
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.MediaRoute2Info;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+class BluetoothRouteProvider {
+ private static final String TAG = "BTRouteProvider";
+ private static BluetoothRouteProvider sInstance;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ BluetoothA2dp mA2dpProfile;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ BluetoothHearingAid mHearingAidProfile;
+
+ private final Context mContext;
+ private final BluetoothAdapter mBluetoothAdapter;
+ private final BluetoothRoutesUpdatedListener mListener;
+ private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>();
+ private final IntentFilter mIntentFilter = new IntentFilter();
+ private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
+ private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
+
+ private BluetoothDevice mActiveDevice = null;
+
+ static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
+ @NonNull BluetoothRoutesUpdatedListener listener) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(listener);
+
+ if (sInstance == null) {
+ BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter == null) {
+ return null;
+ }
+ sInstance = new BluetoothRouteProvider(context, btAdapter, listener);
+ }
+ return sInstance;
+ }
+
+ private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
+ BluetoothRoutesUpdatedListener listener) {
+ mContext = context;
+ mBluetoothAdapter = btAdapter;
+ mListener = listener;
+ buildBluetoothRoutes();
+
+ mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
+ mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
+
+ // Bluetooth on/off broadcasts
+ addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
+
+ // Pairing broadcasts
+ addEventReceiver(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedReceiver());
+
+ DeviceStateChangedRecevier deviceStateChangedReceiver = new DeviceStateChangedRecevier();
+ addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver);
+ addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver);
+ addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
+ deviceStateChangedReceiver);
+ addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
+ deviceStateChangedReceiver);
+
+ mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
+ }
+
+ private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
+ mEventReceiverMap.put(action, eventReceiver);
+ mIntentFilter.addAction(action);
+ }
+
+ private void buildBluetoothRoutes() {
+ mBluetoothRoutes.clear();
+ for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
+ if (device.isConnected()) {
+ BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
+ mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+ }
+ }
+ }
+
+ @NonNull List<MediaRoute2Info> getBluetoothRoutes() {
+ ArrayList<MediaRoute2Info> routes = new ArrayList<>();
+ for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
+ routes.add(btRoute.route);
+ }
+ return routes;
+ }
+
+ private void notifyBluetoothRoutesUpdated() {
+ if (mListener != null) {
+ mListener.onBluetoothRoutesUpdated(getBluetoothRoutes());
+ }
+ }
+
+ private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
+ BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
+ newBtRoute.btDevice = device;
+ newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), device.getName())
+ .addFeature(SystemMediaRoute2Provider.TYPE_LIVE_AUDIO)
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
+ .setDescription(mContext.getResources().getText(
+ R.string.bluetooth_a2dp_audio_route_name).toString())
+ .build();
+ newBtRoute.connectedProfiles = new SparseBooleanArray();
+ return newBtRoute;
+ }
+
+ private void setRouteConnectionStateForDevice(BluetoothDevice device,
+ @MediaRoute2Info.ConnectionState int state) {
+ if (device == null) {
+ Log.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null");
+ return;
+ }
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (btRoute == null) {
+ Log.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null");
+ return;
+ }
+ if (btRoute.route.getConnectionState() != state) {
+ btRoute.route = new MediaRoute2Info.Builder(btRoute.route)
+ .setConnectionState(state).build();
+ }
+ }
+
+ interface BluetoothRoutesUpdatedListener {
+ void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
+ }
+
+ private class BluetoothRouteInfo {
+ public BluetoothDevice btDevice;
+ public MediaRoute2Info route;
+ public SparseBooleanArray connectedProfiles;
+ }
+
+ // These callbacks run on the main thread.
+ private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dpProfile = (BluetoothA2dp) proxy;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAidProfile = (BluetoothHearingAid) proxy;
+ break;
+ default:
+ return;
+ }
+ for (BluetoothDevice device : proxy.getConnectedDevices()) {
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (btRoute == null) {
+ btRoute = createBluetoothRoute(device);
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ }
+ btRoute.connectedProfiles.put(profile, true);
+ }
+ }
+
+ public void onServiceDisconnected(int profile) {
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dpProfile = null;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAidProfile = null;
+ break;
+ default:
+ return;
+ }
+ }
+ }
+ private class BluetoothBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+ BluetoothEventReceiver receiver = mEventReceiverMap.get(action);
+ if (receiver != null) {
+ receiver.onReceive(context, intent, device);
+ }
+ }
+ }
+
+ private interface BluetoothEventReceiver {
+ void onReceive(Context context, Intent intent, BluetoothDevice device);
+ }
+
+ private class AdapterStateChangedReceiver implements BluetoothEventReceiver {
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+ if (state == BluetoothAdapter.STATE_OFF
+ || state == BluetoothAdapter.STATE_TURNING_OFF) {
+ mBluetoothRoutes.clear();
+ notifyBluetoothRoutesUpdated();
+ } else if (state == BluetoothAdapter.STATE_ON) {
+ buildBluetoothRoutes();
+ if (!mBluetoothRoutes.isEmpty()) {
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+ }
+
+ private class BondStateChangedReceiver implements BluetoothEventReceiver {
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.ERROR);
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) {
+ btRoute = createBluetoothRoute(device);
+ if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
+ btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
+ }
+ if (mHearingAidProfile != null
+ && mHearingAidProfile.getConnectedDevices().contains(device)) {
+ btRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+ }
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ notifyBluetoothRoutesUpdated();
+ } else if (bondState == BluetoothDevice.BOND_NONE
+ && mBluetoothRoutes.remove(device.getAddress()) != null) {
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+
+ private class DeviceStateChangedRecevier implements BluetoothEventReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ switch (intent.getAction()) {
+ case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+ String prevActiveDeviceAddress =
+ (mActiveDevice == null) ? null : mActiveDevice.getAddress();
+ String curActiveDeviceAddress =
+ (device == null) ? null : device.getAddress();
+ if (!TextUtils.equals(prevActiveDeviceAddress, curActiveDeviceAddress)) {
+ if (mActiveDevice != null) {
+ setRouteConnectionStateForDevice(mActiveDevice,
+ MediaRoute2Info.CONNECTION_STATE_DISCONNECTED);
+ }
+ if (device != null) {
+ setRouteConnectionStateForDevice(device,
+ MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+ }
+ notifyBluetoothRoutesUpdated();
+ mActiveDevice = device;
+ }
+ break;
+ case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
+ handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
+ break;
+ }
+ }
+
+ private void handleConnectionStateChanged(int profile, Intent intent,
+ BluetoothDevice device) {
+ int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ if (btRoute == null) {
+ btRoute = createBluetoothRoute(device);
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ btRoute.connectedProfiles.put(profile, true);
+ notifyBluetoothRoutesUpdated();
+ } else {
+ btRoute.connectedProfiles.put(profile, true);
+ }
+ } else if (state == BluetoothProfile.STATE_DISCONNECTING
+ || state == BluetoothProfile.STATE_DISCONNECTED) {
+ if (btRoute != null) {
+ btRoute.connectedProfiles.delete(profile);
+ if (btRoute.connectedProfiles.size() == 0) {
+ mBluetoothRoutes.remove(device.getAddress());
+ if (mActiveDevice != null
+ && TextUtils.equals(mActiveDevice.getAddress(),
+ device.getAddress())) {
+ mActiveDevice = null;
+ }
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 9c9a412..408c1c9 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -21,20 +21,24 @@
import android.content.ComponentName;
import android.content.Intent;
import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
+
+import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
abstract class MediaRoute2Provider {
final ComponentName mComponentName;
final String mUniqueId;
+ final Object mLock = new Object();
Callback mCallback;
private volatile MediaRoute2ProviderInfo mProviderInfo;
- private volatile List<RouteSessionInfo> mSessionInfos = Collections.emptyList();
+
+ @GuardedBy("mLock")
+ final List<RoutingSessionInfo> mSessionInfos = new ArrayList<>();
MediaRoute2Provider(@NonNull ComponentName componentName) {
mComponentName = Objects.requireNonNull(componentName, "Component name must not be null.");
@@ -68,12 +72,13 @@
}
@NonNull
- public List<RouteSessionInfo> getSessionInfos() {
- return mSessionInfos;
+ public List<RoutingSessionInfo> getSessionInfos() {
+ synchronized (mLock) {
+ return mSessionInfos;
+ }
}
- void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo,
- List<RouteSessionInfo> sessionInfos) {
+ void setProviderState(MediaRoute2ProviderInfo providerInfo) {
if (providerInfo == null) {
mProviderInfo = null;
} else {
@@ -81,20 +86,19 @@
.setUniqueId(mUniqueId)
.build();
}
- List<RouteSessionInfo> sessionInfoWithProviderId = new ArrayList<RouteSessionInfo>();
- for (RouteSessionInfo sessionInfo : sessionInfos) {
- sessionInfoWithProviderId.add(
- new RouteSessionInfo.Builder(sessionInfo)
- .setProviderId(mUniqueId)
- .build());
- }
- mSessionInfos = sessionInfoWithProviderId;
+ }
+ void notifyProviderState() {
if (mCallback != null) {
mCallback.onProviderStateChanged(this);
}
}
+ void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) {
+ setProviderState(providerInfo);
+ notifyProviderState();
+ }
+
public boolean hasComponentName(String packageName, String className) {
return mComponentName.getPackageName().equals(packageName)
&& mComponentName.getClassName().equals(className);
@@ -103,12 +107,11 @@
public interface Callback {
void onProviderStateChanged(@Nullable MediaRoute2Provider provider);
void onSessionCreated(@NonNull MediaRoute2Provider provider,
- @Nullable RouteSessionInfo sessionInfo, long requestId);
- // TODO: Remove this when MediaRouter2ServiceImpl notifies clients of session changes.
- void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo);
- // TODO: Call this when service actually notifies of session release.
+ @Nullable RoutingSessionInfo sessionInfo, long requestId);
+ void onSessionCreationFailed(@NonNull MediaRoute2Provider provider, long requestId);
+ void onSessionUpdated(@NonNull MediaRoute2Provider provider,
+ @NonNull RoutingSessionInfo sessionInfo);
void onSessionReleased(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo);
+ @NonNull RoutingSessionInfo sessionInfo);
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index 6359835..3840d02 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -17,7 +17,6 @@
package com.android.server.media;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -26,7 +25,7 @@
import android.media.IMediaRoute2ProviderClient;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
@@ -37,8 +36,6 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import java.util.Collections;
-import java.util.List;
import java.util.Objects;
/**
@@ -270,44 +267,126 @@
}
private void onProviderStateUpdated(Connection connection,
- MediaRoute2ProviderInfo providerInfo, List<RouteSessionInfo> sessionInfos) {
+ MediaRoute2ProviderInfo providerInfo) {
if (mActiveConnection != connection) {
return;
}
if (DEBUG) {
Slog.d(TAG, this + ": State changed ");
}
- setAndNotifyProviderState(providerInfo, sessionInfos);
+ setAndNotifyProviderState(providerInfo);
}
- private void onSessionCreated(Connection connection, @Nullable RouteSessionInfo sessionInfo,
+ private void onSessionCreated(Connection connection, RoutingSessionInfo sessionInfo,
long requestId) {
if (mActiveConnection != connection) {
return;
}
- if (sessionInfo != null) {
- sessionInfo = new RouteSessionInfo.Builder(sessionInfo)
- .setProviderId(getUniqueId())
- .build();
+
+ if (sessionInfo == null) {
+ Slog.w(TAG, "onSessionCreated: Ignoring null sessionInfo sent from " + mComponentName);
+ return;
}
+
+ sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .setProviderId(getUniqueId())
+ .build();
+
+ boolean duplicateSessionAlreadyExists = false;
+ synchronized (mLock) {
+ for (int i = 0; i < mSessionInfos.size(); i++) {
+ if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
+ duplicateSessionAlreadyExists = true;
+ break;
+ }
+ }
+ mSessionInfos.add(sessionInfo);
+ }
+
+ if (duplicateSessionAlreadyExists) {
+ Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring.");
+ return;
+ }
+
mCallback.onSessionCreated(this, sessionInfo, requestId);
}
- private void onSessionInfoChanged(Connection connection, RouteSessionInfo sessionInfo) {
+ private void onSessionCreationFailed(Connection connection, long requestId) {
+ if (mActiveConnection != connection) {
+ return;
+ }
+
+ if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) {
+ Slog.w(TAG, "onSessionCreationFailed: Ignoring requestId REQUEST_ID_UNKNOWN");
+ return;
+ }
+
+ mCallback.onSessionCreationFailed(this, requestId);
+ }
+
+ private void onSessionUpdated(Connection connection, RoutingSessionInfo sessionInfo) {
if (mActiveConnection != connection) {
return;
}
if (sessionInfo == null) {
- Slog.w(TAG, "onSessionInfoChanged: Ignoring null sessionInfo sent from "
+ Slog.w(TAG, "onSessionUpdated: Ignoring null sessionInfo sent from "
+ mComponentName);
return;
}
- sessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.setProviderId(getUniqueId())
.build();
- mCallback.onSessionInfoChanged(this, sessionInfo);
+ boolean found = false;
+ synchronized (mLock) {
+ for (int i = 0; i < mSessionInfos.size(); i++) {
+ if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
+ mSessionInfos.set(i, sessionInfo);
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ Slog.w(TAG, "onSessionUpdated: Matching session info not found");
+ return;
+ }
+
+ mCallback.onSessionUpdated(this, sessionInfo);
+ }
+
+ private void onSessionReleased(Connection connection, RoutingSessionInfo sessionInfo) {
+ if (mActiveConnection != connection) {
+ return;
+ }
+ if (sessionInfo == null) {
+ Slog.w(TAG, "onSessionReleased: Ignoring null sessionInfo sent from " + mComponentName);
+ return;
+ }
+
+ sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .setProviderId(getUniqueId())
+ .build();
+
+ boolean found = false;
+ synchronized (mLock) {
+ for (int i = 0; i < mSessionInfos.size(); i++) {
+ if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
+ mSessionInfos.remove(i);
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ Slog.w(TAG, "onSessionReleased: Matching session info not found");
+ return;
+ }
+
+ mCallback.onSessionReleased(this, sessionInfo);
}
private void disconnect() {
@@ -315,7 +394,7 @@
mConnectionReady = false;
mActiveConnection.dispose();
mActiveConnection = null;
- setAndNotifyProviderState(null, Collections.emptyList());
+ setAndNotifyProviderState(null);
}
}
@@ -421,19 +500,24 @@
mHandler.post(() -> onConnectionDied(Connection.this));
}
- void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo,
- List<RouteSessionInfo> sessionInfos) {
- mHandler.post(() -> onProviderStateUpdated(Connection.this,
- providerInfo, sessionInfos));
+ void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo) {
+ mHandler.post(() -> onProviderStateUpdated(Connection.this, providerInfo));
}
- void postSessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
- mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo,
- requestId));
+ void postSessionCreated(RoutingSessionInfo sessionInfo, long requestId) {
+ mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo, requestId));
}
- void postSessionInfoChanged(RouteSessionInfo sessionInfo) {
- mHandler.post(() -> onSessionInfoChanged(Connection.this, sessionInfo));
+ void postSessionCreationFailed(long requestId) {
+ mHandler.post(() -> onSessionCreationFailed(Connection.this, requestId));
+ }
+
+ void postSessionUpdated(RoutingSessionInfo sessionInfo) {
+ mHandler.post(() -> onSessionUpdated(Connection.this, sessionInfo));
+ }
+
+ void postSessionReleased(RoutingSessionInfo sessionInfo) {
+ mHandler.post(() -> onSessionReleased(Connection.this, sessionInfo));
}
}
@@ -449,16 +533,15 @@
}
@Override
- public void updateState(MediaRoute2ProviderInfo providerInfo,
- List<RouteSessionInfo> sessionInfos) {
+ public void updateState(MediaRoute2ProviderInfo providerInfo) {
Connection connection = mConnectionRef.get();
if (connection != null) {
- connection.postProviderStateUpdated(providerInfo, sessionInfos);
+ connection.postProviderStateUpdated(providerInfo);
}
}
@Override
- public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
+ public void notifySessionCreated(RoutingSessionInfo sessionInfo, long requestId) {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.postSessionCreated(sessionInfo, requestId);
@@ -466,10 +549,26 @@
}
@Override
- public void notifySessionInfoChanged(RouteSessionInfo sessionInfo) {
+ public void notifySessionCreationFailed(long requestId) {
Connection connection = mConnectionRef.get();
if (connection != null) {
- connection.postSessionInfoChanged(sessionInfo);
+ connection.postSessionCreationFailed(requestId);
+ }
+ }
+
+ @Override
+ public void notifySessionUpdated(RoutingSessionInfo sessionInfo) {
+ Connection connection = mConnectionRef.get();
+ if (connection != null) {
+ connection.postSessionUpdated(sessionInfo);
+ }
+ }
+
+ @Override
+ public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
+ Connection connection = mConnectionRef.get();
+ if (connection != null) {
+ connection.postSessionReleased(sessionInfo);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 487ab52..161afb5 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -22,7 +22,6 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
@@ -31,8 +30,9 @@
import android.media.IMediaRouter2Manager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteDiscoveryRequest;
-import android.media.RouteSessionInfo;
+import android.media.MediaRoute2ProviderService;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -90,7 +90,7 @@
@NonNull
public List<MediaRoute2Info> getSystemRoutes() {
final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
@@ -117,7 +117,7 @@
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
- final int userId = UserHandle.getUserId(uid);
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
final boolean trusted = mContext.checkCallingOrSelfPermission(
android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
== PackageManager.PERMISSION_GRANTED;
@@ -152,7 +152,7 @@
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
- final int userId = UserHandle.getUserId(uid);
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
@@ -178,18 +178,18 @@
}
public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
- String routeType, int requestId) {
+ String routeFeature, int requestId) {
Objects.requireNonNull(client, "client must not be null");
Objects.requireNonNull(route, "route must not be null");
- if (TextUtils.isEmpty(routeType)) {
- throw new IllegalArgumentException("routeType must not be empty");
+ if (TextUtils.isEmpty(routeFeature)) {
+ throw new IllegalArgumentException("routeFeature must not be empty");
}
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestCreateSessionLocked(client, route, routeType, requestId);
+ requestCreateSessionLocked(client, route, routeFeature, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -284,15 +284,15 @@
}
public void setDiscoveryRequest2(@NonNull IMediaRouter2Client client,
- @NonNull RouteDiscoveryRequest request) {
+ @NonNull RouteDiscoveryPreference preference) {
Objects.requireNonNull(client, "client must not be null");
- Objects.requireNonNull(request, "request must not be null");
+ Objects.requireNonNull(preference, "preference must not be null");
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Client2Record clientRecord = mAllClientRecords.get(client.asBinder());
- setDiscoveryRequestLocked(clientRecord, request);
+ setDiscoveryRequestLocked(clientRecord, preference);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -370,7 +370,7 @@
}
@NonNull
- public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
+ public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -450,7 +450,7 @@
}
private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client,
- @NonNull MediaRoute2Info route, @NonNull String routeType, long requestId) {
+ @NonNull MediaRoute2Info route, @NonNull String routeFeature, long requestId) {
final IBinder binder = client.asBinder();
final Client2Record clientRecord = mAllClientRecords.get(binder);
@@ -463,7 +463,7 @@
clientRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::requestCreateSessionOnHandler,
clientRecord.mUserRecord.mHandler,
- clientRecord, route, routeType, requestId));
+ clientRecord, route, routeFeature, requestId));
}
}
@@ -519,13 +519,13 @@
}
private void setDiscoveryRequestLocked(Client2Record clientRecord,
- RouteDiscoveryRequest discoveryRequest) {
+ RouteDiscoveryPreference discoveryRequest) {
if (clientRecord != null) {
- if (clientRecord.mDiscoveryRequest.equals(discoveryRequest)) {
+ if (clientRecord.mDiscoveryPreference.equals(discoveryRequest)) {
return;
}
- clientRecord.mDiscoveryRequest = discoveryRequest;
+ clientRecord.mDiscoveryPreference = discoveryRequest;
clientRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateClientUsage,
clientRecord.mUserRecord.mHandler, clientRecord));
@@ -622,9 +622,9 @@
}
long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId);
if (clientRecord != null && managerRecord.mTrusted) {
- //TODO: select route type properly
+ //TODO: select route feature properly
requestCreateSessionLocked(clientRecord.mClient, route,
- route.getRouteTypes().get(0), uniqueRequestId);
+ route.getFeatures().get(0), uniqueRequestId);
}
}
}
@@ -653,7 +653,7 @@
}
}
- private List<RouteSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
+ private List<RoutingSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -661,7 +661,7 @@
return Collections.emptyList();
}
- List<RouteSessionInfo> sessionInfos = new ArrayList<>();
+ List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mMediaProviders) {
sessionInfos.addAll(provider.getSessionInfos());
}
@@ -742,7 +742,7 @@
public final boolean mTrusted;
public final int mClientId;
- public RouteDiscoveryRequest mDiscoveryRequest;
+ public RouteDiscoveryPreference mDiscoveryPreference;
public boolean mIsManagerSelecting;
public MediaRoute2Info mSelectingRoute;
public MediaRoute2Info mSelectedRoute;
@@ -752,7 +752,7 @@
mUserRecord = userRecord;
mPackageName = packageName;
mSelectRouteSequenceNumbers = new ArrayList<>();
- mDiscoveryRequest = RouteDiscoveryRequest.EMPTY;
+ mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
mClient = client;
mUid = uid;
mPid = pid;
@@ -876,20 +876,27 @@
@Override
public void onSessionCreated(@NonNull MediaRoute2Provider provider,
- @Nullable RouteSessionInfo sessionInfo, long requestId) {
+ @NonNull RoutingSessionInfo sessionInfo, long requestId) {
sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler,
this, provider, sessionInfo, requestId));
}
@Override
- public void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo) {
+ public void onSessionCreationFailed(@NonNull MediaRoute2Provider provider, long requestId) {
+ sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreationFailedOnHandler,
+ this, provider, requestId));
+ }
+
+ @Override
+ public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
+ @NonNull RoutingSessionInfo sessionInfo) {
sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler,
this, provider, sessionInfo));
}
@Override
- public void onSessionReleased(MediaRoute2Provider provider, RouteSessionInfo sessionInfo) {
+ public void onSessionReleased(MediaRoute2Provider provider,
+ RoutingSessionInfo sessionInfo) {
sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionReleasedOnHandler,
this, provider, sessionInfo));
}
@@ -932,7 +939,7 @@
Slog.w(TAG, "Ignoring invalid route : " + route);
continue;
}
- MediaRoute2Info prevRoute = prevInfo.getRoute(route.getId());
+ MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
if (prevRoute != null) {
if (!Objects.equals(prevRoute, route)) {
@@ -978,7 +985,7 @@
}
private void requestCreateSessionOnHandler(Client2Record clientRecord,
- MediaRoute2Info route, String routeType, long requestId) {
+ MediaRoute2Info route, String routeFeature, long requestId) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider == null) {
@@ -988,20 +995,20 @@
return;
}
- if (!route.getRouteTypes().contains(routeType)) {
+ if (!route.getFeatures().contains(routeFeature)) {
Slog.w(TAG, "Ignoring session creation request since the given route=" + route
- + " doesn't support the given type=" + routeType);
+ + " doesn't support the given feature=" + routeFeature);
notifySessionCreationFailed(clientRecord, toClientRequestId(requestId));
return;
}
// TODO: Apply timeout for each request (How many seconds should we wait?)
SessionCreationRequest request = new SessionCreationRequest(
- clientRecord, route, routeType, requestId);
+ clientRecord, route, routeFeature, requestId);
mSessionCreationRequests.add(request);
provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(),
- routeType, requestId);
+ routeFeature, requestId);
}
private void selectRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1131,7 +1138,14 @@
}
private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
- @Nullable RouteSessionInfo sessionInfo, long requestId) {
+ @NonNull RoutingSessionInfo sessionInfo, long requestId) {
+
+ if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) {
+ // The session is created without any matching request.
+ // TODO: Tell managers for the session creation
+ return;
+ }
+
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
@@ -1159,16 +1173,16 @@
}
String originalRouteId = matchingRequest.mRoute.getId();
- String originalRouteType = matchingRequest.mRouteType;
+ String originalRouteFeature = matchingRequest.mRouteFeature;
Client2Record client2Record = matchingRequest.mClientRecord;
if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)
- || !TextUtils.equals(originalRouteType,
- sessionInfo.getRouteType())) {
+ || !TextUtils.equals(originalRouteFeature,
+ sessionInfo.getRouteFeature())) {
Slog.w(TAG, "Created session doesn't match the original request."
+ " originalRouteId=" + originalRouteId
- + ", originalRouteType=" + originalRouteType + ", requestId=" + requestId
- + ", sessionInfo=" + sessionInfo);
+ + ", originalRouteFeature=" + originalRouteFeature
+ + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo);
notifySessionCreationFailed(matchingRequest.mClientRecord,
toClientRequestId(requestId));
return;
@@ -1181,8 +1195,32 @@
// TODO: Tell managers for the session creation
}
+ private void onSessionCreationFailedOnHandler(@NonNull MediaRoute2Provider provider,
+ long requestId) {
+ SessionCreationRequest matchingRequest = null;
+
+ for (SessionCreationRequest request : mSessionCreationRequests) {
+ if (request.mRequestId == requestId
+ && TextUtils.equals(
+ request.mRoute.getProviderId(), provider.getUniqueId())) {
+ matchingRequest = request;
+ break;
+ }
+ }
+
+ if (matchingRequest == null) {
+ Slog.w(TAG, "Ignoring session creation failed result for unknown request. "
+ + "requestId=" + requestId);
+ return;
+ }
+
+ mSessionCreationRequests.remove(matchingRequest);
+ notifySessionCreationFailed(matchingRequest.mClientRecord,
+ toClientRequestId(requestId));
+ }
+
private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo) {
+ @NonNull RoutingSessionInfo sessionInfo) {
Client2Record client2Record = mSessionToClientMap.get(
sessionInfo.getId());
@@ -1196,7 +1234,7 @@
}
private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo) {
+ @NonNull RoutingSessionInfo sessionInfo) {
Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId());
if (client2Record == null) {
@@ -1208,8 +1246,8 @@
// TODO: Tell managers for the session release
}
- private void notifySessionCreated(Client2Record clientRecord, RouteSessionInfo sessionInfo,
- int requestId) {
+ private void notifySessionCreated(Client2Record clientRecord,
+ RoutingSessionInfo sessionInfo, int requestId) {
try {
clientRecord.mClient.notifySessionCreated(sessionInfo, requestId);
} catch (RemoteException ex) {
@@ -1228,7 +1266,7 @@
}
private void notifySessionInfoChanged(Client2Record clientRecord,
- RouteSessionInfo sessionInfo) {
+ RoutingSessionInfo sessionInfo) {
try {
clientRecord.mClient.notifySessionInfoChanged(sessionInfo);
} catch (RemoteException ex) {
@@ -1238,7 +1276,7 @@
}
private void notifySessionReleased(Client2Record clientRecord,
- RouteSessionInfo sessionInfo) {
+ RoutingSessionInfo sessionInfo) {
try {
clientRecord.mClient.notifySessionReleased(sessionInfo);
} catch (RemoteException ex) {
@@ -1412,8 +1450,8 @@
try {
manager.notifyRouteSelected(clientRecord.mPackageName,
clientRecord.mSelectedRoute);
- manager.notifyRouteTypesChanged(clientRecord.mPackageName,
- clientRecord.mDiscoveryRequest.getRouteTypes());
+ manager.notifyPreferredFeaturesChanged(clientRecord.mPackageName,
+ clientRecord.mDiscoveryPreference.getPreferredFeatures());
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to update client usage. Manager probably died.", ex);
}
@@ -1432,15 +1470,15 @@
final class SessionCreationRequest {
public final Client2Record mClientRecord;
public final MediaRoute2Info mRoute;
- public final String mRouteType;
+ public final String mRouteFeature;
public final long mRequestId;
SessionCreationRequest(@NonNull Client2Record clientRecord,
@NonNull MediaRoute2Info route,
- @NonNull String routeType, long requestId) {
+ @NonNull String routeFeature, long requestId) {
mClientRecord = clientRecord;
mRoute = route;
- mRouteType = routeType;
+ mRouteFeature = routeFeature;
mRequestId = requestId;
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index c76555c..c80a898 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -39,8 +39,8 @@
import android.media.MediaRouterClientState;
import android.media.RemoteDisplayState;
import android.media.RemoteDisplayState.RemoteDisplayInfo;
-import android.media.RouteDiscoveryRequest;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -520,7 +520,7 @@
}
// Binder call
@Override
- public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryRequest request) {
+ public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryPreference request) {
mService2.setDiscoveryRequest2(client, request);
}
@@ -552,7 +552,7 @@
// Binder call
@Override
- public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
+ public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
return mService2.getActiveSessions(manager);
}
@@ -579,7 +579,8 @@
void restoreRoute(int uid) {
ClientRecord clientRecord = null;
synchronized (mLock) {
- UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid));
+ UserRecord userRecord = mUserRecords.get(
+ UserHandle.getUserHandleForUid(uid).getIdentifier());
if (userRecord != null && userRecord.mClientRecords != null) {
for (ClientRecord cr : userRecord.mClientRecords) {
if (validatePackageName(uid, cr.mPackageName)) {
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index f3241ee..b21d2e7 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -77,7 +77,7 @@
@Override
public int getUserId() {
- return UserHandle.getUserId(mSessionToken.getUid());
+ return UserHandle.getUserHandleForUid(mSessionToken.getUid()).getIdentifier();
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index df115d0..1905571 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -135,6 +135,7 @@
private int mMaxVolume = 0;
private int mCurrentVolume = 0;
private int mOptimisticVolume = -1;
+ private String mVolumeControlId;
// End volume handling fields
private boolean mIsActive = false;
@@ -714,7 +715,7 @@
if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current,
- mAudioAttrs);
+ mAudioAttrs, mVolumeControlId);
}
volumeType = mVolumeType;
attributes = mAudioAttrs;
@@ -723,7 +724,7 @@
int max = mAudioManager.getStreamMaxVolume(stream);
int current = mAudioManager.getStreamVolume(stream);
return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
- current, attributes);
+ current, attributes, null);
}
private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
@@ -886,6 +887,7 @@
synchronized (mLock) {
typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+ mVolumeControlId = null;
if (attributes != null) {
mAudioAttrs = attributes;
} else {
@@ -904,13 +906,15 @@
}
@Override
- public void setPlaybackToRemote(int control, int max) throws RemoteException {
+ public void setPlaybackToRemote(int control, int max, String controlId)
+ throws RemoteException {
boolean typeChanged;
synchronized (mLock) {
typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
mVolumeControlType = control;
mMaxVolume = max;
+ mVolumeControlId = controlId;
}
if (typeChanged) {
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index f71fb58..4a6fcdf7 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -166,7 +166,8 @@
}
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(
- UserHandle.getUserId(config.getClientUid()));
+ UserHandle.getUserHandleForUid(config.getClientUid())
+ .getIdentifier());
if (user != null) {
user.mPriorityStack.updateMediaButtonSessionIfNeeded();
}
@@ -472,8 +473,8 @@
if (mContext
.checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
!= PackageManager.PERMISSION_GRANTED
- && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
- resolvedUserId)) {
+ && !isEnabledNotificationListener(compName,
+ UserHandle.getUserHandleForUid(uid).getIdentifier(), resolvedUserId)) {
throw new SecurityException("Missing permission to control media.");
}
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 0ea4e63..6695227 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -30,12 +30,12 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.text.TextUtils;
import android.util.Log;
import com.android.internal.R;
import java.util.Collections;
+import java.util.List;
/**
* Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers.
@@ -55,6 +55,7 @@
private final IAudioService mAudioService;
private final Handler mHandler;
private final Context mContext;
+ private final BluetoothRouteProvider mBtRouteProvider;
private static ComponentName sComponentName = new ComponentName(
SystemMediaRoute2Provider.class.getPackageName$(),
@@ -62,7 +63,7 @@
//TODO: Clean up these when audio manager support multiple bt devices
MediaRoute2Info mDefaultRoute;
- MediaRoute2Info mBluetoothA2dpRoute;
+ @NonNull List<MediaRoute2Info> mBluetoothRoutes = Collections.EMPTY_LIST;
final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
@@ -87,6 +88,10 @@
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
+ mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
+ mBluetoothRoutes = routes;
+ publishRoutes();
+ });
initializeRoutes();
}
@@ -141,8 +146,8 @@
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
- .addRouteType(TYPE_LIVE_AUDIO)
- .addRouteType(TYPE_LIVE_VIDEO)
+ .addFeature(TYPE_LIVE_AUDIO)
+ .addFeature(TYPE_LIVE_VIDEO)
.build();
AudioRoutesInfo newAudioRoutes = null;
@@ -157,7 +162,15 @@
updateAudioRoutes(newAudioRoutes);
}
- publishRoutes();
+ mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes();
+
+ MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
+ builder.addRoute(mDefaultRoute);
+ for (MediaRoute2Info route : mBluetoothRoutes) {
+ builder.addRoute(route);
+ }
+ setProviderState(builder.build());
+ mHandler.post(() -> notifyProviderState());
}
void updateAudioRoutes(AudioRoutesInfo newRoutes) {
@@ -181,25 +194,10 @@
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
- .addRouteType(TYPE_LIVE_AUDIO)
- .addRouteType(TYPE_LIVE_VIDEO)
+ .addFeature(TYPE_LIVE_AUDIO)
+ .addFeature(TYPE_LIVE_VIDEO)
.build();
- if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
- mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
- if (mCurAudioRoutesInfo.bluetoothName != null) {
- //TODO: mark as bluetooth once MediaRoute2Info has device type
- mBluetoothA2dpRoute = new MediaRoute2Info.Builder(BLUETOOTH_ROUTE_ID,
- mCurAudioRoutesInfo.bluetoothName)
- .setDescription(mContext.getResources().getText(
- R.string.bluetooth_a2dp_audio_route_name).toString())
- .addRouteType(TYPE_LIVE_AUDIO)
- .build();
- } else {
- mBluetoothA2dpRoute = null;
- }
- }
-
publishRoutes();
}
@@ -207,15 +205,13 @@
* The first route should be the currently selected system route.
* For example, if there are two system routes (BT and device speaker),
* BT will be the first route in the list.
- *
- * TODO: Support multiple BT devices
*/
void publishRoutes() {
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
- if (mBluetoothA2dpRoute != null) {
- builder.addRoute(mBluetoothA2dpRoute);
- }
builder.addRoute(mDefaultRoute);
- setAndNotifyProviderState(builder.build(), Collections.emptyList());
+ for (MediaRoute2Info route : mBluetoothRoutes) {
+ builder.addRoute(route);
+ }
+ setAndNotifyProviderState(builder.build());
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 7f650ee..b24a938 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -18,8 +18,10 @@
import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal;
+import android.annotation.NonNull;
import android.net.Network;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
import android.telephony.SubscriptionPlan;
import java.util.Set;
@@ -126,4 +128,12 @@
*/
public abstract void setMeteredRestrictedPackagesAsync(
Set<String> packageNames, int userId);
+
+ /**
+ * Notifies that any of the {@link AbstractNetworkStatsProvider} has reached its quota
+ * which was set through {@link AbstractNetworkStatsProvider#setLimit(String, long)}.
+ *
+ * @param tag the human readable identifier of the custom network stats provider.
+ */
+ public abstract void onStatsProviderLimitReached(@NonNull String tag);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 4a45730..c518614 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -75,6 +75,7 @@
import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.netstats.provider.AbstractNetworkStatsProvider.QUOTA_UNLIMITED;
import static android.os.Trace.TRACE_TAG_NETWORK;
import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED;
import static android.provider.Settings.Global.NETPOLICY_QUOTA_ENABLED;
@@ -160,7 +161,7 @@
import android.net.NetworkState;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.net.TrafficStats;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
@@ -391,6 +392,7 @@
private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18;
private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19;
+ private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20;
private static final int UID_MSG_STATE_CHANGED = 100;
private static final int UID_MSG_GONE = 101;
@@ -2875,17 +2877,6 @@
}
@Override
- public void onTetheringChanged(String iface, boolean tethering) {
- // No need to enforce permission because setRestrictBackground() will do it.
- synchronized (mUidRulesFirstLock) {
- if (mRestrictBackground && tethering) {
- Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver");
- setRestrictBackground(false);
- }
- }
- }
-
- @Override
public void setRestrictBackground(boolean restrictBackground) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setRestrictBackground");
try {
@@ -4518,19 +4509,36 @@
mListeners.finishBroadcast();
return true;
}
- case MSG_LIMIT_REACHED: {
- final String iface = (String) msg.obj;
+ case MSG_STATS_PROVIDER_LIMIT_REACHED: {
+ mNetworkStats.forceUpdate();
synchronized (mNetworkPoliciesSecondLock) {
- if (mMeteredIfaces.contains(iface)) {
- // force stats update to make sure we have
- // numbers that caused alert to trigger.
- mNetworkStats.forceUpdate();
-
- updateNetworkEnabledNL();
- updateNotificationsNL();
+ // Some providers might hit the limit reached event prior to others. Thus,
+ // re-calculate and update interface quota for every provider is needed.
+ updateNetworkRulesNL();
+ updateNetworkEnabledNL();
+ updateNotificationsNL();
+ }
+ return true;
+ }
+ case MSG_LIMIT_REACHED: {
+ final String iface = (String) msg.obj;
+ synchronized (mNetworkPoliciesSecondLock) {
+ // fast return if not needed.
+ if (!mMeteredIfaces.contains(iface)) {
+ return true;
}
}
+
+ // force stats update to make sure the service have the numbers that caused
+ // alert to trigger.
+ mNetworkStats.forceUpdate();
+
+ synchronized (mNetworkPoliciesSecondLock) {
+ updateNetworkRulesNL();
+ updateNetworkEnabledNL();
+ updateNotificationsNL();
+ }
return true;
}
case MSG_RESTRICT_BACKGROUND_CHANGED: {
@@ -4573,14 +4581,18 @@
return true;
}
case MSG_UPDATE_INTERFACE_QUOTA: {
- removeInterfaceQuota((String) msg.obj);
+ final String iface = (String) msg.obj;
// int params need to be stitched back into a long
- setInterfaceQuota((String) msg.obj,
- ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL));
+ final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL);
+ removeInterfaceQuota(iface);
+ setInterfaceQuota(iface, quota);
+ mNetworkStats.setStatsProviderLimit(iface, quota);
return true;
}
case MSG_REMOVE_INTERFACE_QUOTA: {
- removeInterfaceQuota((String) msg.obj);
+ final String iface = (String) msg.obj;
+ removeInterfaceQuota(iface);
+ mNetworkStats.setStatsProviderLimit(iface, QUOTA_UNLIMITED);
return true;
}
case MSG_RESET_FIREWALL_RULES_BY_UID: {
@@ -5235,6 +5247,12 @@
mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED,
userId, 0, packageNames).sendToTarget();
}
+
+ @Override
+ public void onStatsProviderLimitReached(@NonNull String tag) {
+ Log.v(TAG, "onStatsProviderLimitReached: " + tag);
+ mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget();
+ }
}
private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) {
@@ -5263,16 +5281,12 @@
}
private int parseSubId(NetworkState state) {
- // TODO: moved to using a legitimate NetworkSpecifier instead of string parsing
int subId = INVALID_SUBSCRIPTION_ID;
if (state != null && state.networkCapabilities != null
&& state.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
NetworkSpecifier spec = state.networkCapabilities.getNetworkSpecifier();
- if (spec instanceof StringNetworkSpecifier) {
- try {
- subId = Integer.parseInt(((StringNetworkSpecifier) spec).specifier);
- } catch (NumberFormatException e) {
- }
+ if (spec instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
}
}
return subId;
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
index 4843ede..6d72cb5 100644
--- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
@@ -16,6 +16,7 @@
package com.android.server.net;
+import android.annotation.NonNull;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
@@ -34,4 +35,10 @@
/** Force update of statistics. */
public abstract void forceUpdate();
+
+ /**
+ * Set the quota limit to all registered custom network stats providers.
+ * Note that invocation of any interface will be sent to all providers.
+ */
+ public abstract void setStatsProviderLimit(@NonNull String iface, long quota);
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 7a6f297..1dcff07 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.ACTION_USER_REMOVED;
@@ -71,6 +72,7 @@
import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.usage.NetworkStatsManager;
@@ -97,6 +99,9 @@
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TrafficStats;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
import android.os.BestClock;
import android.os.Binder;
import android.os.DropBoxManager;
@@ -109,6 +114,7 @@
import android.os.Message;
import android.os.Messenger;
import android.os.PowerManager;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -176,7 +182,7 @@
* This avoids firing the global alert too often on devices with high transfer speeds and
* high quota.
*/
- private static final int PERFORM_POLL_DELAY_MS = 1000;
+ private static final int DEFAULT_PERFORM_POLL_DELAY_MS = 1000;
private static final String TAG_NETSTATS_ERROR = "netstats_error";
@@ -220,6 +226,7 @@
*/
public interface NetworkStatsSettings {
public long getPollInterval();
+ public long getPollDelay();
public boolean getSampleEnabled();
public boolean getAugmentEnabled();
@@ -248,6 +255,7 @@
}
private final Object mStatsLock = new Object();
+ private final Object mStatsProviderLock = new Object();
/** Set of currently active ifaces. */
@GuardedBy("mStatsLock")
@@ -272,6 +280,9 @@
private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
new DropBoxNonMonotonicObserver();
+ private final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList =
+ new RemoteCallbackList<>();
+
@GuardedBy("mStatsLock")
private NetworkStatsRecorder mDevRecorder;
@GuardedBy("mStatsLock")
@@ -502,9 +513,9 @@
}
/**
- * Register for a global alert that is delivered through
- * {@link INetworkManagementEventObserver} once a threshold amount of data
- * has been transferred.
+ * Register for a global alert that is delivered through {@link INetworkManagementEventObserver}
+ * or {@link NetworkStatsProviderCallback#onAlertReached()} once a threshold amount of data has
+ * been transferred.
*/
private void registerGlobalAlert() {
try {
@@ -514,6 +525,7 @@
} catch (RemoteException e) {
// ignored; service lives in system_server
}
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setAlert(mGlobalAlertBytes));
}
@Override
@@ -803,8 +815,7 @@
@Override
public void incrementOperationCount(int uid, int tag, int operationCount) {
if (Binder.getCallingUid() != uid) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_STATS, TAG);
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
}
if (operationCount < 0) {
@@ -1095,7 +1106,7 @@
/**
* Observer that watches for {@link INetworkManagementService} alerts.
*/
- private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
+ private final INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
@Override
public void limitReached(String limitName, String iface) {
// only someone like NMS should be calling us
@@ -1106,7 +1117,7 @@
// such a call pending; UID stats are handled during normal polling interval.
if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) {
mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT,
- PERFORM_POLL_DELAY_MS);
+ mSettings.getPollDelay());
}
}
}
@@ -1252,6 +1263,14 @@
xtSnapshot.combineAllValues(tetherSnapshot);
devSnapshot.combineAllValues(tetherSnapshot);
+ // Snapshot for dev/xt stats from all custom stats providers. Counts per-interface data
+ // from stats providers that isn't already counted by dev and XT stats.
+ Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotStatsProvider");
+ final NetworkStats providersnapshot = getNetworkStatsFromProviders(STATS_PER_IFACE);
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ xtSnapshot.combineAllValues(providersnapshot);
+ devSnapshot.combineAllValues(providersnapshot);
+
// For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
// can't be reattributed to responsible apps.
Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev");
@@ -1355,6 +1374,10 @@
performSampleLocked();
}
+ // request asynchronous stats update from all providers for next poll.
+ // TODO: request with a valid token.
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.requestStatsUpdate(0 /* unused */));
+
// finally, dispatch updated event to any listeners
final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -1476,6 +1499,12 @@
public void forceUpdate() {
NetworkStatsService.this.forceUpdate();
}
+
+ @Override
+ public void setStatsProviderLimit(@NonNull String iface, long quota) {
+ Slog.v(TAG, "setStatsProviderLimit(" + iface + "," + quota + ")");
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setLimit(iface, quota));
+ }
}
@Override
@@ -1690,6 +1719,12 @@
uidSnapshot.combineAllValues(vtStats);
}
+ // get a stale copy of uid stats snapshot provided by providers.
+ final NetworkStats providerStats = getNetworkStatsFromProviders(STATS_PER_UID);
+ providerStats.filter(UID_ALL, ifaces, TAG_ALL);
+ mStatsFactory.apply464xlatAdjustments(uidSnapshot, providerStats, mUseBpfTrafficStats);
+ uidSnapshot.combineAllValues(providerStats);
+
uidSnapshot.combineAllValues(mUidOperations);
return uidSnapshot;
@@ -1726,6 +1761,152 @@
}
}
+ /**
+ * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
+ * statistics that cannot be seen by the kernel to system. To unregister, invoke the
+ * {@code unregister()} of the returned callback.
+ *
+ * @param tag a human readable identifier of the custom network stats provider.
+ * @param provider the binder interface of
+ * {@link android.net.netstats.provider.AbstractNetworkStatsProvider} that
+ * needs to be registered to the system.
+ *
+ * @return a binder interface of
+ * {@link android.net.netstats.provider.NetworkStatsProviderCallback}, which can be
+ * used to report events to the system.
+ */
+ public @NonNull INetworkStatsProviderCallback registerNetworkStatsProvider(
+ @NonNull String tag, @NonNull INetworkStatsProvider provider) {
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+ Objects.requireNonNull(provider, "provider is null");
+ Objects.requireNonNull(tag, "tag is null");
+ try {
+ NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl(
+ tag, provider, mAlertObserver, mStatsProviderCbList);
+ mStatsProviderCbList.register(callback);
+ Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid="
+ + getCallingUid() + "/" + getCallingPid());
+ return callback;
+ } catch (RemoteException e) {
+ Log.e(TAG, "registerNetworkStatsProvider failed", e);
+ }
+ return null;
+ }
+
+ // Collect stats from local cache of providers.
+ private @NonNull NetworkStats getNetworkStatsFromProviders(int how) {
+ final NetworkStats ret = new NetworkStats(0L, 0);
+ invokeForAllStatsProviderCallbacks((cb) -> ret.combineAllValues(cb.getCachedStats(how)));
+ return ret;
+ }
+
+ @FunctionalInterface
+ private interface ThrowingConsumer<S, T extends Throwable> {
+ void accept(S s) throws T;
+ }
+
+ private void invokeForAllStatsProviderCallbacks(
+ @NonNull ThrowingConsumer<NetworkStatsProviderCallbackImpl, RemoteException> task) {
+ synchronized (mStatsProviderCbList) {
+ final int length = mStatsProviderCbList.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ final NetworkStatsProviderCallbackImpl cb =
+ mStatsProviderCbList.getBroadcastItem(i);
+ try {
+ task.accept(cb);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Fail to broadcast to provider: " + cb.mTag, e);
+ }
+ }
+ } finally {
+ mStatsProviderCbList.finishBroadcast();
+ }
+ }
+ }
+
+ private static class NetworkStatsProviderCallbackImpl extends INetworkStatsProviderCallback.Stub
+ implements IBinder.DeathRecipient {
+ @NonNull final String mTag;
+ @NonNull private final Object mProviderStatsLock = new Object();
+ @NonNull final INetworkStatsProvider mProvider;
+ @NonNull final INetworkManagementEventObserver mAlertObserver;
+ @NonNull final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList;
+
+ @GuardedBy("mProviderStatsLock")
+ // STATS_PER_IFACE and STATS_PER_UID
+ private final NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+ @GuardedBy("mProviderStatsLock")
+ private final NetworkStats mUidStats = new NetworkStats(0L, 0);
+
+ NetworkStatsProviderCallbackImpl(
+ @NonNull String tag, @NonNull INetworkStatsProvider provider,
+ @NonNull INetworkManagementEventObserver alertObserver,
+ @NonNull RemoteCallbackList<NetworkStatsProviderCallbackImpl> cbList)
+ throws RemoteException {
+ mTag = tag;
+ mProvider = provider;
+ mProvider.asBinder().linkToDeath(this, 0);
+ mAlertObserver = alertObserver;
+ mStatsProviderCbList = cbList;
+ }
+
+ @NonNull
+ public NetworkStats getCachedStats(int how) {
+ synchronized (mProviderStatsLock) {
+ NetworkStats stats;
+ switch (how) {
+ case STATS_PER_IFACE:
+ stats = mIfaceStats;
+ break;
+ case STATS_PER_UID:
+ stats = mUidStats;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid type: " + how);
+ }
+ // Return a defensive copy instead of local reference.
+ return stats.clone();
+ }
+ }
+
+ @Override
+ public void onStatsUpdated(int token, @Nullable NetworkStats ifaceStats,
+ @Nullable NetworkStats uidStats) {
+ // TODO: 1. Use token to map ifaces to correct NetworkIdentity.
+ // 2. Store the difference and store it directly to the recorder.
+ synchronized (mProviderStatsLock) {
+ if (ifaceStats != null) mIfaceStats.combineAllValues(ifaceStats);
+ if (uidStats != null) mUidStats.combineAllValues(uidStats);
+ }
+ }
+
+ @Override
+ public void onAlertReached() throws RemoteException {
+ mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */);
+ }
+
+ @Override
+ public void onLimitReached() {
+ Log.d(TAG, mTag + ": onLimitReached");
+ LocalServices.getService(NetworkPolicyManagerInternal.class)
+ .onStatsProviderLimitReached(mTag);
+ }
+
+ @Override
+ public void binderDied() {
+ Log.d(TAG, mTag + ": binderDied");
+ mStatsProviderCbList.unregister(this);
+ }
+
+ @Override
+ public void unregister() {
+ Log.d(TAG, mTag + ": unregister");
+ mStatsProviderCbList.unregister(this);
+ }
+
+ }
+
@VisibleForTesting
static class HandlerCallback implements Handler.Callback {
private final NetworkStatsService mService;
@@ -1815,6 +1996,10 @@
return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
}
@Override
+ public long getPollDelay() {
+ return DEFAULT_PERFORM_POLL_DELAY_MS;
+ }
+ @Override
public long getGlobalAlertBytes(long def) {
return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def);
}
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index d66fd57..eaf120e 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -42,8 +42,10 @@
return null;
}
- record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(),
- record.sbn.getUid(), record.getChannel().getId(), false));
+ record.updateNotificationChannel(mConfig.getConversationNotificationChannel(
+ record.sbn.getPackageName(),
+ record.sbn.getUid(), record.getChannel().getId(),
+ record.getNotification().getShortcutId(), true, false));
return null;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 728b297..1d49364 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -25,6 +25,7 @@
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
@@ -236,6 +237,7 @@
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.server.DeviceIdleInternal;
@@ -470,7 +472,8 @@
private static final String LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE = "value";
private RankingHelper mRankingHelper;
- private PreferencesHelper mPreferencesHelper;
+ @VisibleForTesting
+ PreferencesHelper mPreferencesHelper;
private final UserProfiles mUserProfiles = new UserProfiles();
private NotificationListeners mListeners;
@@ -1008,12 +1011,14 @@
if (clearEffects) {
clearEffects();
}
+ mAssistants.onPanelRevealed(items);
}
@Override
public void onPanelHidden() {
MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL);
EventLogTags.writeNotificationPanelHidden();
+ mAssistants.onPanelHidden();
}
@Override
@@ -1060,6 +1065,7 @@
reportSeen(r);
}
r.setVisibility(true, nv.rank, nv.count);
+ mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, true);
boolean isHun = (nv.location
== NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
// hasBeenVisiblyExpanded must be called after updating the expansion state of
@@ -1078,6 +1084,7 @@
NotificationRecord r = mNotificationsByKey.get(nv.key);
if (r == null) continue;
r.setVisibility(false, nv.rank, nv.count);
+ mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, false);
nv.recycle();
}
}
@@ -2219,8 +2226,8 @@
maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
if (!fromListener) {
- final NotificationChannel modifiedChannel =
- mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ final NotificationChannel modifiedChannel = mPreferencesHelper.getNotificationChannel(
+ pkg, uid, channel.getId(), false);
mListeners.notifyNotificationChannelChanged(
pkg, UserHandle.getUserHandleForUid(uid),
modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
@@ -3017,31 +3024,55 @@
@Override
public void createNotificationChannels(String pkg,
- ParceledListSlice channelsList) throws RemoteException {
+ ParceledListSlice channelsList) {
checkCallerIsSystemOrSameApp(pkg);
createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList);
}
@Override
public void createNotificationChannelsForPackage(String pkg, int uid,
- ParceledListSlice channelsList) throws RemoteException {
- checkCallerIsSystem();
+ ParceledListSlice channelsList) {
+ enforceSystemOrSystemUI("only system can call this");
createNotificationChannelsImpl(pkg, uid, channelsList);
}
@Override
+ public void createConversationNotificationChannelForPackage(String pkg, int uid,
+ NotificationChannel parentChannel, String conversationId) {
+ enforceSystemOrSystemUI("only system can call this");
+ Preconditions.checkNotNull(parentChannel);
+ Preconditions.checkNotNull(conversationId);
+ String parentId = parentChannel.getId();
+ NotificationChannel conversationChannel = parentChannel;
+ conversationChannel.setId(String.format(
+ CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
+ conversationChannel.setConversationId(parentId, conversationId);
+ createNotificationChannelsImpl(
+ pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+ }
+
+ @Override
public NotificationChannel getNotificationChannel(String callingPkg, int userId,
String targetPkg, String channelId) {
+ return getConversationNotificationChannel(
+ callingPkg, userId, targetPkg, channelId, true, null);
+ }
+
+ @Override
+ public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId,
+ String targetPkg, String channelId, boolean returnParentIfNoConversationChannel,
+ String conversationId) {
if (canNotifyAsPackage(callingPkg, targetPkg, userId)
- || isCallingUidSystem()) {
+ || isCallerIsSystemOrSystemUi()) {
int targetUid = -1;
try {
targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
} catch (NameNotFoundException e) {
/* ignore */
}
- return mPreferencesHelper.getNotificationChannel(
- targetPkg, targetUid, channelId, false /* includeDeleted */);
+ return mPreferencesHelper.getConversationNotificationChannel(
+ targetPkg, targetUid, channelId, conversationId,
+ returnParentIfNoConversationChannel, false /* includeDeleted */);
}
throw new SecurityException("Pkg " + callingPkg
+ " cannot read channels for " + targetPkg + " in " + userId);
@@ -3072,6 +3103,30 @@
}
@Override
+ public void deleteConversationNotificationChannels(String pkg, int uid,
+ String conversationId) {
+ checkCallerIsSystem();
+ final int callingUid = Binder.getCallingUid();
+ List<NotificationChannel> channels =
+ mPreferencesHelper.getNotificationChannelsByConversationId(
+ pkg, uid, conversationId);
+ if (!channels.isEmpty()) {
+ for (NotificationChannel nc : channels) {
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true,
+ UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
+ mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId());
+ mListeners.notifyNotificationChannelChanged(pkg,
+ UserHandle.getUserHandleForUid(callingUid),
+ mPreferencesHelper.getNotificationChannel(
+ pkg, callingUid, nc.getId(), true),
+ NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
+ }
+ handleSavePolicyFile();
+ }
+ }
+
+
+ @Override
public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
checkCallerIsSystemOrSameApp(pkg);
return mPreferencesHelper.getNotificationChannelGroupWithChannels(
@@ -5202,8 +5257,15 @@
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
channelId = (new Notification.TvExtender(notification)).getChannelId();
}
- final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
- notificationUid, channelId, false /* includeDeleted */);
+ // TODO: flag this behavior
+ String shortcutId = notification.getShortcutId();
+ if (shortcutId == null
+ && notification.getNotificationStyle() == Notification.MessagingStyle.class) {
+ shortcutId = id + tag + NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+ }
+ final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
+ pkg, notificationUid, channelId, shortcutId,
+ true /* parent ok */, false /* includeDeleted */);
if (channel == null) {
final String noChannelStr = "No Channel found for "
+ "pkg=" + pkg
@@ -7839,6 +7901,14 @@
return isUidSystemOrPhone(Binder.getCallingUid());
}
+ private boolean isCallerIsSystemOrSystemUi() {
+ if (isCallerSystemOrPhone()) {
+ return true;
+ }
+ return getContext().checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+ == PERMISSION_GRANTED;
+ }
+
private void checkCallerIsSystemOrShell() {
int callingUid = Binder.getCallingUid();
if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
@@ -8296,6 +8366,32 @@
}
}
+ protected void onPanelRevealed(int items) {
+ for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+ mHandler.post(() -> {
+ final INotificationListener assistant = (INotificationListener) info.service;
+ try {
+ assistant.onPanelRevealed(items);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unable to notify assistant (panel revealed): " + info, ex);
+ }
+ });
+ }
+ }
+
+ protected void onPanelHidden() {
+ for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+ mHandler.post(() -> {
+ final INotificationListener assistant = (INotificationListener) info.service;
+ try {
+ assistant.onPanelHidden();
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unable to notify assistant (panel hidden): " + info, ex);
+ }
+ });
+ }
+ }
+
boolean hasUserSet(int userId) {
synchronized (mLock) {
return mUserSetMap.getOrDefault(userId, false);
@@ -8363,6 +8459,24 @@
}
@GuardedBy("mNotificationLock")
+ void notifyAssistantVisibilityChangedLocked(
+ final StatusBarNotification sbn,
+ final boolean isVisible) {
+ final String key = sbn.getKey();
+ Slog.d(TAG, "notifyAssistantVisibilityChangedLocked: " + key);
+ notifyAssistantLocked(
+ sbn,
+ false /* sameUserOnly */,
+ (assistant, sbnHolder) -> {
+ try {
+ assistant.onNotificationVisibilityChanged(key, isVisible);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unable to notify assistant (visible): " + assistant, ex);
+ }
+ });
+ }
+
+ @GuardedBy("mNotificationLock")
void notifyAssistantExpansionChangedLocked(
final StatusBarNotification sbn,
final boolean isUserAction,
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index cdb0a17..185d75c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -770,6 +770,13 @@
channel.setShowBadge(false);
}
channel.setOriginalImportance(channel.getImportance());
+
+ // validate parent
+ if (channel.getParentChannelId() != null) {
+ Preconditions.checkArgument(r.channels.containsKey(channel.getParentChannelId()),
+ "Tried to create a conversation channel without a preexisting parent");
+ }
+
r.channels.put(channel.getId(), channel);
if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
updateChannelsBypassingDnd(mContext.getUserId());
@@ -851,6 +858,14 @@
public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted) {
Objects.requireNonNull(pkg);
+ return getConversationNotificationChannel(pkg, uid, channelId, null, true, includeDeleted);
+ }
+
+ @Override
+ public NotificationChannel getConversationNotificationChannel(String pkg, int uid,
+ String channelId, String conversationId, boolean returnParentIfNoConversationChannel,
+ boolean includeDeleted) {
+ Preconditions.checkNotNull(pkg);
synchronized (mPackagePreferences) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
@@ -859,11 +874,51 @@
if (channelId == null) {
channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
}
- final NotificationChannel nc = r.channels.get(channelId);
- if (nc != null && (includeDeleted || !nc.isDeleted())) {
+ NotificationChannel channel = null;
+ if (conversationId != null) {
+ // look for an automatically created conversation specific channel
+ channel = findConversationChannel(r, channelId, conversationId, includeDeleted);
+ }
+ if (channel == null && returnParentIfNoConversationChannel) {
+ // look for it just based on its id
+ final NotificationChannel nc = r.channels.get(channelId);
+ if (nc != null && (includeDeleted || !nc.isDeleted())) {
+ return nc;
+ }
+ }
+ return channel;
+ }
+ }
+
+ private NotificationChannel findConversationChannel(PackagePreferences p, String parentId,
+ String conversationId, boolean includeDeleted) {
+ for (NotificationChannel nc : p.channels.values()) {
+ if (conversationId.equals(nc.getConversationId())
+ && parentId.equals(nc.getParentChannelId())
+ && (includeDeleted || !nc.isDeleted())) {
return nc;
}
- return null;
+ }
+ return null;
+ }
+
+ public List<NotificationChannel> getNotificationChannelsByConversationId(String pkg, int uid,
+ String conversationId) {
+ Preconditions.checkNotNull(pkg);
+ Preconditions.checkNotNull(conversationId);
+ List<NotificationChannel> channels = new ArrayList<>();
+ synchronized (mPackagePreferences) {
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
+ if (r == null) {
+ return channels;
+ }
+ for (NotificationChannel nc : r.channels.values()) {
+ if (conversationId.equals(nc.getConversationId())
+ && !nc.isDeleted()) {
+ channels.add(nc);
+ }
+ }
+ return channels;
}
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 7816f36..7e98be7 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -41,10 +41,16 @@
int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty);
boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp, boolean hasDndAccess);
- void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
- NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
+ void updateNotificationChannel(String pkg, int uid, NotificationChannel channel,
+ boolean fromUser);
+ NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+ boolean includeDeleted);
+ NotificationChannel getConversationNotificationChannel(String pkg, int uid, String channelId,
+ String conversationId, boolean returnParentIfNoConversationChannel,
+ boolean includeDeleted);
void deleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannels(String pkg, int uid);
- ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted);
+ ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+ boolean includeDeleted);
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index b782ca96..3c31f6a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -83,6 +83,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -925,7 +926,7 @@
/**
* Updates the target packages' set of enabled overlays in PackageManager.
*/
- private void updateOverlayPaths(int userId, List<String> targetPackageNames) {
+ private ArrayList<String> updateOverlayPaths(int userId, List<String> targetPackageNames) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames);
if (DEBUG) {
@@ -955,6 +956,7 @@
}
}
+ final HashSet<String> updatedPackages = new HashSet<>();
final int n = targetPackageNames.size();
for (int i = 0; i < n; i++) {
final String targetPackageName = targetPackageNames.get(i);
@@ -965,11 +967,13 @@
}
if (!pm.setEnabledOverlayPackages(
- userId, targetPackageName, pendingChanges.get(targetPackageName))) {
+ userId, targetPackageName, pendingChanges.get(targetPackageName),
+ updatedPackages)) {
Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
targetPackageName, userId));
}
}
+ return new ArrayList<>(updatedPackages);
} finally {
traceEnd(TRACE_TAG_RRO);
}
@@ -980,10 +984,10 @@
}
private void updateAssets(final int userId, List<String> targetPackageNames) {
- updateOverlayPaths(userId, targetPackageNames);
final IActivityManager am = ActivityManager.getService();
try {
- am.scheduleApplicationInfoChanged(targetPackageNames, userId);
+ final ArrayList<String> updatedPaths = updateOverlayPaths(userId, targetPackageNames);
+ am.scheduleApplicationInfoChanged(updatedPaths, userId);
} catch (RemoteException e) {
// Intentionally left empty.
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 019c952..9623542 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -685,7 +685,7 @@
// Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
if (targetPackage != null && overlayPackage != null
&& !("android".equals(targetPackageName)
- && overlayPackage.isStaticOverlayPackage())) {
+ && overlayPackage.isStaticOverlayPackage())) {
mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
}
@@ -703,9 +703,9 @@
if (currentState != newState) {
if (DEBUG) {
Slog.d(TAG, String.format("%s:%d: %s -> %s",
- overlayPackageName, userId,
- OverlayInfo.stateToString(currentState),
- OverlayInfo.stateToString(newState)));
+ overlayPackageName, userId,
+ OverlayInfo.stateToString(currentState),
+ OverlayInfo.stateToString(newState)));
}
modified |= mSettings.setState(overlayPackageName, userId, newState);
}
diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING
new file mode 100644
index 0000000..52163a0
--- /dev/null
+++ b/services/core/java/com/android/server/om/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.om."
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java
index 31d30362..c5b868f 100644
--- a/services/core/java/com/android/server/people/PeopleServiceInternal.java
+++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java
@@ -16,9 +16,25 @@
package com.android.server.people;
+import android.annotation.NonNull;
import android.service.appprediction.IPredictionService;
/**
* @hide Only for use within the system server.
*/
-public abstract class PeopleServiceInternal extends IPredictionService.Stub {}
+public abstract class PeopleServiceInternal extends IPredictionService.Stub {
+
+ /**
+ * The number conversation infos will be dynamic, based on the currently installed apps on the
+ * device. All of which should be combined into a single blob to be backed up.
+ */
+ public abstract byte[] backupConversationInfos(@NonNull int userId);
+
+ /**
+ * Multiple conversation infos may exist in the restore payload, child classes are required to
+ * manage the restoration based on how individual conversation infos were originally combined
+ * during backup.
+ */
+ public abstract void restoreConversationInfos(@NonNull int userId, @NonNull String key,
+ @NonNull byte[] payload);
+}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 307a07b..2807909 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -32,10 +32,13 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
+import android.content.pm.parsing.AndroidPackage;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.sysprop.ApexProperties;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Singleton;
import android.util.Slog;
@@ -44,15 +47,19 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
+import com.google.android.collect.Lists;
+
import java.io.File;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -97,12 +104,27 @@
* Minimal information about APEX mount points and the original APEX package they refer to.
*/
static class ActiveApexInfo {
+ @Nullable public final String apexModuleName;
public final File apexDirectory;
- public final File preinstalledApexPath;
+ public final File preInstalledApexPath;
- private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) {
+ private ActiveApexInfo(File apexDirectory, File preInstalledApexPath) {
+ this(null, apexDirectory, preInstalledApexPath);
+ }
+
+ private ActiveApexInfo(@Nullable String apexModuleName, File apexDirectory,
+ File preInstalledApexPath) {
+ this.apexModuleName = apexModuleName;
this.apexDirectory = apexDirectory;
- this.preinstalledApexPath = preinstalledApexPath;
+ this.preInstalledApexPath = preInstalledApexPath;
+ }
+
+ private ActiveApexInfo(ApexInfo apexInfo) {
+ this(
+ apexInfo.moduleName,
+ new File(Environment.getApexDirectory() + File.separator
+ + apexInfo.moduleName),
+ new File(apexInfo.preinstalledModulePath));
}
}
@@ -232,6 +254,17 @@
abstract boolean uninstallApex(String apexPackagePath);
/**
+ * Registers an APK package as an embedded apk of apex.
+ */
+ abstract void registerApkInApex(AndroidPackage pkg);
+
+ /**
+ * Returns list of {@code packageName} of apks inside the given apex.
+ * @param apexPackageName Package name of the apk container of apex
+ */
+ abstract List<String> getApksInApex(String apexPackageName);
+
+ /**
* Dumps various state information to the provided {@link PrintWriter} object.
*
* @param pw the {@link PrintWriter} object to send information to.
@@ -255,16 +288,33 @@
static class ApexManagerImpl extends ApexManager {
private final IApexService mApexService;
private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private Set<ActiveApexInfo> mActiveApexInfosCache;
+
/**
- * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
- * AndroidManifest.xml}
- *
- * <p>Note that key of this map is {@code packageName} field of the corresponding {@code
- * AndroidManifest.xml}.
- */
+ * Contains the list of {@code packageName}s of apks-in-apex for given
+ * {@code apexModuleName}. See {@link #mPackageNameToApexModuleName} to understand the
+ * difference between {@code packageName} and {@code apexModuleName}.
+ */
+ @GuardedBy("mLock")
+ private Map<String, List<String>> mApksInApex = new ArrayMap<>();
+
@GuardedBy("mLock")
private List<PackageInfo> mAllPackagesCache;
+ /**
+ * An APEX is a file format that delivers the apex-payload wrapped in an apk container. The
+ * apk container has a reference name, called {@code packageName}, which is found inside the
+ * {@code AndroidManifest.xml}. The apex payload inside the container also has a reference
+ * name, called {@code apexModuleName}, which is found in {@code apex_manifest.json} file.
+ *
+ * {@link #mPackageNameToApexModuleName} contains the mapping from {@code packageName} of
+ * the apk container to {@code apexModuleName} of the apex-payload inside.
+ */
+ @GuardedBy("mLock")
+ private Map<String, String> mPackageNameToApexModuleName;
+
ApexManagerImpl(IApexService apexService) {
mApexService = apexService;
}
@@ -291,18 +341,25 @@
@Override
List<ActiveApexInfo> getActiveApexInfos() {
- try {
- return Arrays.stream(mApexService.getActivePackages())
- .map(apexInfo -> new ActiveApexInfo(
- new File(
- Environment.getApexDirectory() + File.separator
- + apexInfo.moduleName),
- new File(apexInfo.preinstalledModulePath))).collect(
- Collectors.toList());
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
+ synchronized (mLock) {
+ if (mActiveApexInfosCache == null) {
+ try {
+ mActiveApexInfosCache = new ArraySet<>();
+ final ApexInfo[] activePackages = mApexService.getActivePackages();
+ for (int i = 0; i < activePackages.length; i++) {
+ ApexInfo apexInfo = activePackages[i];
+ mActiveApexInfosCache.add(new ActiveApexInfo(apexInfo));
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
+ }
+ }
+ if (mActiveApexInfosCache != null) {
+ return new ArrayList<>(mActiveApexInfosCache);
+ } else {
+ return Collections.emptyList();
+ }
}
- return Collections.emptyList();
}
@Override
@@ -318,6 +375,33 @@
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
+ private void populatePackageNameToApexModuleNameIfNeeded() {
+ synchronized (mLock) {
+ if (mPackageNameToApexModuleName != null) {
+ return;
+ }
+ try {
+ mPackageNameToApexModuleName = new ArrayMap<>();
+ final ApexInfo[] allPkgs = mApexService.getAllPackages();
+ for (int i = 0; i < allPkgs.length; i++) {
+ ApexInfo ai = allPkgs[i];
+ PackageParser.PackageLite pkgLite;
+ try {
+ File apexFile = new File(ai.modulePath);
+ pkgLite = PackageParser.parsePackageLite(apexFile, 0);
+ } catch (PackageParser.PackageParserException pe) {
+ throw new IllegalStateException("Unable to parse: "
+ + ai.modulePath, pe);
+ }
+ mPackageNameToApexModuleName.put(pkgLite.packageName, ai.moduleName);
+ }
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to retrieve packages from apexservice: ", re);
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
private void populateAllPackagesCacheIfNeeded() {
synchronized (mLock) {
if (mAllPackagesCache != null) {
@@ -366,7 +450,6 @@
}
factoryPackagesSet.add(packageInfo.packageName);
}
-
}
} catch (RemoteException re) {
Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
@@ -533,6 +616,36 @@
}
}
+ @Override
+ void registerApkInApex(AndroidPackage pkg) {
+ synchronized (mLock) {
+ final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator();
+ while (it.hasNext()) {
+ final ActiveApexInfo aai = it.next();
+ if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) {
+ List<String> apks = mApksInApex.get(aai.apexModuleName);
+ if (apks == null) {
+ apks = Lists.newArrayList();
+ mApksInApex.put(aai.apexModuleName, apks);
+ }
+ apks.add(pkg.getPackageName());
+ }
+ }
+ }
+ }
+
+ @Override
+ List<String> getApksInApex(String apexPackageName) {
+ populatePackageNameToApexModuleNameIfNeeded();
+ synchronized (mLock) {
+ String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
+ if (moduleName == null) {
+ return Collections.emptyList();
+ }
+ return mApksInApex.getOrDefault(moduleName, Collections.emptyList());
+ }
+ }
+
/**
* Dump information about the packages contained in a particular cache
* @param packagesCache the cache to print information about.
@@ -614,7 +727,6 @@
* updating APEX packages.
*/
private static final class ApexManagerFlattenedApex extends ApexManager {
-
@Override
List<ActiveApexInfo> getActiveApexInfos() {
// There is no apexd running in case of flattened apex
@@ -721,6 +833,16 @@
}
@Override
+ void registerApkInApex(AndroidPackage pkg) {
+ // No-op
+ }
+
+ @Override
+ List<String> getApksInApex(String apexPackageName) {
+ return Collections.emptyList();
+ }
+
+ @Override
void dump(PrintWriter pw, String packageName) {
// No-op
}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index c4bcf80..3ed3534 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -53,6 +53,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.StringTokenizer;
/**
* The entity responsible for filtering visibility between apps based on declarations in their
@@ -219,10 +220,15 @@
continue;
}
final Uri data = intent.getData();
- if ("content".equalsIgnoreCase(intent.getScheme())
- && data != null
- && Objects.equals(provider.getAuthority(), data.getAuthority())) {
- return true;
+ if (!"content".equalsIgnoreCase(intent.getScheme()) || data == null
+ || provider.getAuthority() == null) {
+ continue;
+ }
+ StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", false);
+ while (authorities.hasMoreElements()) {
+ if (Objects.equals(authorities.nextElement(), data.getAuthority())) {
+ return true;
+ }
}
}
for (int s = ArrayUtils.size(potentialTarget.getServices()) - 1; s >= 0; s--) {
@@ -449,6 +455,7 @@
}
final PackageSetting callingPkgSetting;
final ArraySet<PackageSetting> callingSharedPkgSettings;
+ Trace.beginSection("callingSetting instanceof");
if (callingSetting instanceof PackageSetting) {
callingPkgSetting = (PackageSetting) callingSetting;
callingSharedPkgSettings = null;
@@ -456,6 +463,7 @@
callingPkgSetting = null;
callingSharedPkgSettings = ((SharedUserSetting) callingSetting).packages;
}
+ Trace.endSection();
if (callingPkgSetting != null) {
if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
@@ -485,6 +493,7 @@
return true;
}
final String targetName = targetPkg.getPackageName();
+ Trace.beginSection("getAppId");
final int callingAppId;
if (callingPkgSetting != null) {
callingAppId = callingPkgSetting.appId;
@@ -492,6 +501,7 @@
callingAppId = callingSharedPkgSettings.valueAt(0).appId; // all should be the same
}
final int targetAppId = targetPkgSetting.appId;
+ Trace.endSection();
if (callingAppId == targetAppId) {
if (DEBUG_LOGGING) {
log(callingSetting, targetPkgSetting, "same app id");
@@ -499,38 +509,64 @@
return false;
}
- if (callingSetting.getPermissionsState().hasPermission(
- Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "has query-all permission");
+ try {
+ Trace.beginSection("hasPermission");
+ if (callingSetting.getPermissionsState().hasPermission(
+ Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "has query-all permission");
+ }
+ return false;
}
- return false;
+ } finally {
+ Trace.endSection();
}
- if (mForceQueryable.contains(targetAppId)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "force queryable");
+ try {
+ Trace.beginSection("mForceQueryable");
+ if (mForceQueryable.contains(targetAppId)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "force queryable");
+ }
+ return false;
}
- return false;
+ } finally {
+ Trace.endSection();
}
- if (mQueriesViaPackage.contains(callingAppId, targetAppId)) {
- // the calling package has explicitly declared the target package; allow
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "queries package");
+ try {
+ Trace.beginSection("mQueriesViaPackage");
+ if (mQueriesViaPackage.contains(callingAppId, targetAppId)) {
+ // the calling package has explicitly declared the target package; allow
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "queries package");
+ }
+ return false;
}
- return false;
- } else if (mQueriesViaIntent.contains(callingAppId, targetAppId)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "queries intent");
+ } finally {
+ Trace.endSection();
+ }
+ try {
+ Trace.beginSection("mQueriesViaIntent");
+ if (mQueriesViaIntent.contains(callingAppId, targetAppId)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "queries intent");
+ }
+ return false;
}
- return false;
+ } finally {
+ Trace.endSection();
}
- final int targetUid = UserHandle.getUid(userId, targetAppId);
- if (mImplicitlyQueryable.contains(callingUid, targetUid)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "implicitly queryable for user");
+ try {
+ Trace.beginSection("mImplicitlyQueryable");
+ final int targetUid = UserHandle.getUid(userId, targetAppId);
+ if (mImplicitlyQueryable.contains(callingUid, targetUid)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "implicitly queryable for user");
+ }
+ return false;
}
- return false;
+ } finally {
+ Trace.endSection();
}
if (callingPkgSetting != null) {
if (callingPkgInstruments(callingPkgSetting, targetPkgSetting, targetName)) {
@@ -576,17 +612,22 @@
private static boolean callingPkgInstruments(PackageSetting callingPkgSetting,
PackageSetting targetPkgSetting,
String targetName) {
- final List<ComponentParseUtils.ParsedInstrumentation> inst =
- callingPkgSetting.pkg.getInstrumentations();
- for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) {
- if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) {
- if (DEBUG_LOGGING) {
- log(callingPkgSetting, targetPkgSetting, "instrumentation");
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingPkgInstruments");
+ final List<ComponentParseUtils.ParsedInstrumentation> inst =
+ callingPkgSetting.pkg.getInstrumentations();
+ for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) {
+ if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) {
+ if (DEBUG_LOGGING) {
+ log(callingPkgSetting, targetPkgSetting, "instrumentation");
+ }
+ return true;
}
- return true;
}
+ return false;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- return false;
}
private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting,
@@ -597,7 +638,7 @@
private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting,
String description, Throwable throwable) {
Slog.wtf(TAG,
- "interaction: " + callingPkgSetting.toString()
+ "interaction: " + callingPkgSetting
+ " -> " + targetPkgSetting.name + " "
+ description, throwable);
}
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index ffcd6cf..bcfe577 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -338,9 +338,8 @@
}
@GuardedBy("mService.mLock")
- public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg,
+ public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg, @Nullable PackageSetting ps,
@NonNull int[] userIds) {
- PackageSetting ps = mService.getPackageSetting(pkg.getPackageName());
if (ps == null) {
return;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index c43f234..6331dd4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -188,7 +188,7 @@
}
};
- public PackageInstallerService(Context context, PackageManagerService pm, ApexManager am) {
+ public PackageInstallerService(Context context, PackageManagerService pm) {
mContext = context;
mPm = pm;
mPermissionManager = LocalServices.getService(PermissionManagerServiceInternal.class);
@@ -206,9 +206,8 @@
mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
mSessionsDir.mkdirs();
- mApexManager = am;
-
- mStagingManager = new StagingManager(this, am, context);
+ mApexManager = ApexManager.getInstance();
+ mStagingManager = new StagingManager(this, context);
}
boolean okToSendBroadcasts() {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 97a2d43..165bdeb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2413,16 +2413,6 @@
@Override
public void addFile(String name, long lengthBytes, byte[] metadata) {
- if (mIncrementalFileStorages != null) {
- try {
- mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata));
- //TODO(b/136132412): merge incremental and callback installation schemes
- return;
- } catch (IOException ex) {
- throw new IllegalStateException(
- "Failed to add and configure Incremental File: " + name, ex);
- }
- }
if (!isDataLoaderInstallation()) {
throw new IllegalStateException(
"Cannot add files to non-data loader installation session.");
@@ -2435,7 +2425,6 @@
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotSealedLocked("addFile");
-
mFiles.add(FileInfo.added(name, lengthBytes, metadata));
}
}
@@ -2486,7 +2475,7 @@
*/
private void prepareDataLoader()
throws PackageManagerException, StreamingException {
- if (!isStreamingInstallation()) {
+ if (!isDataLoaderInstallation()) {
return;
}
@@ -2500,6 +2489,18 @@
file -> file.name.substring(
0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect(
Collectors.toList());
+ if (mIncrementalFileStorages != null) {
+ for (InstallationFile file : addedFiles) {
+ try {
+ mIncrementalFileStorages.addFile(file);
+ } catch (IOException ex) {
+ // TODO(b/146080380): add incremental-specific error code
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to add and configure Incremental File: " + file.getName(), ex);
+ }
+ }
+ return;
+ }
final FileSystemConnector connector = new FileSystemConnector(addedFiles);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b26e6c7..410017a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -190,6 +190,7 @@
import android.content.pm.ServiceInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
@@ -492,6 +493,7 @@
static final int SCAN_AS_PRODUCT = 1 << 20;
static final int SCAN_AS_SYSTEM_EXT = 1 << 21;
static final int SCAN_AS_ODM = 1 << 22;
+ static final int SCAN_AS_APK_IN_APEX = 1 << 23;
@IntDef(flag = true, prefix = { "SCAN_" }, value = {
SCAN_NO_DEX,
@@ -2589,6 +2591,9 @@
& (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) {
return true;
}
+ if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
+ return true;
+ }
return false;
}
@@ -3332,7 +3337,7 @@
}
}
- mInstallerService = new PackageInstallerService(mContext, this, mApexManager);
+ mInstallerService = new PackageInstallerService(mContext, this);
final Pair<ComponentName, String> instantAppResolverComponent =
getInstantAppResolverLPr();
if (instantAppResolverComponent != null) {
@@ -5344,8 +5349,9 @@
if (!mUserManager.exists(userId)) return null;
final int callingUid = Binder.getCallingUid();
flags = updateFlagsForComponent(flags, userId);
- mPermissionManager.enforceCrossUserPermission(callingUid, userId,
- false /* requireFullPermission */, false /* checkShell */, "get service info");
+ mPermissionManager.enforceCrossUserOrProfilePermission(
+ callingUid, userId, false /* requireFullPermission */, false /* checkShell */,
+ "get service info");
synchronized (mLock) {
ParsedService s = mComponentResolver.getService(component);
if (DEBUG_PACKAGE_INFO) Log.v(
@@ -7795,8 +7801,10 @@
String resolvedType, int flags, int userId, int callingUid,
boolean includeInstantApps) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
- mPermissionManager.enforceCrossUserPermission(callingUid, userId,
- false /*requireFullPermission*/, false /*checkShell*/,
+ mPermissionManager.enforceCrossUserOrProfilePermission(callingUid,
+ userId,
+ false /*requireFullPermission*/,
+ false /*checkShell*/,
"query intent receivers");
final String instantAppPkgName = getInstantAppPackageName(callingUid);
flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps);
@@ -11595,6 +11603,23 @@
return false;
}
SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx);
+
+ // Remove the shared library overlays from its dependent packages.
+ for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+ final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+ libraryInfo, 0, currentUserId);
+ if (dependents == null) {
+ continue;
+ }
+ for (VersionedPackage dependentPackage : dependents) {
+ final PackageSetting ps = mSettings.mPackages.get(
+ dependentPackage.getPackageName());
+ if (ps != null) {
+ ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId);
+ }
+ }
+ }
+
versionedLib.remove(version);
if (versionedLib.size() <= 0) {
mSharedLibraries.remove(name);
@@ -11710,6 +11735,9 @@
mSettings.insertPackageSettingLPw(pkgSetting, pkg);
// Add the new setting to mPackages
mPackages.put(pkg.getAppInfoPackageName(), pkg);
+ if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
+ mApexManager.registerApkInApex(pkg);
+ }
// Add the package's KeySets to the global KeySetManagerService
KeySetManagerService ksms = mSettings.mKeySetManagerService;
@@ -15200,6 +15228,29 @@
// upcoming call to mSettings.writeLPr().
}
}
+
+ // Retrieve the overlays for shared libraries of the package.
+ if (pkg.getUsesLibraryInfos() != null) {
+ for (SharedLibraryInfo sharedLib : pkg.getUsesLibraryInfos()) {
+ for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+ if (!sharedLib.isDynamic()) {
+ // TODO(146804378): Support overlaying static shared libraries
+ continue;
+ }
+ final PackageSetting libPs = mSettings.mPackages.get(
+ sharedLib.getPackageName());
+ if (libPs == null) {
+ continue;
+ }
+ final String[] overlayPaths = libPs.getOverlayPaths(currentUserId);
+ if (overlayPaths != null) {
+ ps.setOverlayPathsForLibrary(sharedLib.getName(),
+ Arrays.asList(overlayPaths), currentUserId);
+ }
+ }
+ }
+ }
+
// It's implied that when a user requests installation, they want the app to be
// installed and enabled.
if (userId != UserHandle.USER_ALL) {
@@ -17452,7 +17503,8 @@
synchronized (mLock) {
if (res) {
if (pkg != null) {
- mInstantAppRegistry.onPackageUninstalledLPw(pkg, info.removedUsers);
+ mInstantAppRegistry.onPackageUninstalledLPw(pkg, uninstalledPs,
+ info.removedUsers);
}
updateSequenceNumberLP(uninstalledPs, info.removedUsers);
updateInstantAppInstallerLocked(packageName);
@@ -17757,10 +17809,10 @@
ApexManager.ActiveApexInfo apexInfo) {
for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
SystemPartition sp = SYSTEM_PARTITIONS.get(i);
- if (apexInfo.preinstalledApexPath.getAbsolutePath().startsWith(
+ if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
sp.folder.getAbsolutePath())) {
- return new SystemPartition(apexInfo.apexDirectory, sp.scanFlag,
- false /* hasOverlays */);
+ return new SystemPartition(apexInfo.apexDirectory,
+ sp.scanFlag | SCAN_AS_APK_IN_APEX, false /* hasOverlays */);
}
}
return null;
@@ -19970,8 +20022,9 @@
String initiatingPackageName;
String originatingPackageName;
+ final InstallSource installSource;
synchronized (mLock) {
- final InstallSource installSource = getInstallSourceLocked(packageName, callingUid);
+ installSource = getInstallSourceLocked(packageName, callingUid);
if (installSource == null) {
return null;
}
@@ -19985,9 +20038,16 @@
}
if (installSource.isInitiatingPackageUninstalled) {
- // TODO(b/146555198) Allow the app itself to see the info
- // (at least for non-instant apps)
- initiatingPackageName = null;
+ // We can't check visibility in the usual way, since the initiating package is no
+ // longer present. So we apply simpler rules to whether to expose the info:
+ // 1. Instant apps can't see it.
+ // 2. Otherwise only the installed app itself can see it.
+ final boolean isInstantApp = getInstantAppPackageName(callingUid) != null;
+ if (!isInstantApp && isCallerSameApp(packageName, callingUid)) {
+ initiatingPackageName = installSource.initiatingPackageName;
+ } else {
+ initiatingPackageName = null;
+ }
} else {
// All installSource strings are interned, so == is ok here
if (installSource.initiatingPackageName == installSource.installerPackageName) {
@@ -20012,13 +20072,27 @@
}
}
+ // Remaining work can safely be done outside the lock. (Note that installSource is
+ // immutable so it's ok to carry on reading from it.)
+
if (originatingPackageName != null && mContext.checkCallingOrSelfPermission(
Manifest.permission.INSTALL_PACKAGES) != PackageManager.PERMISSION_GRANTED) {
originatingPackageName = null;
}
- return new InstallSourceInfo(initiatingPackageName, originatingPackageName,
- installerPackageName);
+ // If you can see the initiatingPackageName, and we have valid signing info for it,
+ // then we let you see that too.
+ final SigningInfo initiatingPackageSigningInfo;
+ final PackageSignatures signatures = installSource.initiatingPackageSignatures;
+ if (initiatingPackageName != null && signatures != null
+ && signatures.mSigningDetails != SigningDetails.UNKNOWN) {
+ initiatingPackageSigningInfo = new SigningInfo(signatures.mSigningDetails);
+ } else {
+ initiatingPackageSigningInfo = null;
+ }
+
+ return new InstallSourceInfo(initiatingPackageName, initiatingPackageSigningInfo,
+ originatingPackageName, installerPackageName);
}
@GuardedBy("mLock")
@@ -20122,8 +20196,7 @@
// Disable any carrier apps. We do this very early in boot to prevent the apps from being
// disabled after already being started.
CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
- mPermissionManagerService, mContext.getContentResolver(),
- UserHandle.USER_SYSTEM);
+ mPermissionManagerService, UserHandle.USER_SYSTEM, mContext);
disableSkuSpecificApps();
@@ -22217,6 +22290,13 @@
mPermissionManager.onNewUserCreated(userId);
}
+ boolean readPermissionStateForUser(@UserIdInt int userId) {
+ synchronized (mPackages) {
+ mSettings.readPermissionStateForUserSyncLPr(userId);
+ return mSettings.areDefaultRuntimePermissionsGrantedLPr(userId);
+ }
+ }
+
@Override
public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
mContext.enforceCallingOrSelfPermission(
@@ -22791,7 +22871,7 @@
ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length);
for (String pkgName: pkgNames) {
- synchronized (mPackages) {
+ synchronized (mLock) {
if (pkgName == null) {
continue;
}
@@ -23240,9 +23320,11 @@
@Override
public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
- @Nullable List<String> overlayPackageNames) {
+ @Nullable List<String> overlayPackageNames,
+ @NonNull Collection<String> outUpdatedPackageNames) {
synchronized (mLock) {
- if (targetPackageName == null || mPackages.get(targetPackageName) == null) {
+ final AndroidPackage targetPkg = mPackages.get(targetPackageName);
+ if (targetPackageName == null || targetPkg == null) {
Slog.e(TAG, "failed to find package " + targetPackageName);
return false;
}
@@ -23261,8 +23343,41 @@
}
}
+ ArraySet<String> updatedPackageNames = null;
+ if (targetPkg.getLibraryNames() != null) {
+ // Set the overlay paths for dependencies of the shared library.
+ updatedPackageNames = new ArraySet<>();
+ for (String libName : targetPkg.getLibraryNames()) {
+ final SharedLibraryInfo info = getSharedLibraryInfoLPr(libName,
+ SharedLibraryInfo.VERSION_UNDEFINED);
+ if (info == null) {
+ continue;
+ }
+ final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+ info, 0, userId);
+ if (dependents == null) {
+ continue;
+ }
+ for (VersionedPackage dependent : dependents) {
+ final PackageSetting ps = mSettings.mPackages.get(
+ dependent.getPackageName());
+ if (ps == null) {
+ continue;
+ }
+ ps.setOverlayPathsForLibrary(libName, overlayPaths, userId);
+ updatedPackageNames.add(dependent.getPackageName());
+ }
+ }
+ }
+
final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
ps.setOverlayPaths(overlayPaths, userId);
+
+ outUpdatedPackageNames.add(targetPackageName);
+ if (updatedPackageNames != null) {
+ outUpdatedPackageNames.addAll(updatedPackageNames);
+ }
+
return true;
}
}
@@ -23452,6 +23567,11 @@
}
@Override
+ public List<String> getApksInApex(String apexPackageName) {
+ return PackageManagerService.this.mApexManager.getApksInApex(apexPackageName);
+ }
+
+ @Override
public void uninstallApex(String packageName, long versionCode, int userId,
IntentSender intentSender, int flags) {
final int callerUid = Binder.getCallingUid();
@@ -23632,7 +23752,7 @@
@Nullable
public PackageSetting getPackageSetting(String packageName) {
- synchronized (mPackages) {
+ synchronized (mLock) {
packageName = resolveInternalPackageNameLPr(
packageName, PackageManager.VERSION_CODE_HIGHEST);
return mSettings.mPackages.get(packageName);
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 0c0b93b..f1ac0af 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -41,6 +41,7 @@
import java.io.File;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -312,12 +313,21 @@
}
void setOverlayPaths(List<String> overlayPaths, int userId) {
- modifyUserState(userId).overlayPaths = overlayPaths == null ? null :
- overlayPaths.toArray(new String[overlayPaths.size()]);
+ modifyUserState(userId).setOverlayPaths(overlayPaths == null ? null :
+ overlayPaths.toArray(new String[overlayPaths.size()]));
}
String[] getOverlayPaths(int userId) {
- return readUserState(userId).overlayPaths;
+ return readUserState(userId).getOverlayPaths();
+ }
+
+ void setOverlayPathsForLibrary(String libName, List<String> overlayPaths, int userId) {
+ modifyUserState(userId).setSharedLibraryOverlayPaths(libName,
+ overlayPaths == null ? null : overlayPaths.toArray(new String[0]));
+ }
+
+ Map<String, String[]> getOverlayPathsForLibrary(int userId) {
+ return readUserState(userId).getSharedLibraryOverlayPaths();
}
/** Only use for testing. Do NOT use in production code. */
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index ec84b51..4f18cb4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3125,6 +3125,10 @@
return true;
}
+ void readPermissionStateForUserSyncLPr(@UserIdInt int userId) {
+ mRuntimePermissionsPersistence.readStateForUserSyncLPr(userId);
+ }
+
void applyDefaultPreferredAppsLPw(int userId) {
// First pull data from any pre-installed apps.
final PackageManagerInternal pmInternal =
@@ -4742,6 +4746,22 @@
}
}
+ Map<String, String[]> sharedLibraryOverlayPaths =
+ ps.getOverlayPathsForLibrary(user.id);
+ if (sharedLibraryOverlayPaths != null) {
+ for (Map.Entry<String, String[]> libOverlayPaths :
+ sharedLibraryOverlayPaths.entrySet()) {
+ if (libOverlayPaths.getValue() == null) {
+ continue;
+ }
+ pw.print(prefix); pw.print(" ");
+ pw.print(libOverlayPaths.getKey()); pw.println(" overlay paths:");
+ for (String path : libOverlayPaths.getValue()) {
+ pw.print(prefix); pw.print(" "); pw.println(path);
+ }
+ }
+ }
+
String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
if (lastDisabledAppCaller != null) {
pw.print(prefix); pw.print(" lastDisabledCaller: ");
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 9e462cd..7888d1f 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -33,11 +33,13 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.ParceledListSlice;
+import android.content.pm.parsing.AndroidPackage;
import android.content.rollback.IRollbackManager;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
@@ -47,9 +49,12 @@
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.util.IntArray;
@@ -61,6 +66,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageHelper;
import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
import java.io.File;
import java.io.IOException;
@@ -93,10 +99,11 @@
@GuardedBy("mStagedSessions")
private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
- StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
+ StagingManager(PackageInstallerService pi, Context context) {
mPi = pi;
- mApexManager = am;
mContext = context;
+
+ mApexManager = ApexManager.getInstance();
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mPreRebootVerificationHandler = new PreRebootVerificationHandler(
BackgroundThread.get().getLooper());
@@ -334,6 +341,88 @@
return PackageHelper.getStorageManager().needsCheckpoint();
}
+ /**
+ * Apks inside apex are not installed using apk-install flow. They are scanned from the system
+ * directory directly by PackageManager, as such, RollbackManager need to handle their data
+ * separately here.
+ */
+ private void snapshotAndRestoreApkInApexUserData(PackageInstallerSession session) {
+ // We want to process apks inside apex. So current session needs to contain apex.
+ if (!sessionContainsApex(session)) {
+ return;
+ }
+
+ boolean doSnapshotOrRestore =
+ (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+ || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
+ if (!doSnapshotOrRestore) {
+ return;
+ }
+
+ // Find all the apex sessions that needs processing
+ List<PackageInstallerSession> apexSessions = new ArrayList<>();
+ if (session.isMultiPackage()) {
+ List<PackageInstallerSession> childrenSessions = new ArrayList<>();
+ synchronized (mStagedSessions) {
+ for (int childSessionId : session.getChildSessionIds()) {
+ PackageInstallerSession childSession = mStagedSessions.get(childSessionId);
+ if (childSession != null) {
+ childrenSessions.add(childSession);
+ }
+ }
+ }
+ for (PackageInstallerSession childSession : childrenSessions) {
+ if (sessionContainsApex(childSession)) {
+ apexSessions.add(childSession);
+ }
+ }
+ } else {
+ apexSessions.add(session);
+ }
+
+ // For each apex, process the apks inside it
+ for (PackageInstallerSession apexSession : apexSessions) {
+ List<String> apksInApex = mApexManager.getApksInApex(apexSession.getPackageName());
+ for (String apk: apksInApex) {
+ snapshotAndRestoreApkInApexUserData(apk);
+ }
+ }
+ }
+
+ private void snapshotAndRestoreApkInApexUserData(String packageName) {
+ IRollbackManager rm = IRollbackManager.Stub.asInterface(
+ ServiceManager.getService(Context.ROLLBACK_SERVICE));
+
+ PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class);
+ AndroidPackage pkg = mPmi.getPackage(packageName);
+ if (pkg == null) {
+ Slog.e(TAG, "Could not find package: " + packageName
+ + "for snapshotting/restoring user data.");
+ return;
+ }
+ final String seInfo = pkg.getSeInfo();
+ final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
+ final int[] allUsers = um.getUserIds();
+
+ int appId = -1;
+ long ceDataInode = -1;
+ final PackageSetting ps = (PackageSetting) mPmi.getPackageSetting(packageName);
+ if (ps != null && rm != null) {
+ appId = ps.appId;
+ ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM);
+ // NOTE: We ignore the user specified in the InstallParam because we know this is
+ // an update, and hence need to restore data for all installed users.
+ final int[] installedUsers = ps.queryInstalledUsers(allUsers, true);
+
+ try {
+ rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
+ seInfo, 0 /*token*/);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
+ }
+ }
+ }
+
private void resumeSession(@NonNull PackageInstallerSession session) {
Slog.d(TAG, "Resuming session " + session.sessionId);
@@ -407,6 +496,7 @@
abortCheckpoint();
return;
}
+ snapshotAndRestoreApkInApexUserData(session);
Slog.i(TAG, "APEX packages in session " + session.sessionId
+ " were successfully activated. Proceeding with APK packages, if any");
}
@@ -481,18 +571,17 @@
} else {
params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
}
- int apkSessionId = mPi.createSession(
- params, originalSession.getInstallerPackageName(),
- 0 /* UserHandle.SYSTEM */);
- PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
-
try {
+ int apkSessionId = mPi.createSession(
+ params, originalSession.getInstallerPackageName(),
+ 0 /* UserHandle.SYSTEM */);
+ PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
apkSession.open();
for (String apkFilePath : apkFilePaths) {
File apkFile = new File(apkFilePath);
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
ParcelFileDescriptor.MODE_READ_ONLY);
- long sizeBytes = pfd.getStatSize();
+ long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize();
if (sizeBytes < 0) {
Slog.e(TAG, "Unable to get size of: " + apkFilePath);
throw new PackageManagerException(errorCode,
@@ -500,11 +589,11 @@
}
apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
}
- } catch (IOException e) {
+ return apkSession;
+ } catch (IOException | ParcelableException e) {
Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
- throw new PackageManagerException(errorCode, "Failed to write APK session", e);
+ throw new PackageManagerException(errorCode, "Failed to create/write APK session", e);
}
- return apkSession;
}
/**
@@ -529,7 +618,7 @@
Arrays.stream(session.getChildSessionIds())
// Retrieve cached sessions matching ids.
.mapToObj(i -> mStagedSessions.get(i))
- // Filter only the ones containing APKs.s
+ // Filter only the ones containing APKs.
.filter(childSession -> !isApexSession(childSession))
.collect(Collectors.toList());
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e5d5b57..6ee8648 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -492,6 +492,10 @@
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mUms.cleanupPartialUsers();
+
+ if (mUms.mPm.isDeviceUpgrading()) {
+ mUms.cleanupPreCreatedUsers();
+ }
}
}
@@ -617,6 +621,33 @@
}
}
+ /**
+ * Removes any pre-created users from the system. Should be invoked after OTAs, to ensure
+ * pre-created users are not stale. New pre-created pool can be re-created after the update.
+ */
+ void cleanupPreCreatedUsers() {
+ final ArrayList<UserInfo> preCreatedUsers;
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ preCreatedUsers = new ArrayList<>(userSize);
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i).info;
+ if (ui.preCreated) {
+ preCreatedUsers.add(ui);
+ addRemovingUserIdLocked(ui.id);
+ ui.flags |= UserInfo.FLAG_DISABLED;
+ ui.partial = true;
+ }
+ }
+ }
+ final int preCreatedSize = preCreatedUsers.size();
+ for (int i = 0; i < preCreatedSize; i++) {
+ UserInfo ui = preCreatedUsers.get(i);
+ Slog.i(LOG_TAG, "Removing pre-created user " + ui.id);
+ removeUserState(ui.id);
+ }
+ }
+
@Override
public String getUserAccount(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("get user account");
@@ -3078,7 +3109,6 @@
@NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId,
boolean preCreate, @Nullable String[] disallowedPackages,
@NonNull TimingsTraceAndSlog t) {
-
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
if (userTypeDetails == null) {
Slog.e(LOG_TAG, "Cannot create user of invalid user type: " + userType);
@@ -3254,9 +3284,9 @@
mBaseUserRestrictions.append(userId, restrictions);
}
- t.traceBegin("PM.onNewUserCreated");
+ t.traceBegin("PM.onNewUserCreated-" + userId);
mPm.onNewUserCreated(userId);
-
+ t.traceEnd();
if (preCreate) {
// Must start user (which will be stopped right away, through
// UserController.finishUserUnlockedCompleted) so services can properly
@@ -3323,11 +3353,16 @@
preCreatedUser.preCreated = false;
preCreatedUser.creationTime = getCreationTime();
- dispatchUserAddedIntent(preCreatedUser);
synchronized (mPackagesLock) {
writeUserLP(preCreatedUserData);
writeUserListLP();
}
+ updateUserIds();
+ if (!mPm.readPermissionStateForUser(preCreatedUser.id)) {
+ // Could not read the existing permissions, re-grant them.
+ mPm.onNewUserCreated(preCreatedUser.id);
+ }
+ dispatchUserAddedIntent(preCreatedUser);
return preCreatedUser;
}
@@ -4027,14 +4062,16 @@
synchronized (mUsersLock) {
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).info.partial) {
+ UserInfo userInfo = mUsers.valueAt(i).info;
+ if (!userInfo.partial && !userInfo.preCreated) {
num++;
}
}
final int[] newUsers = new int[num];
int n = 0;
for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).info.partial) {
+ UserInfo userInfo = mUsers.valueAt(i).info;
+ if (!userInfo.partial && !userInfo.preCreated) {
newUsers[n++] = mUsers.keyAt(i);
}
}
@@ -4095,7 +4132,10 @@
* recycled.
*/
void reconcileUsers(String volumeUuid) {
- mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(true /* excludeDying */));
+ mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(
+ /* excludePartial= */ true,
+ /* excludeDying= */ true,
+ /* excludePreCreated= */ false));
}
/**
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 815f7b4..89030ed 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -199,13 +199,31 @@
);
/**
- * Special user restrictions that are applied globally when set by the profile owner of a
- * managed profile that was created during the device provisioning flow.
+ * Special user restrictions that profile owner of an organization-owned managed profile can
+ * set on the parent profile instance to apply them globally.
*/
private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
Sets.newArraySet(
UserManager.DISALLOW_CONFIG_DATE_TIME,
- UserManager.DISALLOW_CAMERA
+ UserManager.DISALLOW_CAMERA,
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_BLUETOOTH,
+ UserManager.DISALLOW_BLUETOOTH_SHARING,
+ UserManager.DISALLOW_CONFIG_BLUETOOTH,
+ UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+ UserManager.DISALLOW_CONFIG_LOCATION,
+ UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+ UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
+ UserManager.DISALLOW_CONFIG_TETHERING,
+ UserManager.DISALLOW_CONFIG_WIFI,
+ UserManager.DISALLOW_CONTENT_CAPTURE,
+ UserManager.DISALLOW_CONTENT_SUGGESTIONS,
+ UserManager.DISALLOW_DATA_ROAMING,
+ UserManager.DISALLOW_DEBUGGING_FEATURES,
+ UserManager.DISALLOW_SAFE_BOOT,
+ UserManager.DISALLOW_SHARE_LOCATION,
+ UserManager.DISALLOW_SMS,
+ UserManager.DISALLOW_USB_FILE_TRANSFER
);
/**
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 29183bb..df24c013 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -222,6 +222,7 @@
// If the dex file is the primary apk (or a split) and not isUsedByOtherApps
// do not record it. This case does not bring any new usable information
// and can be safely skipped.
+ dexPathIndex++;
continue;
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 6d6ec25..46893b2 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -592,12 +592,6 @@
getDefaultSystemHandlerActivityPackageForCategory(Intent.CATEGORY_APP_MAPS, userId),
userId, ALWAYS_LOCATION_PERMISSIONS);
- // Gallery
- grantPermissionsToSystemPackage(
- getDefaultSystemHandlerActivityPackageForCategory(
- Intent.CATEGORY_APP_GALLERY, userId),
- userId, STORAGE_PERMISSIONS);
-
// Email
grantPermissionsToSystemPackage(
getDefaultSystemHandlerActivityPackageForCategory(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d921f31..0411e29 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -55,6 +55,8 @@
import android.app.ActivityManager;
import android.app.ApplicationPackageManager;
import android.app.IActivityManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -107,6 +109,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.compat.IPlatformCompat;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.RoSystemProperties;
@@ -224,6 +227,8 @@
private final Handler mHandler;
private final Context mContext;
private final MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
/** Internal storage for permissions and related settings */
@GuardedBy("mLock")
@@ -1824,6 +1829,14 @@
return true;
}
+ /**
+ * This change makes it so that apps are told to show rationale for asking for background
+ * location access every time they request.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L;
+
@Override
public boolean shouldShowRequestPermissionRationale(String permName,
String packageName, int userId) {
@@ -1862,6 +1875,16 @@
return false;
}
+ try {
+ if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+ && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID,
+ packageName, userId)) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to check if compatibility change is enabled.", e);
+ }
+
return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
}
@@ -4005,21 +4028,128 @@
PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
}
- if (!requirePermissionWhenSameUser && userId == UserHandle.getUserId(callingUid)) return;
- if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
- if (requireFullPermission) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
- } else {
- try {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
- } catch (SecurityException se) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS, message);
- }
- }
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (hasCrossUserPermission(
+ callingUid, callingUserId, userId, requireFullPermission,
+ requirePermissionWhenSameUser)) {
+ return;
}
+ String errorMessage = buildInvalidCrossUserPermissionMessage(
+ message, requireFullPermission);
+ Slog.w(TAG, errorMessage);
+ throw new SecurityException(errorMessage);
+ }
+
+ /**
+ * Checks if the request is from the system or an app that has the appropriate cross-user
+ * permissions defined as follows:
+ * <ul>
+ * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li>
+ * <li>INTERACT_ACROSS_USERS if the given {@userId} is in a different profile group
+ * to the caller.</li>
+ * <li>Otherwise, INTERACT_ACROSS_PROFILES if the given {@userId} is in the same profile group
+ * as the caller.</li>
+ * </ul>
+ *
+ * @param checkShell whether to prevent shell from access if there's a debugging restriction
+ * @param message the message to log on security exception
+ */
+ private void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+ boolean requireFullPermission, boolean checkShell,
+ String message) {
+ if (userId < 0) {
+ throw new IllegalArgumentException("Invalid userId " + userId);
+ }
+ if (checkShell) {
+ PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
+ UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+ }
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission,
+ /*requirePermissionWhenSameUser= */ false)) {
+ return;
+ }
+ final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
+ if (isSameProfileGroup
+ && hasPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)) {
+ return;
+ }
+ String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
+ message, requireFullPermission, isSameProfileGroup);
+ Slog.w(TAG, errorMessage);
+ throw new SecurityException(errorMessage);
+ }
+
+ private boolean hasCrossUserPermission(
+ int callingUid, int callingUserId, int userId, boolean requireFullPermission,
+ boolean requirePermissionWhenSameUser) {
+ if (!requirePermissionWhenSameUser && userId == callingUserId) {
+ return true;
+ }
+ if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+ return true;
+ }
+ if (requireFullPermission) {
+ return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ }
+ return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS);
+ }
+
+ private boolean hasPermission(String permission) {
+ return mContext.checkCallingOrSelfPermission(permission)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private static String buildInvalidCrossUserPermissionMessage(
+ String message, boolean requireFullPermission) {
+ StringBuilder builder = new StringBuilder();
+ if (message != null) {
+ builder.append(message);
+ builder.append(": ");
+ }
+ builder.append("Requires ");
+ builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ if (requireFullPermission) {
+ builder.append(".");
+ return builder.toString();
+ }
+ builder.append(" or ");
+ builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+ builder.append(".");
+ return builder.toString();
+ }
+
+ private static String buildInvalidCrossUserOrProfilePermissionMessage(
+ String message, boolean requireFullPermission, boolean isSameProfileGroup) {
+ StringBuilder builder = new StringBuilder();
+ if (message != null) {
+ builder.append(message);
+ builder.append(": ");
+ }
+ builder.append("Requires ");
+ builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ if (requireFullPermission) {
+ builder.append(".");
+ return builder.toString();
+ }
+ builder.append(" or ");
+ builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+ if (isSameProfileGroup) {
+ builder.append(" or ");
+ builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES);
+ }
+ builder.append(".");
+ return builder.toString();
}
@GuardedBy({"mSettings.mLock", "mLock"})
@@ -4215,6 +4345,17 @@
PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId,
requireFullPermission, checkShell, requirePermissionWhenSameUser, message);
}
+
+ @Override
+ public void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+ boolean requireFullPermission, boolean checkShell, String message) {
+ PermissionManagerService.this.enforceCrossUserOrProfilePermission(callingUid,
+ userId,
+ requireFullPermission,
+ checkShell,
+ message);
+ }
+
@Override
public void enforceGrantRevokeRuntimePermissionPermissions(String message) {
PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message);
@@ -4453,8 +4594,8 @@
@Override
public void onNewUserCreated(int userId) {
+ mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
synchronized (mLock) {
- mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
// NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
PermissionManagerService.this.updateAllPermissions(
StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 0f22619..58a9f42 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -271,6 +271,15 @@
*/
public abstract void enforceCrossUserPermission(int callingUid, int userId,
boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
+ /**
+ * Similar to {@link #enforceCrossUserPermission(int, int, boolean, boolean, String)}
+ * but also allows INTERACT_ACROSS_PROFILES permission if calling user and {@code userId} are
+ * in the same profile group.
+ */
+ public abstract void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+ boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
/**
* @see #enforceCrossUserPermission(int, int, boolean, boolean, String)
* @param requirePermissionWhenSameUser When {@code true}, still require the cross user
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 5271493..6daf516 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -743,8 +743,8 @@
} else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
// Airplane mode can be changed after ECM exits if airplane toggle button
// is pressed during ECM mode
- if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
- mIsWaitingForEcmExit) {
+ if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false))
+ && mIsWaitingForEcmExit) {
mIsWaitingForEcmExit = false;
changeAirplaneModeSystemSetting(true);
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index a62bb74..4b3746b 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -45,7 +45,6 @@
import com.android.server.power.BatterySaverStateMachineProto;
import java.io.PrintWriter;
-import java.text.NumberFormat;
/**
* Decides when to enable / disable battery saver.
@@ -796,8 +795,7 @@
manager.notifyAsUser(TAG, DYNAMIC_MODE_NOTIFICATION_ID,
buildNotification(DYNAMIC_MODE_NOTIF_CHANNEL_ID,
- mContext.getResources().getString(
- R.string.dynamic_mode_notification_title),
+ R.string.dynamic_mode_notification_title,
R.string.dynamic_mode_notification_summary,
Intent.ACTION_POWER_USAGE_SUMMARY),
UserHandle.ALL);
@@ -813,13 +811,10 @@
ensureNotificationChannelExists(manager, BATTERY_SAVER_NOTIF_CHANNEL_ID,
R.string.battery_saver_notification_channel_name);
- final String percentage = NumberFormat.getPercentInstance()
- .format((double) mBatteryLevel / 100.0);
manager.notifyAsUser(TAG, STICKY_AUTO_DISABLED_NOTIFICATION_ID,
buildNotification(BATTERY_SAVER_NOTIF_CHANNEL_ID,
- mContext.getResources().getString(
- R.string.battery_saver_charged_notification_title, percentage),
- R.string.battery_saver_off_notification_summary,
+ R.string.battery_saver_off_notification_title,
+ R.string.battery_saver_charged_notification_summary,
Settings.ACTION_BATTERY_SAVER_SETTINGS),
UserHandle.ALL);
});
@@ -834,13 +829,14 @@
manager.createNotificationChannel(channel);
}
- private Notification buildNotification(@NonNull String channelId, @NonNull String title,
+ private Notification buildNotification(@NonNull String channelId, @StringRes int titleId,
@StringRes int summaryId, @NonNull String intentAction) {
Resources res = mContext.getResources();
Intent intent = new Intent(intentAction);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent batterySaverIntent = PendingIntent.getActivity(
mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ final String title = res.getString(titleId);
final String summary = res.getString(summaryId);
return new Notification.Builder(mContext, channelId)
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 88c1564..7d0072a 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -62,7 +62,7 @@
private static final String TAG = "RollbackManager";
- @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
+ @IntDef(prefix = { "ROLLBACK_STATE_" }, value = {
ROLLBACK_STATE_ENABLING,
ROLLBACK_STATE_AVAILABLE,
ROLLBACK_STATE_COMMITTED,
@@ -92,6 +92,19 @@
*/
static final int ROLLBACK_STATE_DELETED = 4;
+ @IntDef(flag = true, prefix = { "MATCH_" }, value = {
+ MATCH_APK_IN_APEX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RollbackInfoFlags {}
+
+ /**
+ * {@link RollbackInfo} flag: include {@code RollbackInfo} packages that are apk-in-apex.
+ * These packages do not have their own sessions. They are embedded in an apex which has a
+ * session id.
+ */
+ static final int MATCH_APK_IN_APEX = 1;
+
/**
* The session ID for the staged session if this rollback data represents a staged session,
* {@code -1} otherwise.
@@ -159,6 +172,13 @@
@Nullable public final String mInstallerPackageName;
/**
+ * This array holds all of the rollback tokens associated with package sessions included in
+ * this rollback.
+ */
+ @GuardedBy("mLock")
+ private final IntArray mTokens = new IntArray();
+
+ /**
* Constructs a new, empty Rollback instance.
*
* @param rollbackId the id of the rollback.
@@ -323,8 +343,8 @@
new VersionedPackage(packageName, newVersion),
new VersionedPackage(packageName, installedVersion),
new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
- isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */,
- rollbackDataPolicy);
+ isApex, false /* isApkInApex */, new IntArray(),
+ new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy);
synchronized (mLock) {
info.getPackages().add(packageRollbackInfo);
@@ -334,6 +354,30 @@
}
/**
+ * Enables this rollback for the provided apk-in-apex.
+ *
+ * @return boolean True if the rollback was enabled successfully for the specified package.
+ */
+ boolean enableForPackageInApex(String packageName, long installedVersion,
+ int rollbackDataPolicy) {
+ // TODO(b/142712057): Extract the new version number of apk-in-apex
+ // The new version for the apk-in-apex is set to 0 for now. If the package is then further
+ // updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced()
+ // will be called and this rollback will be deleted. Other ways of package update have not
+ // been handled yet.
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
+ new VersionedPackage(packageName, 0 /* newVersion */),
+ new VersionedPackage(packageName, installedVersion),
+ new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
+ false /* isApex */, true /* isApkInApex */, new IntArray(),
+ new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy);
+ synchronized (mLock) {
+ info.getPackages().add(packageRollbackInfo);
+ }
+ return true;
+ }
+
+ /**
* Snapshots user data for the provided package and user ids. Does nothing if this rollback is
* not in the ENABLING state.
*/
@@ -428,6 +472,11 @@
parentSessionId);
for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
+ if (pkgRollbackInfo.isApkInApex()) {
+ // No need to issue a downgrade install request for apk-in-apex. It will
+ // be rolled back when its parent apex is downgraded.
+ continue;
+ }
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
String installerPackageName = mInstallerPackageName;
@@ -453,7 +502,8 @@
this, pkgRollbackInfo.getPackageName());
if (packageCodePaths == null) {
sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
- "Backup copy of package inaccessible");
+ "Backup copy of package: "
+ + pkgRollbackInfo.getPackageName() + " is inaccessible");
return;
}
@@ -696,9 +746,50 @@
}
}
- int getPackageCount() {
+ /**
+ * Returns the number of {@link PackageRollbackInfo} we are storing in this {@link Rollback}
+ * instance. By default, this method does not include apk-in-apex package in the count.
+ *
+ * @param flags Apk-in-apex packages can be included in the count by passing
+ * {@link Rollback#MATCH_APK_IN_APEX}
+ *
+ * @return Counts number of {@link PackageRollbackInfo} stored in the {@link Rollback}
+ * according to {@code flags} passed
+ */
+ int getPackageCount(@RollbackInfoFlags int flags) {
synchronized (mLock) {
- return info.getPackages().size();
+ List<PackageRollbackInfo> packages = info.getPackages();
+ if ((flags & MATCH_APK_IN_APEX) != 0) {
+ return packages.size();
+ }
+
+ int packagesWithoutApkInApex = 0;
+ for (PackageRollbackInfo rollbackInfo : packages) {
+ if (!rollbackInfo.isApkInApex()) {
+ packagesWithoutApkInApex++;
+ }
+ }
+ return packagesWithoutApkInApex;
+ }
+ }
+
+ /**
+ * Adds a rollback token to be associated with this rollback. This may be used to
+ * identify which rollback should be removed in case {@link PackageManager} sends an
+ * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
+ */
+ void addToken(int token) {
+ synchronized (mLock) {
+ mTokens.add(token);
+ }
+ }
+
+ /**
+ * Returns true if this rollback is associated with the provided {@code token}.
+ */
+ boolean hasToken(int token) {
+ synchronized (mLock) {
+ return mTokens.indexOf(token) != -1;
}
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e29d1a7..eefcde6 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -235,12 +235,17 @@
Slog.v(TAG, "broadcast=ACTION_CANCEL_ENABLE_ROLLBACK token=" + token);
}
synchronized (mLock) {
- for (NewRollback rollback : mNewRollbacks) {
- if (rollback.hasToken(token)) {
- rollback.setCancelled();
- return;
+ NewRollback found = null;
+ for (NewRollback newRollback : mNewRollbacks) {
+ if (newRollback.rollback.hasToken(token)) {
+ found = newRollback;
+ break;
}
}
+ if (found != null) {
+ mNewRollbacks.remove(found);
+ found.rollback.delete(mAppDataRollbackHelper);
+ }
}
}
}
@@ -433,10 +438,14 @@
rollback.delete(mAppDataRollbackHelper);
}
}
- for (NewRollback newRollback : mNewRollbacks) {
+ Iterator<NewRollback> iter2 = mNewRollbacks.iterator();
+ while (iter2.hasNext()) {
+ NewRollback newRollback = iter2.next();
if (newRollback.rollback.includesPackage(packageName)) {
- newRollback.setCancelled();
+ iter2.remove();
+ newRollback.rollback.delete(mAppDataRollbackHelper);
}
+
}
}
}
@@ -798,7 +807,7 @@
mNewRollbacks.add(newRollback);
}
}
- newRollback.addToken(token);
+ newRollback.rollback.addToken(token);
return enableRollbackForPackageSession(newRollback.rollback, packageSession);
}
@@ -891,9 +900,36 @@
}
ApplicationInfo appInfo = pkgInfo.applicationInfo;
- return rollback.enableForPackage(packageName, newPackage.versionCode,
+ boolean success = rollback.enableForPackage(packageName, newPackage.versionCode,
pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
appInfo.splitSourceDirs, session.rollbackDataPolicy);
+ if (!success) {
+ return success;
+ }
+
+ if (isApex) {
+ // Check if this apex contains apks inside it. If true, then they should be added as
+ // a RollbackPackageInfo into this rollback
+ final PackageManagerInternal pmi = LocalServices.getService(
+ PackageManagerInternal.class);
+ List<String> apksInApex = pmi.getApksInApex(packageName);
+ for (String apkInApex : apksInApex) {
+ // Get information about the currently installed package.
+ final PackageInfo apkPkgInfo;
+ try {
+ apkPkgInfo = getPackageInfo(apkInApex);
+ } catch (PackageManager.NameNotFoundException e) {
+ // TODO: Support rolling back fresh package installs rather than
+ // fail here. Test this case.
+ Slog.e(TAG, apkInApex + " is not installed");
+ return false;
+ }
+ success = rollback.enableForPackageInApex(
+ apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy);
+ if (!success) return success;
+ }
+ }
+ return true;
}
@Override
@@ -907,9 +943,13 @@
getHandler().post(() -> {
snapshotUserDataInternal(packageName, userIds);
restoreUserDataInternal(packageName, userIds, appId, seInfo);
- final PackageManagerInternal pmi = LocalServices.getService(
- PackageManagerInternal.class);
- pmi.finishPackageInstall(token, false);
+ // When this method is called as part of the install flow, a positive token number is
+ // passed to it. Need to notify the PackageManager when we are done.
+ if (token > 0) {
+ final PackageManagerInternal pmi = LocalServices.getService(
+ PackageManagerInternal.class);
+ pmi.finishPackageInstall(token, false);
+ }
});
}
@@ -1189,13 +1229,11 @@
Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId());
}
- if (newRollback.isCancelled()) {
- Slog.e(TAG, "Rollback has been cancelled by PackageManager");
- rollback.delete(mAppDataRollbackHelper);
- return null;
- }
-
- if (rollback.getPackageCount() != newRollback.getPackageSessionIdCount()) {
+ // We are checking if number of packages (excluding apk-in-apex) we enabled for rollback is
+ // equal to the number of sessions we are installing, to ensure we didn't skip enabling
+ // of any sessions. If we successfully enable an apex, then we can assume we enabled
+ // rollback for the embedded apk-in-apex, if any.
+ if (rollback.getPackageCount(0 /*flags*/) != newRollback.getPackageSessionIdCount()) {
Slog.e(TAG, "Failed to enable rollback for all packages in session.");
rollback.delete(mAppDataRollbackHelper);
return null;
@@ -1300,22 +1338,12 @@
public final Rollback rollback;
/**
- * This array holds all of the rollback tokens associated with package sessions included in
- * this rollback.
- */
- @GuardedBy("mNewRollbackLock")
- private final IntArray mTokens = new IntArray();
-
- /**
* Session ids for all packages in the install. For multi-package sessions, this is the list
* of child session ids. For normal sessions, this list is a single element with the normal
* session id.
*/
private final int[] mPackageSessionIds;
- @GuardedBy("mNewRollbackLock")
- private boolean mIsCancelled = false;
-
/**
* The number of sessions in the install which are notified with success by
* {@link PackageInstaller.SessionCallback#onFinished(int, boolean)}.
@@ -1332,52 +1360,6 @@
}
/**
- * Adds a rollback token to be associated with this NewRollback. This may be used to
- * identify which rollback should be cancelled in case {@link PackageManager} sends an
- * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
- */
- void addToken(int token) {
- synchronized (mNewRollbackLock) {
- mTokens.add(token);
- }
- }
-
- /**
- * Returns true if this NewRollback is associated with the provided {@code token}.
- */
- boolean hasToken(int token) {
- synchronized (mNewRollbackLock) {
- return mTokens.indexOf(token) != -1;
- }
- }
-
- /**
- * Returns true if this NewRollback has been cancelled.
- *
- * <p>Rollback could be invalidated and cancelled if RollbackManager receives
- * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} from {@link PackageManager}.
- *
- * <p>The main underlying assumption here is that if enabling the rollback times out, then
- * {@link PackageManager} will NOT send
- * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)} before it broadcasts
- * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK}.
- */
- boolean isCancelled() {
- synchronized (mNewRollbackLock) {
- return mIsCancelled;
- }
- }
-
- /**
- * Sets this NewRollback to be marked as cancelled.
- */
- void setCancelled() {
- synchronized (mNewRollbackLock) {
- mIsCancelled = true;
- }
- }
-
- /**
* Returns true if this NewRollback contains the provided {@code packageSessionId}.
*/
boolean containsSessionId(int packageSessionId) {
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index a0ef8cf..b9ef7b3 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -280,7 +280,6 @@
.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
"");
- mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
} else if (sessionInfo.isStagedSessionFailed()
&& markStagedSessionHandled(rollbackId)) {
logEvent(moduleMetadataPackage,
@@ -291,6 +290,11 @@
}
}
}
+
+ // Wait for all pending staged sessions to get handled before rebooting.
+ if (isPendingStagedSessionsEmpty()) {
+ mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
+ }
}
/**
@@ -303,6 +307,16 @@
}
}
+ /**
+ * Returns {@code true} if all pending staged rollback sessions were marked as handled,
+ * {@code false} if there is any left.
+ */
+ private boolean isPendingStagedSessionsEmpty() {
+ synchronized (mPendingStagedRollbackIds) {
+ return mPendingStagedRollbackIds.isEmpty();
+ }
+ }
+
private void saveLastStagedRollbackId(int stagedRollbackId) {
try {
FileOutputStream fos = new FileOutputStream(mLastStagedRollbackIdFile);
@@ -414,6 +428,9 @@
reasonToLog, failedPackageToLog);
}
} else {
+ if (rollback.isStaged()) {
+ markStagedSessionHandled(rollback.getRollbackId());
+ }
logEvent(logPackage,
StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
reasonToLog, failedPackageToLog);
@@ -431,6 +448,16 @@
RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
+ // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
+ // pending staged rollbacks are handled.
+ synchronized (mPendingStagedRollbackIds) {
+ for (RollbackInfo rollback : rollbacks) {
+ if (rollback.isStaged()) {
+ mPendingStagedRollbackIds.add(rollback.getRollbackId());
+ }
+ }
+ }
+
for (RollbackInfo rollback : rollbacks) {
VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index df75a29..bbcd0de 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -341,6 +341,7 @@
json.put("pendingRestores", convertToJsonArray(pendingRestores));
json.put("isApex", info.isApex());
+ json.put("isApkInApex", info.isApkInApex());
// Field is named 'installedUsers' for legacy reasons.
json.put("installedUsers", convertToJsonArray(snapshottedUsers));
@@ -364,6 +365,7 @@
json.getJSONArray("pendingRestores"));
final boolean isApex = json.getBoolean("isApex");
+ final boolean isApkInApex = json.getBoolean("isApkInApex");
// Field is named 'installedUsers' for legacy reasons.
final IntArray snapshottedUsers = convertToIntArray(json.getJSONArray("installedUsers"));
@@ -375,8 +377,8 @@
PackageManager.RollbackDataPolicy.RESTORE);
return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo,
- pendingBackups, pendingRestores, isApex, snapshottedUsers, ceSnapshotInodes,
- rollbackDataPolicy);
+ pendingBackups, pendingRestores, isApex, isApkInApex, snapshottedUsers,
+ ceSnapshotInodes, rollbackDataPolicy);
}
private static JSONArray versionedPackagesToJson(List<VersionedPackage> packages)
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 0b89646..a641f06 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -21,8 +21,10 @@
import android.hardware.audio.common.V2_0.Uuid;
import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.hardware.soundtrigger.V2_3.Properties;
import android.media.audio.common.AudioConfig;
import android.media.audio.common.AudioOffloadInfo;
+import android.media.soundtrigger_middleware.AudioCapabilities;
import android.media.soundtrigger_middleware.ConfidenceLevel;
import android.media.soundtrigger_middleware.ModelParameter;
import android.media.soundtrigger_middleware.ModelParameterRange;
@@ -69,6 +71,15 @@
return aidlProperties;
}
+ static @NonNull SoundTriggerModuleProperties hidl2aidlProperties(
+ @NonNull Properties hidlProperties) {
+ SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base);
+ aidlProperties.supportedModelArch = hidlProperties.supportedModelArch;
+ aidlProperties.audioCapabilities =
+ hidl2aidlAudioCapabilities(hidlProperties.audioCapabilities);
+ return aidlProperties;
+ }
+
static @NonNull
String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
if (hidlUuid.node == null || hidlUuid.node.length != 6) {
@@ -201,16 +212,17 @@
return hidlModel;
}
- static @NonNull
- ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig(
+ static @NonNull android.hardware.soundtrigger.V2_3.RecognitionConfig aidl2hidlRecognitionConfig(
@NonNull RecognitionConfig aidlConfig) {
- ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig();
- hidlConfig.header.captureRequested = aidlConfig.captureRequested;
+ android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
+ new android.hardware.soundtrigger.V2_3.RecognitionConfig();
+ hidlConfig.base.header.captureRequested = aidlConfig.captureRequested;
for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
- hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
+ hidlConfig.base.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
}
- hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
+ hidlConfig.base.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
"SoundTrigger RecognitionConfig");
+ hidlConfig.audioCapabilities = aidlConfig.audioCapabilities;
return hidlConfig;
}
@@ -387,4 +399,17 @@
return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
}
}
+
+ static int hidl2aidlAudioCapabilities(int hidlCapabilities) {
+ int aidlCapabilities = 0;
+ if ((hidlCapabilities
+ & android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION) != 0) {
+ aidlCapabilities |= AudioCapabilities.ECHO_CANCELLATION;
+ }
+ if ((hidlCapabilities
+ & android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+ aidlCapabilities |= AudioCapabilities.NOISE_SUPPRESSION;
+ }
+ return aidlCapabilities;
+ }
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
new file mode 100644
index 0000000..b19e2ed
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+
+/**
+ * A factory for creating instances of {@link ISoundTriggerHw}.
+ *
+ * @hide
+ */
+public interface HalFactory {
+ /**
+ * @return An instance of {@link ISoundTriggerHw}.
+ */
+ ISoundTriggerHw create();
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
index f0a0d83..a42d292 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -66,12 +66,25 @@
return model_2_0;
}
- static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_1_to_2_0(
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config) {
+ static android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_1(
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
+ return config.base;
+ }
+
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_0(
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
- config.header;
+ config.base.header;
// Note: this mutates the input!
- config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
+ config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.base.data);
return config_2_0;
}
+
+ static android.hardware.soundtrigger.V2_3.Properties convertProperties_2_0_to_2_3(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties) {
+ android.hardware.soundtrigger.V2_3.Properties properties_2_3 =
+ new android.hardware.soundtrigger.V2_3.Properties();
+ properties_2_3.base = properties;
+ return properties_2_3;
+ }
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
index 81252c9..8b434bd 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -16,7 +16,6 @@
package com.android.server.soundtrigger_middleware;
-import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
import android.hardware.soundtrigger.V2_3.ModelParameterRange;
import android.hidl.base.V1_0.IBase;
import android.os.IHwBinder;
@@ -54,9 +53,10 @@
*/
public interface ISoundTriggerHw2 {
/**
- * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getPropertiesEx(
+ * android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getPropertiesExCallback)
*/
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties();
+ android.hardware.soundtrigger.V2_3.Properties getProperties();
/**
* @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
@@ -92,12 +92,12 @@
void stopAllRecognitions();
/**
- * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int,
- * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig,
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#startRecognition_2_3(int,
+ * android.hardware.soundtrigger.V2_3.RecognitionConfig,
* android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
*/
void startRecognition(int modelHandle,
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
SoundTriggerHw2Compat.Callback callback, int cookie);
/**
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index 4a852c4..2f087f4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -112,18 +112,23 @@
}
@Override
- public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() {
+ public android.hardware.soundtrigger.V2_3.Properties getProperties() {
try {
AtomicInteger retval = new AtomicInteger(-1);
- AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties>
+ AtomicReference<android.hardware.soundtrigger.V2_3.Properties>
properties =
new AtomicReference<>();
- as2_0().getProperties(
- (r, p) -> {
- retval.set(r);
- properties.set(p);
- });
- handleHalStatus(retval.get(), "getProperties");
+ try {
+ as2_3().getProperties_2_3(
+ (r, p) -> {
+ retval.set(r);
+ properties.set(p);
+ });
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ return getProperties_2_0();
+ }
+ handleHalStatus(retval.get(), "getProperties_2_3");
return properties.get();
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
@@ -212,16 +217,15 @@
@Override
public void startRecognition(int modelHandle,
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
Callback callback, int cookie) {
try {
try {
- int retval = as2_1().startRecognition_2_1(modelHandle, config,
- new SoundTriggerCallback(callback), cookie);
- handleHalStatus(retval, "startRecognition_2_1");
+ int retval = as2_3().startRecognition_2_3(modelHandle, config);
+ handleHalStatus(retval, "startRecognition_2_3");
} catch (NotSupported e) {
// Fall-back to the 2.0 version:
- startRecognition_2_0(modelHandle, config, callback, cookie);
+ startRecognition_2_1(modelHandle, config, callback, cookie);
}
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
@@ -312,6 +316,21 @@
return as2_0().interfaceDescriptor();
}
+ private android.hardware.soundtrigger.V2_3.Properties getProperties_2_0()
+ throws RemoteException {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicReference<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties>
+ properties =
+ new AtomicReference<>();
+ as2_0().getProperties(
+ (r, p) -> {
+ retval.set(r);
+ properties.set(p);
+ });
+ handleHalStatus(retval.get(), "getProperties");
+ return Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get());
+ }
+
private int loadSoundModel_2_0(
android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
Callback callback, int cookie)
@@ -349,13 +368,31 @@
return handle.get();
}
+ private void startRecognition_2_1(int modelHandle,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
+ Callback callback, int cookie) {
+ try {
+ try {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config_2_1 =
+ Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_1(config);
+ int retval = as2_1().startRecognition_2_1(modelHandle, config_2_1,
+ new SoundTriggerCallback(callback), cookie);
+ handleHalStatus(retval, "startRecognition_2_1");
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ startRecognition_2_0(modelHandle, config, callback, cookie);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
private void startRecognition_2_0(int modelHandle,
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
Callback callback, int cookie)
throws RemoteException {
-
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
- Hw2CompatUtil.convertRecognitionConfig_2_1_to_2_0(config);
+ Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_0(config);
int retval = as2_0().startRecognition(modelHandle, config_2_0,
new SoundTriggerCallback(callback), cookie);
handleHalStatus(retval, "startRecognition");
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index 9d51b65..9f4b09a 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -78,18 +78,17 @@
}
/**
- * Most generic constructor - gets an array of HAL driver instances.
+ * Constructor - gets an array of HAL driver factories.
*/
- public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw[] halServices,
+ public SoundTriggerMiddlewareImpl(@NonNull HalFactory[] halFactories,
@NonNull AudioSessionProvider audioSessionProvider) {
- List<SoundTriggerModule> modules = new ArrayList<>(halServices.length);
+ List<SoundTriggerModule> modules = new ArrayList<>(halFactories.length);
- for (int i = 0; i < halServices.length; ++i) {
- ISoundTriggerHw service = halServices[i];
+ for (int i = 0; i < halFactories.length; ++i) {
try {
- modules.add(new SoundTriggerModule(service, audioSessionProvider));
+ modules.add(new SoundTriggerModule(halFactories[i], audioSessionProvider));
} catch (Exception e) {
- Log.e(TAG, "Failed to a SoundTriggerModule instance", e);
+ Log.e(TAG, "Failed to add a SoundTriggerModule instance", e);
}
}
@@ -97,11 +96,11 @@
}
/**
- * Convenience constructor - gets a single HAL driver instance.
+ * Convenience constructor - gets a single HAL factory.
*/
- public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw halService,
+ public SoundTriggerMiddlewareImpl(@NonNull HalFactory factory,
@NonNull AudioSessionProvider audioSessionProvider) {
- this(new ISoundTriggerHw[]{halService}, audioSessionProvider);
+ this(new HalFactory[]{factory}, audioSessionProvider);
}
@Override
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 987c05f..12f9fd9 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.PermissionChecker;
import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
@@ -32,6 +33,7 @@
import android.media.soundtrigger_middleware.RecognitionStatus;
import android.media.soundtrigger_middleware.SoundModel;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.Status;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
@@ -111,9 +113,9 @@
public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
static private final String TAG = "SoundTriggerMiddlewareService";
- final ISoundTriggerMiddlewareService mDelegate;
- final Context mContext;
- Set<Integer> mModuleHandles;
+ private final ISoundTriggerMiddlewareService mDelegate;
+ private final Context mContext;
+ private Set<Integer> mModuleHandles;
/**
* Constructor for internal use only. Could be exposed for testing purposes in the future.
@@ -223,23 +225,48 @@
}
/**
- * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this
- * service.
+ * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+ * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+ * caller temporarily doesn't have the right permissions to use this service.
*/
private void checkPermissions() {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO,
- "Caller must have the android.permission.RECORD_AUDIO permission.");
- mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD,
- "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission.");
+ enforcePermission(Manifest.permission.RECORD_AUDIO);
+ enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
}
/**
- * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt
- * active sound trigger sessions.
+ * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+ * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+ * caller temporarily doesn't have the right permissions to preempt active sound trigger
+ * sessions.
*/
private void checkPreemptPermissions() {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER,
- "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission.");
+ enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+ * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+ * caller temporarily doesn't have the given permission.
+ *
+ * @param permission The permission to check.
+ */
+ private void enforcePermission(String permission) {
+ final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext,
+ permission);
+ switch (status) {
+ case PermissionChecker.PERMISSION_GRANTED:
+ return;
+ case PermissionChecker.PERMISSION_DENIED:
+ throw new SecurityException(
+ String.format("Caller must have the %s permission.", permission));
+ case PermissionChecker.PERMISSION_DENIED_APP_OP:
+ throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
+ String.format("Caller must have the %s permission.", permission));
+ default:
+ throw new InternalServerError(
+ new RuntimeException("Unexpected perimission check result."));
+ }
}
/** State of a sound model. */
@@ -253,7 +280,7 @@
}
/** Activity state. */
- public Activity activityState = Activity.LOADED;
+ Activity activityState = Activity.LOADED;
/**
* A map of known parameter support. A missing key means we don't know yet whether the
@@ -267,7 +294,7 @@
*
* @param modelParam The parameter key.
*/
- public void checkSupported(int modelParam) {
+ void checkSupported(int modelParam) {
if (!parameterSupport.containsKey(modelParam)) {
throw new IllegalStateException("Parameter has not been checked for support.");
}
@@ -284,7 +311,7 @@
* @param modelParam The parameter key.
* @param value The value.
*/
- public void checkSupported(int modelParam, int value) {
+ void checkSupported(int modelParam, int value) {
if (!parameterSupport.containsKey(modelParam)) {
throw new IllegalStateException("Parameter has not been checked for support.");
}
@@ -302,7 +329,7 @@
* @param modelParam The parameter key.
* @param range The parameter value range, or null if not supported.
*/
- public void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
+ void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
parameterSupport.put(modelParam, range);
}
}
@@ -311,27 +338,26 @@
* Entry-point to this module: exposes the module as a {@link SystemService}.
*/
public static final class Lifecycle extends SystemService {
- private SoundTriggerMiddlewareService mService;
-
public Lifecycle(Context context) {
super(context);
}
@Override
public void onStart() {
- ISoundTriggerHw[] services;
- try {
- services = new ISoundTriggerHw[]{ISoundTriggerHw.getService(true)};
- Log.d(TAG, "Connected to default ISoundTriggerHw");
- } catch (Exception e) {
- Log.e(TAG, "Failed to connect to default ISoundTriggerHw", e);
- services = new ISoundTriggerHw[0];
- }
+ HalFactory[] factories = new HalFactory[]{() -> {
+ try {
+ Log.d(TAG, "Connecting to default ISoundTriggerHw");
+ return ISoundTriggerHw.getService(true);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }};
- mService = new SoundTriggerMiddlewareService(
- new SoundTriggerMiddlewareImpl(services, new AudioSessionProviderImpl()),
- getContext());
- publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, mService);
+ publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
+ new SoundTriggerMiddlewareService(
+ new SoundTriggerMiddlewareImpl(factories,
+ new AudioSessionProviderImpl()),
+ getContext()));
}
}
@@ -343,7 +369,7 @@
DeathRecipient {
private final ISoundTriggerCallback mCallback;
private ISoundTriggerModule mDelegate;
- private Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+ private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
ModuleService(@NonNull ISoundTriggerCallback callback) {
mCallback = callback;
@@ -653,7 +679,7 @@
} catch (RemoteException e) {
// Dead client will be handled by binderDied() - no need to handle here.
// In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback execption.", e);
+ Log.e(TAG, "Client callback exception.", e);
}
}
}
@@ -669,20 +695,33 @@
} catch (RemoteException e) {
// Dead client will be handled by binderDied() - no need to handle here.
// In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback execption.", e);
+ Log.e(TAG, "Client callback exception.", e);
}
}
}
@Override
- public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+ public void onRecognitionAvailabilityChange(boolean available) {
synchronized (this) {
try {
mCallback.onRecognitionAvailabilityChange(available);
} catch (RemoteException e) {
// Dead client will be handled by binderDied() - no need to handle here.
// In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback execption.", e);
+ Log.e(TAG, "Client callback exception.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onModuleDied() {
+ synchronized (this) {
+ try {
+ mCallback.onModuleDied();
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
}
}
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 81789e1..adf16fa 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -30,7 +30,9 @@
import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
import android.media.soundtrigger_middleware.Status;
import android.os.IBinder;
+import android.os.IHwBinder;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.util.Log;
import java.util.HashMap;
@@ -57,6 +59,7 @@
* gracefully handle driver malfunction and such behavior will result in undefined behavior. If this
* service is to used with an untrusted driver, the driver must be wrapped with validation / error
* recovery code.
+ * <li>Recovery from driver death is supported.</li>
* <li>RemoteExceptions thrown by the driver are treated as RuntimeExceptions - they are not
* considered recoverable faults and should not occur in a properly functioning system.
* <li>There is no binder instance associated with this implementation. Do not call asBinder().
@@ -79,27 +82,29 @@
*
* @hide
*/
-class SoundTriggerModule {
+class SoundTriggerModule implements IHwBinder.DeathRecipient {
static private final String TAG = "SoundTriggerModule";
- @NonNull private final ISoundTriggerHw2 mHalService;
+ @NonNull private HalFactory mHalFactory;
+ @NonNull private ISoundTriggerHw2 mHalService;
@NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
private final Set<Session> mActiveSessions = new HashSet<>();
private int mNumLoadedModels = 0;
- private SoundTriggerModuleProperties mProperties = null;
+ private SoundTriggerModuleProperties mProperties;
private boolean mRecognitionAvailable;
/**
* Ctor.
*
- * @param halService The underlying HAL driver.
+ * @param halFactory A factory for the underlying HAL driver.
*/
- SoundTriggerModule(@NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw halService,
+ SoundTriggerModule(@NonNull HalFactory halFactory,
@NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) {
- assert halService != null;
- mHalService = new SoundTriggerHw2Compat(halService);
+ assert halFactory != null;
+ mHalFactory = halFactory;
mAudioSessionProvider = audioSessionProvider;
- mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
+ attachToHal();
+ mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
// We conservatively assume that external capture is active until explicitly told otherwise.
mRecognitionAvailable = mProperties.concurrentCapture;
}
@@ -117,7 +122,7 @@
* @return The interface through which this module can be controlled.
*/
synchronized @NonNull
- Session attach(@NonNull ISoundTriggerCallback callback) {
+ ISoundTriggerModule attach(@NonNull ISoundTriggerCallback callback) {
Log.d(TAG, "attach()");
Session session = new Session(callback);
mActiveSessions.add(session);
@@ -145,6 +150,7 @@
*/
synchronized void setExternalCaptureState(boolean active) {
Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active));
+
if (mProperties.concurrentCapture) {
// If we support concurrent capture, we don't care about any of this.
return;
@@ -162,6 +168,23 @@
}
}
+ @Override
+ public synchronized void serviceDied(long cookie) {
+ Log.w(TAG, String.format("Underlying HAL driver died."));
+ for (Session session : mActiveSessions) {
+ session.moduleDied();
+ }
+ attachToHal();
+ }
+
+ /**
+ * Attached to the HAL service via factory.
+ */
+ private void attachToHal() {
+ mHalService = new SoundTriggerHw2Compat(mHalFactory.create());
+ mHalService.linkToDeath(this, 0);
+ }
+
/**
* Remove session from the list of active sessions.
*
@@ -204,7 +227,11 @@
public void detach() {
Log.d(TAG, "detach()");
synchronized (SoundTriggerModule.this) {
+ if (mCallback == null) {
+ return;
+ }
removeSession(this);
+ mCallback = null;
}
}
@@ -212,6 +239,7 @@
public int loadModel(@NonNull SoundModel model) {
Log.d(TAG, String.format("loadModel(model=%s)", model));
synchronized (SoundTriggerModule.this) {
+ checkValid();
if (mNumLoadedModels == mProperties.maxSoundModels) {
throw new RecoverableException(Status.RESOURCE_CONTENTION,
"Maximum number of models loaded.");
@@ -227,6 +255,7 @@
public int loadPhraseModel(@NonNull PhraseSoundModel model) {
Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
synchronized (SoundTriggerModule.this) {
+ checkValid();
if (mNumLoadedModels == mProperties.maxSoundModels) {
throw new RecoverableException(Status.RESOURCE_CONTENTION,
"Maximum number of models loaded.");
@@ -243,6 +272,7 @@
public void unloadModel(int modelHandle) {
Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).unload();
--mNumLoadedModels;
}
@@ -253,6 +283,7 @@
Log.d(TAG,
String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).startRecognition(config);
}
}
@@ -269,26 +300,28 @@
public void forceRecognitionEvent(int modelHandle) {
Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).forceRecognitionEvent();
}
}
@Override
- public void setModelParameter(int modelHandle, int modelParam, int value)
- throws RemoteException {
+ public void setModelParameter(int modelHandle, int modelParam, int value) {
Log.d(TAG,
String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
modelParam, value));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).setParameter(modelParam, value);
}
}
@Override
- public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+ public int getModelParameter(int modelHandle, int modelParam) {
Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
modelParam));
synchronized (SoundTriggerModule.this) {
+ checkValid();
return mLoadedModels.get(modelHandle).getParameter(modelParam);
}
}
@@ -299,6 +332,7 @@
Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
modelParam));
synchronized (SoundTriggerModule.this) {
+ checkValid();
return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
}
}
@@ -322,6 +356,25 @@
}
}
+ /**
+ * The underlying module HAL is dead.
+ */
+ private void moduleDied() {
+ try {
+ mCallback.onModuleDied();
+ removeSession(this);
+ mCallback = null;
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ private void checkValid() {
+ if (mCallback == null) {
+ throw new ServiceSpecificException(Status.DEAD_OBJECT);
+ }
+ }
+
@Override
public @NonNull
IBinder asBinder() {
@@ -350,10 +403,6 @@
SoundTriggerModule.this.notifyAll();
}
- private void waitStateChange() throws InterruptedException {
- SoundTriggerModule.this.wait();
- }
-
private int load(@NonNull SoundModel model) {
mModelType = model.type;
ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
@@ -401,10 +450,10 @@
notifyAbort();
return;
}
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig =
+ android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
ConversionUtil.aidl2hidlRecognitionConfig(config);
- hidlConfig.header.captureDevice = mSession.mDeviceHandle;
- hidlConfig.header.captureHandle = mSession.mIoHandle;
+ hidlConfig.base.header.captureDevice = mSession.mDeviceHandle;
+ hidlConfig.base.header.captureHandle = mSession.mIoHandle;
mHalService.startRecognition(mHandle, hidlConfig, this, 0);
setState(ModelState.ACTIVE);
}
diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS
index 8d7f882..fc7fd22 100644
--- a/services/core/java/com/android/server/stats/OWNERS
+++ b/services/core/java/com/android/server/stats/OWNERS
@@ -1,9 +1,8 @@
-bookatz@google.com
-cjyu@google.com
-dwchen@google.com
+jeffreyhuang@google.com
joeo@google.com
+muhammadq@google.com
+ruchirr@google.com
singhtejinder@google.com
-stlafon@google.com
+tsaichristine@google.com
yaochen@google.com
-yanglu@google.com
-yro@google.com
\ No newline at end of file
+yro@google.com
diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java
index e367f28..1f7ed8a 100644
--- a/services/core/java/com/android/server/stats/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/StatsPullAtomService.java
@@ -16,11 +16,166 @@
package com.android.server.stats;
-import android.content.Context;
-import android.util.Slog;
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.getUidForPid;
+import static android.os.storage.VolumeInfo.TYPE_PRIVATE;
+import static android.os.storage.VolumeInfo.TYPE_PUBLIC;
+import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
+import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.ProcfsMemoryUtil.forEachPid;
+import static com.android.server.stats.ProcfsMemoryUtil.readCmdlineFromProcfs;
+import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequest;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalUidOps;
+import android.app.INotificationManager;
+import android.app.ProcessMemoryState;
+import android.app.StatsManager;
+import android.app.StatsManager.PullAtomMetadata;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.UidTraffic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.NetworkStats;
+import android.net.wifi.WifiManager;
+import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CoolingDevice;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.IPullAtomCallback;
+import android.os.IStatsCompanionService;
+import android.os.IStatsd;
+import android.os.IStoraged;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.os.StatsLogEventWrapper;
+import android.os.SynchronousResultReceiver;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Temperature;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.connectivity.WifiActivityEnergyInfo;
+import android.os.storage.DiskInfo;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.provider.Settings;
+import android.stats.storage.StorageEnums;
+import android.telephony.ModemActivityInfo;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.StatsEvent;
+import android.util.StatsLog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.procstats.IProcessStats;
+import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.internal.os.BinderCallsStats.ExportedCallStat;
+import com.android.internal.os.KernelCpuSpeedReader;
+import com.android.internal.os.KernelCpuThreadReader;
+import com.android.internal.os.KernelCpuThreadReaderDiff;
+import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
+import com.android.internal.os.LooperStats;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.StoragedUidIoStatsReader;
+import com.android.internal.util.DumpUtils;
+import com.android.server.BinderCallsStatsService;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
+import com.android.server.am.MemoryStatUtil.MemoryStat;
+import com.android.server.notification.NotificationManagerService;
+import com.android.server.role.RoleManagerInternal;
+import com.android.server.stats.IonMemoryUtil.IonAllocations;
+import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot;
+import com.android.server.storage.DiskStatsFileLogger;
+import com.android.server.storage.DiskStatsLoggingService;
+
+import com.google.android.collect.Sets;
+
+import libcore.io.IoUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* SystemService containing PullAtomCallbacks that are registered with statsd.
@@ -31,13 +186,42 @@
private static final String TAG = "StatsPullAtomService";
private static final boolean DEBUG = true;
+ private static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity";
+ /**
+ * How long to wait on an individual subsystem to return its stats.
+ */
+ private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
+
+ private final Object mNetworkStatsLock = new Object();
+ @GuardedBy("mNetworkStatsLock")
+ private INetworkStatsService mNetworkStatsService;
+ private final Object mThermalLock = new Object();
+ @GuardedBy("mThermalLock")
+ private IThermalService mThermalService;
+
+ private final Context mContext;
+ private StatsManager mStatsManager;
+
public StatsPullAtomService(Context context) {
super(context);
+ mContext = context;
}
@Override
public void onStart() {
- // No op.
+ mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER);
+
+ // Used to initialize the CPU Frequency atom.
+ PowerProfile powerProfile = new PowerProfile(mContext);
+ final int numClusters = powerProfile.getNumCpuClusters();
+ mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
+ int firstCpuOfCluster = 0;
+ for (int i = 0; i < numClusters; i++) {
+ final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(i);
+ mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
+ numSpeedSteps);
+ firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
+ }
}
@Override
@@ -54,5 +238,1017 @@
if (DEBUG) {
Slog.d(TAG, "Registering all pullers with statsd");
}
+ registerWifiBytesTransfer();
+ registerWifiBytesTransferBackground();
+ registerMobileBytesTransfer();
+ registerMobileBytesTransferBackground();
+ registerBluetoothBytesTransfer();
+ registerKernelWakelock();
+ registerCpuTimePerFreq();
+ registerCpuTimePerUid();
+ registerCpuTimePerUidFreq();
+ registerCpuActiveTime();
+ registerCpuClusterTime();
+ registerWifiActivityInfo();
+ registerModemActivityInfo();
+ registerBluetoothActivityInfo();
+ registerSystemElapsedRealtime();
+ registerSystemUptime();
+ registerRemainingBatteryCapacity();
+ registerFullBatteryCapacity();
+ registerBatteryVoltage();
+ registerBatteryLevel();
+ registerBatteryCycleCount();
+ registerProcessMemoryState();
+ registerProcessMemoryHighWaterMark();
+ registerProcessMemorySnapshot();
+ registerSystemIonHeapSize();
+ registerProcessSystemIonHeapSize();
+ registerTemperature();
+ registerCoolingDevice();
+ registerBinderCalls();
+ registerBinderCallsExceptions();
+ registerLooperStats();
+ registerDiskStats();
+ registerDirectoryUsage();
+ registerAppSize();
+ registerCategorySize();
+ registerNumFingerprintsEnrolled();
+ registerNumFacesEnrolled();
+ registerProcStats();
+ registerProcStatsPkgProc();
+ registerDiskIO();
+ registerPowerProfile();
+ registerProcessCpuTime();
+ registerCpuTimePerThreadFreq();
+ registerDeviceCalculatedPowerUse();
+ registerDeviceCalculatedPowerBlameUid();
+ registerDeviceCalculatedPowerBlameOther();
+ registerDebugElapsedClock();
+ registerDebugFailingElapsedClock();
+ registerBuildInformation();
+ registerRoleHolder();
+ registerDangerousPermissionState();
+ registerTimeZoneDataInfo();
+ registerExternalStorageInfo();
+ registerAppsOnExternalStorageInfo();
+ registerFaceSettings();
+ registerAppOps();
+ registerNotificationRemoteViews();
+ registerDangerousPermissionState();
+ registerDangerousPermissionStateSampled();
+ }
+
+ private INetworkStatsService getINetworkStatsService() {
+ synchronized (mNetworkStatsLock) {
+ if (mNetworkStatsService == null) {
+ mNetworkStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ if (mNetworkStatsService != null) {
+ try {
+ mNetworkStatsService.asBinder().linkToDeath(() -> {
+ synchronized (mNetworkStatsLock) {
+ mNetworkStatsService = null;
+ }
+ }, /* flags */ 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "linkToDeath with NetworkStatsService failed", e);
+ mNetworkStatsService = null;
+ }
+ }
+
+ }
+ return mNetworkStatsService;
+ }
+ }
+
+ private IThermalService getIThermalService() {
+ synchronized (mThermalLock) {
+ if (mThermalService == null) {
+ mThermalService = IThermalService.Stub.asInterface(
+ ServiceManager.getService(Context.THERMAL_SERVICE));
+ if (mThermalService != null) {
+ try {
+ mThermalService.asBinder().linkToDeath(() -> {
+ synchronized (mThermalLock) {
+ mThermalService = null;
+ }
+ }, /* flags */ 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "linkToDeath with thermalService failed", e);
+ mThermalService = null;
+ }
+ }
+ }
+ return mThermalService;
+ }
+ }
+ private void registerWifiBytesTransfer() {
+ int tagId = StatsLog.WIFI_BYTES_TRANSFER;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2, 3, 4, 5})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullWifiBytesTransfer(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullWifiBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+ INetworkStatsService networkStatsService = getINetworkStatsService();
+ if (networkStatsService == null) {
+ Slog.e(TAG, "NetworkStats Service is not available!");
+ return StatsManager.PULL_SKIP;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ // TODO: Consider caching the following call to get BatteryStatsInternal.
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getWifiIfaces();
+ if (ifaces.length == 0) {
+ return StatsManager.PULL_SKIP;
+ }
+ // Combine all the metrics per Uid into one record.
+ NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid();
+ addNetworkStats(atomTag, pulledData, stats, false);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void addNetworkStats(
+ int tag, List<StatsEvent> ret, NetworkStats stats, boolean withFGBG) {
+ int size = stats.size();
+ NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
+ for (int j = 0; j < size; j++) {
+ stats.getValues(j, entry);
+ StatsEvent.Builder e = StatsEvent.newBuilder();
+ e.setAtomId(tag);
+ e.writeInt(entry.uid);
+ if (withFGBG) {
+ e.writeInt(entry.set);
+ }
+ e.writeLong(entry.rxBytes);
+ e.writeLong(entry.rxPackets);
+ e.writeLong(entry.txBytes);
+ e.writeLong(entry.txPackets);
+ ret.add(e.build());
+ }
+ }
+
+ /**
+ * Allows rollups per UID but keeping the set (foreground/background) slicing.
+ * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
+ */
+ private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
+ final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
+
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ entry.iface = NetworkStats.IFACE_ALL;
+ entry.tag = NetworkStats.TAG_NONE;
+ entry.metered = NetworkStats.METERED_ALL;
+ entry.roaming = NetworkStats.ROAMING_ALL;
+
+ int size = stats.size();
+ NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
+ for (int i = 0; i < size; i++) {
+ stats.getValues(i, recycle);
+
+ // Skip specific tags, since already counted in TAG_NONE
+ if (recycle.tag != NetworkStats.TAG_NONE) continue;
+
+ entry.set = recycle.set; // Allows slicing by background/foreground
+ entry.uid = recycle.uid;
+ entry.rxBytes = recycle.rxBytes;
+ entry.rxPackets = recycle.rxPackets;
+ entry.txBytes = recycle.txBytes;
+ entry.txPackets = recycle.txPackets;
+ // Operations purposefully omitted since we don't use them for statsd.
+ ret.combineValues(entry);
+ }
+ return ret;
+ }
+
+ private void registerWifiBytesTransferBackground() {
+ int tagId = StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {3, 4, 5, 6})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullWifiBytesTransferBackground(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullWifiBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) {
+ INetworkStatsService networkStatsService = getINetworkStatsService();
+ if (networkStatsService == null) {
+ Slog.e(TAG, "NetworkStats Service is not available!");
+ return StatsManager.PULL_SKIP;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getWifiIfaces();
+ if (ifaces.length == 0) {
+ return StatsManager.PULL_SKIP;
+ }
+ NetworkStats stats = rollupNetworkStatsByFGBG(
+ networkStatsService.getDetailedUidStats(ifaces));
+ addNetworkStats(atomTag, pulledData, stats, true);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerMobileBytesTransfer() {
+ int tagId = StatsLog.MOBILE_BYTES_TRANSFER;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2, 3, 4, 5})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullMobileBytesTransfer(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullMobileBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+ INetworkStatsService networkStatsService = getINetworkStatsService();
+ if (networkStatsService == null) {
+ Slog.e(TAG, "NetworkStats Service is not available!");
+ return StatsManager.PULL_SKIP;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getMobileIfaces();
+ if (ifaces.length == 0) {
+ return StatsManager.PULL_SKIP;
+ }
+ // Combine all the metrics per Uid into one record.
+ NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid();
+ addNetworkStats(atomTag, pulledData, stats, false);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerMobileBytesTransferBackground() {
+ int tagId = StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {3, 4, 5, 6})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullMobileBytesTransferBackground(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullMobileBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) {
+ INetworkStatsService networkStatsService = getINetworkStatsService();
+ if (networkStatsService == null) {
+ Slog.e(TAG, "NetworkStats Service is not available!");
+ return StatsManager.PULL_SKIP;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getMobileIfaces();
+ if (ifaces.length == 0) {
+ return StatsManager.PULL_SKIP;
+ }
+ NetworkStats stats = rollupNetworkStatsByFGBG(
+ networkStatsService.getDetailedUidStats(ifaces));
+ addNetworkStats(atomTag, pulledData, stats, true);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerBluetoothBytesTransfer() {
+ int tagId = StatsLog.BLUETOOTH_BYTES_TRANSFER;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2, 3})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullBluetoothBytesTransfer(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ /**
+ * Helper method to extract the Parcelable controller info from a
+ * SynchronousResultReceiver.
+ */
+ private static <T extends Parcelable> T awaitControllerInfo(
+ @Nullable SynchronousResultReceiver receiver) {
+ if (receiver == null) {
+ return null;
+ }
+
+ try {
+ final SynchronousResultReceiver.Result result =
+ receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
+ if (result.bundle != null) {
+ // This is the final destination for the Bundle.
+ result.bundle.setDefusable(true);
+
+ final T data = result.bundle.getParcelable(RESULT_RECEIVER_CONTROLLER_KEY);
+ if (data != null) {
+ return data;
+ }
+ }
+ Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
+ } catch (TimeoutException e) {
+ Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
+ }
+ return null;
+ }
+
+ private synchronized BluetoothActivityEnergyInfo fetchBluetoothData() {
+ // TODO: Investigate whether the synchronized keyword is needed.
+ final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver(
+ "bluetooth");
+ adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
+ return awaitControllerInfo(bluetoothReceiver);
+ } else {
+ Slog.e(TAG, "Failed to get bluetooth adapter!");
+ return null;
+ }
+ }
+
+ private int pullBluetoothBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+ BluetoothActivityEnergyInfo info = fetchBluetoothData();
+ if (info == null || info.getUidTraffic() == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ for (UidTraffic traffic : info.getUidTraffic()) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(traffic.getUid())
+ .writeLong(traffic.getRxBytes())
+ .writeLong(traffic.getTxBytes())
+ .build();
+ pulledData.add(e);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+ private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
+ private void registerKernelWakelock() {
+ int tagId = StatsLog.KERNEL_WAKELOCK;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ /* PullAtomMetadata */ null,
+ (atomTag, data) -> pullKernelWakelock(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullKernelWakelock(int atomTag, List<StatsEvent> pulledData) {
+ final KernelWakelockStats wakelockStats =
+ mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+ for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+ String name = ent.getKey();
+ KernelWakelockStats.Entry kws = ent.getValue();
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeString(name)
+ .writeInt(kws.mCount)
+ .writeInt(kws.mVersion)
+ .writeLong(kws.mTotalTime)
+ .build();
+ pulledData.add(e);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
+ // Disables throttler on CPU time readers.
+ private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader =
+ new KernelCpuUidUserSysTimeReader(false);
+ private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader =
+ new KernelCpuUidFreqTimeReader(false);
+ private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader =
+ new KernelCpuUidActiveTimeReader(false);
+ private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader =
+ new KernelCpuUidClusterTimeReader(false);
+
+ private void registerCpuTimePerFreq() {
+ int tagId = StatsLog.CPU_TIME_PER_FREQ;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {3})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuTimePerFreq(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullCpuTimePerFreq(int atomTag, List<StatsEvent> pulledData) {
+ for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+ long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
+ if (clusterTimeMs != null) {
+ for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(cluster)
+ .writeInt(speed)
+ .writeLong(clusterTimeMs[speed])
+ .build();
+ pulledData.add(e);
+ }
+ }
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerCpuTimePerUid() {
+ int tagId = StatsLog.CPU_TIME_PER_UID;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2, 3})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuTimePerUid(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullCpuTimePerUid(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
+ long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeLong(userTimeUs)
+ .writeLong(systemTimeUs)
+ .build();
+ pulledData.add(e);
+ });
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerCpuTimePerUidFreq() {
+ // the throttling is 3sec, handled in
+ // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+ int tagId = StatsLog.CPU_TIME_PER_UID_FREQ;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {4})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuTimeperUidFreq(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullCpuTimeperUidFreq(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
+ for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+ if (cpuFreqTimeMs[freqIndex] != 0) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeInt(freqIndex)
+ .writeLong(cpuFreqTimeMs[freqIndex])
+ .build();
+ pulledData.add(e);
+ }
+ }
+ });
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerCpuActiveTime() {
+ // the throttling is 3sec, handled in
+ // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+ int tagId = StatsLog.CPU_ACTIVE_TIME;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuActiveTime(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullCpuActiveTime(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeLong(cpuActiveTimesMs)
+ .build();
+ pulledData.add(e);
+ });
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerCpuClusterTime() {
+ // the throttling is 3sec, handled in
+ // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+ int tagId = StatsLog.CPU_CLUSTER_TIME;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {3})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuClusterTime(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullCpuClusterTime(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
+ for (int i = 0; i < cpuClusterTimesMs.length; i++) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeInt(i)
+ .writeLong(cpuClusterTimesMs[i])
+ .build();
+ pulledData.add(e);
+ }
+ });
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerWifiActivityInfo() {
+ // No op.
+ }
+
+ private void pullWifiActivityInfo() {
+ // No op.
+ }
+
+ private void registerModemActivityInfo() {
+ // No op.
+ }
+
+ private void pullModemActivityInfo() {
+ // No op.
+ }
+
+ private void registerBluetoothActivityInfo() {
+ int tagId = StatsLog.BLUETOOTH_ACTIVITY_INFO;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ /* metadata */ null,
+ (atomTag, data) -> pullBluetoothActivityInfo(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullBluetoothActivityInfo(int atomTag, List<StatsEvent> pulledData) {
+ BluetoothActivityEnergyInfo info = fetchBluetoothData();
+ if (info == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeLong(info.getTimeStamp())
+ .writeInt(info.getBluetoothStackState())
+ .writeLong(info.getControllerTxTimeMillis())
+ .writeLong(info.getControllerRxTimeMillis())
+ .writeLong(info.getControllerIdleTimeMillis())
+ .writeLong(info.getControllerEnergyUsed())
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerSystemElapsedRealtime() {
+ // No op.
+ }
+
+ private void pullSystemElapsedRealtime() {
+ // No op.
+ }
+
+ private void registerSystemUptime() {
+ int tagId = StatsLog.SYSTEM_UPTIME;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullSystemUptime(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullSystemUptime(int atomTag, List<StatsEvent> pulledData) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeLong(SystemClock.elapsedRealtime())
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerRemainingBatteryCapacity() {
+ // No op.
+ }
+
+ private void pullRemainingBatteryCapacity() {
+ // No op.
+ }
+
+ private void registerFullBatteryCapacity() {
+ // No op.
+ }
+
+ private void pullFullBatteryCapacity() {
+ // No op.
+ }
+
+ private void registerBatteryVoltage() {
+ // No op.
+ }
+
+ private void pullBatteryVoltage() {
+ // No op.
+ }
+
+ private void registerBatteryLevel() {
+ // No op.
+ }
+
+ private void pullBatteryLevel() {
+ // No op.
+ }
+
+ private void registerBatteryCycleCount() {
+ // No op.
+ }
+
+ private void pullBatteryCycleCount() {
+ // No op.
+ }
+
+ private void registerProcessMemoryState() {
+ // No op.
+ }
+
+ private void pullProcessMemoryState() {
+ // No op.
+ }
+
+ private void registerProcessMemoryHighWaterMark() {
+ // No op.
+ }
+
+ private void pullProcessMemoryHighWaterMark() {
+ // No op.
+ }
+
+ private void registerProcessMemorySnapshot() {
+ // No op.
+ }
+
+ private void pullProcessMemorySnapshot() {
+ // No op.
+ }
+
+ private void registerSystemIonHeapSize() {
+ // No op.
+ }
+
+ private void pullSystemIonHeapSize() {
+ // No op.
+ }
+
+ private void registerProcessSystemIonHeapSize() {
+ // No op.
+ }
+
+ private void pullProcessSystemIonHeapSize() {
+ // No op.
+ }
+
+ private void registerTemperature() {
+ // No op.
+ }
+
+ private void pullTemperature() {
+ // No op.
+ }
+
+ private void registerCoolingDevice() {
+ // No op.
+ }
+
+ private void pullCooldownDevice() {
+ // No op.
+ }
+
+ private void registerBinderCalls() {
+ // No op.
+ }
+
+ private void pullBinderCalls() {
+ // No op.
+ }
+
+ private void registerBinderCallsExceptions() {
+ // No op.
+ }
+
+ private void pullBinderCallsExceptions() {
+ // No op.
+ }
+
+ private void registerLooperStats() {
+ // No op.
+ }
+
+ private void pullLooperStats() {
+ // No op.
+ }
+
+ private void registerDiskStats() {
+ // No op.
+ }
+
+ private void pullDiskStats() {
+ // No op.
+ }
+
+ private void registerDirectoryUsage() {
+ // No op.
+ }
+
+ private void pullDirectoryUsage() {
+ // No op.
+ }
+
+ private void registerAppSize() {
+ // No op.
+ }
+
+ private void pullAppSize() {
+ // No op.
+ }
+
+ private void registerCategorySize() {
+ // No op.
+ }
+
+ private void pullCategorySize() {
+ // No op.
+ }
+
+ private void registerNumFingerprintsEnrolled() {
+ // No op.
+ }
+
+ private void pullNumFingerprintsEnrolled() {
+ // No op.
+ }
+
+ private void registerNumFacesEnrolled() {
+ // No op.
+ }
+
+ private void pullNumFacesEnrolled() {
+ // No op.
+ }
+
+ private void registerProcStats() {
+ // No op.
+ }
+
+ private void pullProcStats() {
+ // No op.
+ }
+
+ private void registerProcStatsPkgProc() {
+ // No op.
+ }
+
+ private void pullProcStatsPkgProc() {
+ // No op.
+ }
+
+ private void registerDiskIO() {
+ // No op.
+ }
+
+ private void pullDiskIO() {
+ // No op.
+ }
+
+ private void registerPowerProfile() {
+ int tagId = StatsLog.POWER_PROFILE;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ /* PullAtomMetadata */ null,
+ (atomTag, data) -> pullPowerProfile(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullPowerProfile(int atomTag, List<StatsEvent> pulledData) {
+ PowerProfile powerProfile = new PowerProfile(mContext);
+ ProtoOutputStream proto = new ProtoOutputStream();
+ powerProfile.dumpDebug(proto);
+ proto.flush();
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeByteArray(proto.getBytes())
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerProcessCpuTime() {
+ // No op.
+ }
+
+ private void pullProcessCpuTime() {
+ // No op.
+ }
+
+ private void registerCpuTimePerThreadFreq() {
+ // No op.
+ }
+
+ private void pullCpuTimePerThreadFreq() {
+ // No op.
+ }
+
+ private void registerDeviceCalculatedPowerUse() {
+ // No op.
+ }
+
+ private void pullDeviceCalculatedPowerUse() {
+ // No op.
+ }
+
+ private void registerDeviceCalculatedPowerBlameUid() {
+ // No op.
+ }
+
+ private void pullDeviceCalculatedPowerBlameUid() {
+ // No op.
+ }
+
+ private void registerDeviceCalculatedPowerBlameOther() {
+ // No op.
+ }
+
+ private void pullDeviceCalculatedPowerBlameOther() {
+ // No op.
+ }
+
+ private void registerDebugElapsedClock() {
+ // No op.
+ }
+
+ private void pullDebugElapsedClock() {
+ // No op.
+ }
+
+ private void registerDebugFailingElapsedClock() {
+ // No op.
+ }
+
+ private void pullDebugFailingElapsedClock() {
+ // No op.
+ }
+
+ private void registerBuildInformation() {
+ int tagId = StatsLog.BUILD_INFORMATION;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullBuildInformation(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
+ }
+
+ private int pullBuildInformation(int atomTag, List<StatsEvent> pulledData) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeString(Build.FINGERPRINT)
+ .writeString(Build.BRAND)
+ .writeString(Build.PRODUCT)
+ .writeString(Build.DEVICE)
+ .writeString(Build.VERSION.RELEASE)
+ .writeString(Build.ID)
+ .writeString(Build.VERSION.INCREMENTAL)
+ .writeString(Build.TYPE)
+ .writeString(Build.TAGS)
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerRoleHolder() {
+ // No op.
+ }
+
+ private void pullRoleHolder() {
+ // No op.
+ }
+
+ private void registerDangerousPermissionState() {
+ // No op.
+ }
+
+ private void pullDangerousPermissionState() {
+ // No op.
+ }
+
+ private void registerTimeZoneDataInfo() {
+ // No op.
+ }
+
+ private void pullTimeZoneDataInfo() {
+ // No op.
+ }
+
+ private void registerExternalStorageInfo() {
+ // No op.
+ }
+
+ private void pullExternalStorageInfo() {
+ // No op.
+ }
+
+ private void registerAppsOnExternalStorageInfo() {
+ // No op.
+ }
+
+ private void pullAppsOnExternalStorageInfo() {
+ // No op.
+ }
+
+ private void registerFaceSettings() {
+ // No op.
+ }
+
+ private void pullRegisterFaceSettings() {
+ // No op.
+ }
+
+ private void registerAppOps() {
+ // No op.
+ }
+
+ private void pullAppOps() {
+ // No op.
+ }
+
+ private void registerNotificationRemoteViews() {
+ // No op.
+ }
+
+ private void pullNotificationRemoteViews() {
+ // No op.
+ }
+
+ private void registerDangerousPermissionStateSampled() {
+ // No op.
+ }
+
+ private void pullDangerousPermissionStateSampled() {
+ // No op.
}
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index f661b5e..468b806 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -21,7 +21,6 @@
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
import android.os.TimestampedValue;
import java.io.PrintWriter;
@@ -62,10 +61,10 @@
/** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
void acquireWakeLock();
- /** Returns the elapsedRealtimeMillis clock value. The WakeLock must be held. */
+ /** Returns the elapsedRealtimeMillis clock value. */
long elapsedRealtimeMillis();
- /** Returns the system clock value. The WakeLock must be held. */
+ /** Returns the system clock value. */
long systemClockMillis();
/** Sets the device system clock. The WakeLock must be held. */
@@ -73,9 +72,6 @@
/** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */
void releaseWakeLock();
-
- /** Send the supplied intent as a stick broadcast. */
- void sendStickyBroadcast(@NonNull Intent intent);
}
/** Initialize the strategy. */
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
index 42d59d5..19484db 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
@@ -20,11 +20,9 @@
import android.app.AlarmManager;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
@@ -90,13 +88,11 @@
@Override
public long elapsedRealtimeMillis() {
- checkWakeLockHeld();
return SystemClock.elapsedRealtime();
}
@Override
public long systemClockMillis() {
- checkWakeLockHeld();
return System.currentTimeMillis();
}
@@ -112,11 +108,6 @@
mWakeLock.release();
}
- @Override
- public void sendStickyBroadcast(@NonNull Intent intent) {
- mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
- }
-
private void checkWakeLockHeld() {
if (!mWakeLock.isHeld()) {
Slog.wtf(TAG, "WakeLock " + mWakeLock + " not held");
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index da848d8..e95fc4a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,9 +23,7 @@
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
import android.os.TimestampedValue;
-import android.telephony.TelephonyManager;
import android.util.LocalLog;
import android.util.Slog;
@@ -535,17 +533,6 @@
} else {
mLastAutoSystemClockTimeSet = null;
}
-
- // Historically, Android has sent a TelephonyManager.ACTION_NETWORK_SET_TIME broadcast only
- // when setting the time using NITZ.
- if (origin == ORIGIN_PHONE) {
- // Send a broadcast that telephony code used to send after setting the clock.
- // TODO Remove this broadcast as soon as there are no remaining listeners.
- Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_SET_TIME);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.putExtra("time", newSystemClockMillis);
- mCallback.sendStickyBroadcast(intent);
- }
}
/**
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 5b58199..eb7c5ca 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -127,6 +127,9 @@
// A map from user id to UserState.
private final SparseArray<UserState> mUserStates = new SparseArray<>();
+ // A map from session id to session state saved in userstate
+ private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>();
+
private final WatchLogHandler mWatchLogHandler;
public TvInputManagerService(Context context) {
@@ -632,7 +635,8 @@
UserState userState = getOrCreateUserStateLocked(userId);
SessionState sessionState = userState.sessionStateMap.get(sessionToken);
if (DEBUG) {
- Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")");
+ Slog.d(TAG, "createSessionInternalLocked(inputId="
+ + sessionState.inputId + ", sessionId=" + sessionState.sessionId + ")");
}
InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
@@ -643,9 +647,11 @@
// Create a session. When failed, send a null token immediately.
try {
if (sessionState.isRecordingSession) {
- service.createRecordingSession(callback, sessionState.inputId);
+ service.createRecordingSession(
+ callback, sessionState.inputId, sessionState.sessionId);
} else {
- service.createSession(channels[1], callback, sessionState.inputId);
+ service.createSession(
+ channels[1], callback, sessionState.inputId, sessionState.sessionId);
}
} catch (RemoteException e) {
Slog.e(TAG, "error in createSession", e);
@@ -715,6 +721,8 @@
}
}
+ mSessionIdToSessionStateMap.remove(sessionState.sessionId);
+
ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName);
if (serviceState != null) {
serviceState.sessionTokens.remove(sessionToken);
@@ -1156,9 +1164,11 @@
public void createSession(final ITvInputClient client, final String inputId,
boolean isRecordingSession, int seq, int userId) {
final int callingUid = Binder.getCallingUid();
- final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
userId, "createSession");
final long identity = Binder.clearCallingIdentity();
+ StringBuilder sessionId = new StringBuilder();
try {
synchronized (mLock) {
if (userId != mCurrentUserId && !isRecordingSession) {
@@ -1187,15 +1197,21 @@
return;
}
+ // Create a unique session id with pid, uid and resolved user id
+ sessionId.append(callingUid).append(callingPid).append(resolvedUserId);
+
// Create a new session token and a session state.
IBinder sessionToken = new Binder();
SessionState sessionState = new SessionState(sessionToken, info.getId(),
info.getComponent(), isRecordingSession, client, seq, callingUid,
- resolvedUserId);
+ callingPid, resolvedUserId, sessionId.toString());
// Add them to the global session state map of the current user.
userState.sessionStateMap.put(sessionToken, sessionState);
+ // Map the session id to the sessionStateMap in the user state
+ mSessionIdToSessionStateMap.put(sessionId.toString(), sessionState);
+
// Also, add them to the session state map of the current service.
serviceState.sessionTokens.add(sessionToken);
@@ -2003,6 +2019,43 @@
}
@Override
+ public int getClientPid(String sessionId) {
+ ensureTunerResourceAccessPermission();
+ final long identity = Binder.clearCallingIdentity();
+
+ int clientPid = TvInputManager.UNKNOWN_CLIENT_PID;
+ try {
+ synchronized (mLock) {
+ try {
+ clientPid = getClientPidLocked(sessionId);
+ } catch (ClientPidNotFoundException e) {
+ Slog.e(TAG, "error in getClientPid", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return clientPid;
+ }
+
+ private int getClientPidLocked(String sessionId)
+ throws IllegalStateException {
+ if (mSessionIdToSessionStateMap.get(sessionId) == null) {
+ throw new IllegalStateException("Client Pid not found with sessionId "
+ + sessionId);
+ }
+ return mSessionIdToSessionStateMap.get(sessionId).callingPid;
+ }
+
+ private void ensureTunerResourceAccessPermission() {
+ if (mContext.checkCallingPermission(
+ android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires TUNER_RESOURCE_ACCESS permission");
+ }
+ }
+
+ @Override
@SuppressWarnings("resource")
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -2094,9 +2147,11 @@
pw.increaseIndent();
pw.println("inputId: " + session.inputId);
+ pw.println("sessionId: " + session.sessionId);
pw.println("client: " + session.client);
pw.println("seq: " + session.seq);
pw.println("callingUid: " + session.callingUid);
+ pw.println("callingPid: " + session.callingPid);
pw.println("userId: " + session.userId);
pw.println("sessionToken: " + session.sessionToken);
pw.println("session: " + session.session);
@@ -2226,11 +2281,13 @@
private final class SessionState implements IBinder.DeathRecipient {
private final String inputId;
+ private final String sessionId;
private final ComponentName componentName;
private final boolean isRecordingSession;
private final ITvInputClient client;
private final int seq;
private final int callingUid;
+ private final int callingPid;
private final int userId;
private final IBinder sessionToken;
private ITvInputSession session;
@@ -2240,7 +2297,7 @@
private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
- int userId) {
+ int callingPid, int userId, String sessionId) {
this.sessionToken = sessionToken;
this.inputId = inputId;
this.componentName = componentName;
@@ -2248,7 +2305,9 @@
this.client = client;
this.seq = seq;
this.callingUid = callingUid;
+ this.callingPid = callingPid;
this.userId = userId;
+ this.sessionId = sessionId;
}
@Override
@@ -2962,4 +3021,10 @@
super(name);
}
}
+
+ private static class ClientPidNotFoundException extends IllegalArgumentException {
+ public ClientPidNotFoundException(String name) {
+ super(name);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/utils/quota/QuotaTracker.java b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
index ef1f426..a8cf9f6 100644
--- a/services/core/java/com/android/server/utils/quota/QuotaTracker.java
+++ b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
@@ -172,6 +172,16 @@
// Exposed API to users.
+ /** Remove all saved events from the tracker. */
+ public void clear() {
+ synchronized (mLock) {
+ mInQuotaAlarmListener.clearLocked();
+ mFreeQuota.clear();
+
+ dropEverythingLocked();
+ }
+ }
+
/**
* @return true if the UPTC is within quota, false otherwise.
* @throws IllegalStateException if given categorizer returns a Category that's not recognized.
@@ -245,10 +255,7 @@
mIsEnabled = enable;
if (!mIsEnabled) {
- mInQuotaAlarmListener.clearLocked();
- mFreeQuota.clear();
-
- dropEverythingLocked();
+ clear();
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 26d76a8d..19bc560 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1633,12 +1633,7 @@
requestedVrComponent = (aInfo.requestedVrComponent == null) ?
null : ComponentName.unflattenFromString(aInfo.requestedVrComponent);
- lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
- if (info.applicationInfo.isPrivilegedApp()
- && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
- || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
- lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
- }
+ lockTaskLaunchMode = getLockTaskLaunchMode(aInfo, options);
if (options != null) {
pendingOptions = options;
@@ -1646,15 +1641,27 @@
if (usageReport != null) {
appTimeTracker = new AppTimeTracker(usageReport);
}
- final boolean useLockTask = pendingOptions.getLockTaskMode();
- if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
- lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
- }
// Gets launch display id from options. It returns INVALID_DISPLAY if not set.
mHandoverLaunchDisplayId = options.getLaunchDisplayId();
}
}
+ static int getLockTaskLaunchMode(ActivityInfo aInfo, @Nullable ActivityOptions options) {
+ int lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
+ if (aInfo.applicationInfo.isPrivilegedApp()
+ && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
+ || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
+ lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+ }
+ if (options != null) {
+ final boolean useLockTask = options.getLockTaskMode();
+ if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
+ lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+ }
+ }
+ return lockTaskLaunchMode;
+ }
+
@Override
ActivityRecord asActivityRecord() {
// I am an activity record!
@@ -2130,8 +2137,10 @@
(intent == null || (intent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
}
+ @Override
boolean isFocusable() {
- return mRootWindowContainer.isFocusable(this, isAlwaysFocusable());
+ return super.isFocusable()
+ && (getWindowConfiguration().canReceiveKeys() || isAlwaysFocusable());
}
boolean isResizeable() {
@@ -2482,7 +2491,7 @@
// We are finishing the top focused activity and its stack has nothing to be focused so
// the next focusable stack should be focused.
if (mayAdjustTop
- && (stack.topRunningActivity() == null || !stack.isFocusable())) {
+ && (stack.topRunningActivity() == null || !stack.isTopActivityFocusable())) {
if (shouldAdjustGlobalFocus) {
// Move the entire hierarchy to top with updating global top resumed activity
// and focused application if needed.
@@ -5934,7 +5943,11 @@
mAnimationBoundsLayer = createAnimationBoundsLayer(t);
// Crop to stack bounds.
- t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
+ if (!WindowManagerService.sHierarchicalAnimations) {
+ // For Hierarchical animation, we don't need to set window crop since the leash
+ // surface size has already same as the animating container.
+ t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
+ }
t.setLayer(mAnimationBoundsLayer, layer);
// Reparent leash to animation bounds layer.
@@ -5982,9 +5995,10 @@
return;
}
clearThumbnail();
+ final Transaction transaction = getAnimatingContainer().getPendingTransaction();
mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory,
- getPendingTransaction(), this, thumbnailHeader);
- mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader));
+ transaction, getAnimatingContainer(), thumbnailHeader);
+ mThumbnail.startAnimation(transaction, loadThumbnailAnimation(thumbnailHeader));
}
/**
@@ -6011,13 +6025,13 @@
if (thumbnail == null) {
return;
}
+ final Transaction transaction = getAnimatingContainer().getPendingTransaction();
mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory,
- getPendingTransaction(), this, thumbnail);
+ transaction, getAnimatingContainer(), thumbnail);
final Animation animation =
getDisplayContent().mAppTransition.createCrossProfileAppsThumbnailAnimationLocked(
win.getFrameLw());
- mThumbnail.startAnimation(getPendingTransaction(), animation, new Point(frame.left,
- frame.top));
+ mThumbnail.startAnimation(transaction, animation, new Point(frame.left, frame.top));
}
private Animation loadThumbnailAnimation(GraphicBuffer thumbnailHeader) {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index c959439..f5fba8e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -161,6 +161,7 @@
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.ITaskOrganizer;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -803,6 +804,16 @@
setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
false /* creating */);
+
+ windowingMode = getWindowingMode();
+ /*
+ * Different windowing modes may be managed by different task organizers. If
+ * getTaskOrganizer returns null, we still call transferToTaskOrganizer to
+ * make sure we clear it.
+ */
+ final ITaskOrganizer org =
+ mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
+ transferToTaskOrganizer(org);
}
/**
@@ -1237,13 +1248,20 @@
}
}
+ @Override
boolean isFocusable() {
+ return super.isFocusable() && !(inSplitScreenPrimaryWindowingMode()
+ && mRootWindowContainer.mIsDockMinimized);
+ }
+
+ boolean isTopActivityFocusable() {
final ActivityRecord r = topRunningActivity();
- return mRootWindowContainer.isFocusable(this, r != null && r.isFocusable());
+ return r != null ? r.isFocusable()
+ : (isFocusable() && getWindowConfiguration().canReceiveKeys());
}
boolean isFocusableAndVisible() {
- return isFocusable() && shouldBeVisible(null /* starting */);
+ return isTopActivityFocusable() && shouldBeVisible(null /* starting */);
}
@Override
@@ -1650,6 +1668,33 @@
}
/**
+ * Indicate whether the first task in this stack is controlled by a TaskOrganizer. We aren't
+ * expecting to use the TaskOrganizer in multiple task per stack scenarios so checking
+ * the first one is ok.
+ */
+ boolean isControlledByTaskOrganizer() {
+ return getChildCount() > 0 && getTopMostTask().mTaskOrganizer != null;
+ }
+
+ private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) {
+ tr.setTaskOrganizer(organizer);
+ }
+
+ /**
+ * Transfer control of the leashes and IWindowContainers to the given ITaskOrganizer.
+ * This will (or shortly there-after) invoke the taskAppeared callbacks.
+ * If the tasks had a previous TaskOrganizer, setTaskOrganizer will take care of
+ * emitting the taskVanished callbacks.
+ */
+ void transferToTaskOrganizer(ITaskOrganizer organizer) {
+ final PooledConsumer c = PooledLambda.obtainConsumer(
+ ActivityStack::transferSingleTaskToOrganizer,
+ PooledLambda.__(Task.class), organizer);
+ forAllTasks(c);
+ c.recycle();
+ }
+
+ /**
* Returns true if the stack should be visible.
*
* @param starting The currently starting activity or null if there is none.
@@ -3577,6 +3622,15 @@
void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds, int animationDuration,
boolean fromFullscreen) {
if (!inPinnedWindowingMode()) return;
+
+ /**
+ * TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation.
+ * If this PIP Task is controlled by a TaskOrganizer, the animation occurs entirely
+ * on the TaskOrganizer side, so we just hand over the leash without doing any animation.
+ * We have to be careful to not schedule the enter-pip callback as the TaskOrganizer
+ * needs to have flexibility to schedule that at an appropriate point in the animation.
+ */
+ if (isControlledByTaskOrganizer()) return;
if (toBounds == null /* toFullscreen */) {
final Configuration parentConfig = getParent().getConfiguration();
final ActivityRecord top = topRunningNonOverlayTaskActivity();
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index d63165a..aa90248 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2548,6 +2548,7 @@
final PooledConsumer c = PooledLambda.obtainConsumer(
ActivityRecord::updatePictureInPictureMode,
PooledLambda.__(ActivityRecord.class), targetStackBounds, forceUpdate);
+ task.getStack().setBounds(targetStackBounds);
task.forAllActivities(c);
c.recycle();
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index df97caa..d61d29d 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -52,6 +52,7 @@
import android.os.UserManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.BlockedAppActivity;
import com.android.internal.app.HarmfulAppWarningActivity;
import com.android.internal.app.SuspendedAppActivity;
import com.android.internal.app.UnlaunchableAppActivity;
@@ -166,6 +167,9 @@
// no user action can undo this.
return true;
}
+ if (interceptLockTaskModeViolationPackageIfNeeded()) {
+ return true;
+ }
if (interceptHarmfulAppIfNeeded()) {
// If the app has a "harmful app" warning associated with it, we should ask to uninstall
// before issuing the work challenge.
@@ -262,6 +266,25 @@
return true;
}
+ private boolean interceptLockTaskModeViolationPackageIfNeeded() {
+ if (mAInfo == null || mAInfo.applicationInfo == null) {
+ return false;
+ }
+ LockTaskController controller = mService.getLockTaskController();
+ String packageName = mAInfo.applicationInfo.packageName;
+ int lockTaskLaunchMode = ActivityRecord.getLockTaskLaunchMode(mAInfo, mActivityOptions);
+ if (controller.isActivityAllowed(mUserId, packageName, lockTaskLaunchMode)) {
+ return false;
+ }
+ mIntent = BlockedAppActivity.createIntent(mUserId, mAInfo.applicationInfo.packageName);
+ mCallingPid = mRealCallingPid;
+ mCallingUid = mRealCallingUid;
+ mResolvedType = null;
+ mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid);
+ mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
+ return true;
+ }
+
private boolean interceptWorkProfileChallengeIfNeeded() {
final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
if (interceptingIntent == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 61ba15c..6587226 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1569,7 +1569,7 @@
if (mDoResume) {
final ActivityRecord topTaskActivity =
mStartActivity.getTask().topRunningActivityLocked();
- if (!mTargetStack.isFocusable()
+ if (!mTargetStack.isTopActivityFocusable()
|| (topTaskActivity != null && topTaskActivity.isTaskOverlay()
&& mStartActivity != topTaskActivity)) {
// If the activity is not focusable, we can't resume it, but still would like to
@@ -1588,7 +1588,7 @@
// will not update the focused stack. If starting the new activity now allows the
// task stack to be focusable, then ensure that we now update the focused stack
// accordingly.
- if (mTargetStack.isFocusable()
+ if (mTargetStack.isTopActivityFocusable()
&& !mRootWindowContainer.isTopDisplayFocusedStack(mTargetStack)) {
mTargetStack.moveToFront("startActivityInner");
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 474c5c9..e308f6b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -28,7 +28,7 @@
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.Manifest.permission.STOP_APP_SWITCHES;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW;
import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
@@ -226,6 +226,7 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.IRecentsAnimationRunner;
+import android.view.ITaskOrganizer;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.WindowContainerTransaction;
@@ -247,7 +248,6 @@
import com.android.internal.policy.KeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -344,6 +344,10 @@
/** This activity is being relaunched due to a free-resize operation. */
public static final int RELAUNCH_REASON_FREE_RESIZE = 2;
+ /** Flag indicating that an applied transaction may have effected lifecycle */
+ private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1;
+ private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1;
+
Context mContext;
/**
@@ -662,6 +666,12 @@
private FontScaleSettingObserver mFontScaleSettingObserver;
+ /**
+ * Stores the registration and state of TaskOrganizers in use.
+ */
+ TaskOrganizerController mTaskOrganizerController =
+ new TaskOrganizerController(this, mGlobalLock);
+
private int mDeviceOwnerUid = Process.INVALID_UID;
private final class FontScaleSettingObserver extends ContentObserver {
@@ -1271,6 +1281,14 @@
.execute();
}
+ @Override
+ public final void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
+ enforceCallerIsRecentsOrHasPermission(
+ MANAGE_ACTIVITY_STACKS, "registerTaskOrganizer()");
+ synchronized (mGlobalLock) {
+ mTaskOrganizerController.registerTaskOrganizer(organizer, windowingMode);
+ }
+ }
@Override
public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
@@ -1421,7 +1439,7 @@
int handleIncomingUser(int callingPid, int callingUid, int userId, String name) {
return mAmInternal.handleIncomingUser(callingPid, callingUid, userId, false /* allowAll */,
- ALLOW_FULL_ONLY, name, null /* callerPackage */);
+ ALLOW_NON_FULL, name, null /* callerPackage */);
}
@Override
@@ -3285,7 +3303,7 @@
}
}
- private void sanitizeAndApplyConfigChange(ConfigurationContainer container,
+ private int sanitizeAndApplyChange(ConfigurationContainer container,
WindowContainerTransaction.Change change) {
if (!(container instanceof Task || container instanceof ActivityStack)) {
throw new RuntimeException("Invalid token in task transaction");
@@ -3297,12 +3315,22 @@
configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
- Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
- c.setTo(change.getConfiguration(), configMask, windowMask);
- container.onRequestedOverrideConfigurationChanged(c);
- // TODO(b/145675353): remove the following once we could apply new bounds to the
- // pinned stack together with its children.
- resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+ int effects = 0;
+ if (configMask != 0) {
+ Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
+ c.setTo(change.getConfiguration(), configMask, windowMask);
+ container.onRequestedOverrideConfigurationChanged(c);
+ // TODO(b/145675353): remove the following once we could apply new bounds to the
+ // pinned stack together with its children.
+ resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+ effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ }
+ if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) {
+ if (container.setFocusable(change.getFocusable())) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
+ return effects;
}
private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
@@ -3319,23 +3347,68 @@
}
}
+ private int applyWindowContainerChange(ConfigurationContainer cc,
+ WindowContainerTransaction.Change c) {
+ int effects = sanitizeAndApplyChange(cc, c);
+
+ Rect enterPipBounds = c.getEnterPipBounds();
+ if (enterPipBounds != null) {
+ Task tr = (Task) cc;
+ mStackSupervisor.updatePictureInPictureMode(tr,
+ enterPipBounds, true);
+ }
+ return effects;
+ }
+
@Override
public void applyContainerTransaction(WindowContainerTransaction t) {
mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()");
+ if (t == null) {
+ return;
+ }
long ident = Binder.clearCallingIdentity();
try {
- if (t == null) {
- return;
- }
synchronized (mGlobalLock) {
- Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
- t.getChanges().entrySet().iterator();
- while (entries.hasNext()) {
- final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
- entries.next();
- final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder(
- entry.getKey()).getContainer();
- sanitizeAndApplyConfigChange(cc, entry.getValue());
+ int effects = 0;
+ deferWindowLayout();
+ try {
+ ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
+ Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
+ t.getChanges().entrySet().iterator();
+ while (entries.hasNext()) {
+ final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
+ entries.next();
+ final ConfigurationContainer cc =
+ ConfigurationContainer.RemoteToken.fromBinder(
+ entry.getKey()).getContainer();
+ int containerEffect = applyWindowContainerChange(cc, entry.getValue());
+ effects |= containerEffect;
+ // Lifecycle changes will trigger ensureConfig for everything.
+ if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
+ && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+ if (cc instanceof WindowContainer) {
+ haveConfigChanges.add((WindowContainer) cc);
+ }
+ }
+ }
+ if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
+ // Already calls ensureActivityConfig
+ mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+ final PooledConsumer f = PooledLambda.obtainConsumer(
+ ActivityRecord::ensureActivityConfiguration,
+ PooledLambda.__(ActivityRecord.class), 0,
+ false /* preserveWindow */);
+ try {
+ for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
+ haveConfigChanges.valueAt(i).forAllActivities(f);
+ }
+ } finally {
+ f.recycle();
+ }
+ }
+ } finally {
+ continueWindowLayout();
}
}
} finally {
@@ -4057,7 +4130,11 @@
throw new IllegalArgumentException("Stack: " + stack
+ " doesn't support animated resize.");
}
- if (animate) {
+ /**
+ * TODO(b/146594635): Remove all PIP animation code from WM
+ * once SysUI handles animation. Don't even try to animate TaskOrganized tasks.
+ */
+ if (animate && !stack.isControlledByTaskOrganizer()) {
stack.animateResizePinnedStack(null /* destBounds */,
null /* sourceHintBounds */, animationDuration,
false /* fromFullscreen */);
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index dd3365c..1e60ce8 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -624,6 +624,21 @@
return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
}
+ /**
+ * Returns {@code true} if this container is focusable. Generally, if a parent is not focusable,
+ * this will not be focusable either.
+ */
+ boolean isFocusable() {
+ // TODO(split): Move this to WindowContainer once Split-screen is based on a WindowContainer
+ // like DisplayArea vs. TaskTiles.
+ ConfigurationContainer parent = getParent();
+ return parent == null || parent.isFocusable();
+ }
+
+ boolean setFocusable(boolean focusable) {
+ return false;
+ }
+
boolean hasChild() {
return getChildCount() > 0;
}
@@ -666,4 +681,8 @@
return sb.toString();
}
}
+
+ RemoteToken getRemoteToken() {
+ return mRemoteToken;
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ba9d757..908c4f1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5915,7 +5915,7 @@
final ActivityRecord resumedActivity = stack.getResumedActivity();
if (resumedActivity != null
&& (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
- || !stack.isFocusable())) {
+ || !stack.isTopActivityFocusable())) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack
+ " mResumedActivity=" + resumedActivity);
someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
@@ -6238,7 +6238,7 @@
for (int i = getStackCount() - 1; i >= 0; --i) {
final ActivityStack stack = getStackAt(i);
// Only consider focusable stacks other than the current focused one.
- if (stack == focusedStack || !stack.isFocusable()) {
+ if (stack == focusedStack || !stack.isTopActivityFocusable()) {
continue;
}
topRunning = stack.topRunningActivity();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f9ad03f..9c62e99 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2061,7 +2061,8 @@
cf.set(displayFrames.mRestricted);
}
applyStableConstraints(sysUiFl, fl, cf, displayFrames);
- if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+ if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+ && adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
@@ -2138,7 +2139,8 @@
applyStableConstraints(sysUiFl, fl, cf, displayFrames);
- if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+ if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+ && adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
@@ -2179,7 +2181,8 @@
cf.set(displayFrames.mContent);
df.set(displayFrames.mContent);
}
- if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+ if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+ && adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index c09834f..9d985d7 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -75,7 +75,8 @@
// activities are actually behind other fullscreen activities, but still required
// to be visible (such as performing Recents animation).
final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind
- && mContiner.isFocusable() && mContiner.isInStackLocked(starting) == null;
+ && mContiner.isTopActivityFocusable()
+ && mContiner.isInStackLocked(starting) == null;
final PooledConsumer f = PooledLambda.obtainConsumer(
EnsureActivitiesVisibleHelper::setActivityVisibilityState, this,
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index c4b67d7..091f66c 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -279,6 +279,19 @@
// we avoid reintroducing this concept by just choosing one of them here.
inputWindowHandle.surfaceInset = child.getAttrs().surfaceInsets.left;
+ /**
+ * If the window is in a TaskManaged by a TaskOrganizer then most cropping
+ * will be applied using the SurfaceControl hierarchy from the Organizer.
+ * This means we need to make sure that these changes in crop are reflected
+ * in the input windows, and so ensure this flag is set so that
+ * the input crop always reflects the surface hierarchy.
+ * we may have some issues with modal-windows, but I guess we can
+ * cross that bridge when we come to implementing full-screen TaskOrg
+ */
+ if (child.getTask() != null && child.getTask().isControlledByTaskOrganizer()) {
+ inputWindowHandle.replaceTouchableRegionWithCrop(null /* Use this surfaces crop */);
+ }
+
if (child.mGlobalScale != 1) {
// If we are scaling the window, input coordinates need
// to be inversely scaled to map from what is on screen
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index a13383d..5a591ec 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -129,6 +129,7 @@
if (win == null) {
setServerVisible(false);
mSource.setFrame(new Rect());
+ mSource.setVisibleFrame(null);
} else if (mControllable) {
mWin.setControllableInsetProvider(this);
if (mControlTarget != null) {
@@ -160,6 +161,15 @@
mTmpRect.inset(mWin.mGivenContentInsets);
}
mSource.setFrame(mTmpRect);
+
+ if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0
+ || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) {
+ mTmpRect.set(mWin.getFrameLw());
+ mTmpRect.inset(mWin.mGivenVisibleInsets);
+ mSource.setVisibleFrame(mTmpRect);
+ } else {
+ mSource.setVisibleFrame(null);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 02413bb..33b0453 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -23,6 +23,8 @@
import static android.content.Context.DEVICE_POLICY_SERVICE;
import static android.content.Context.STATUS_BAR_SERVICE;
import static android.content.Intent.ACTION_CALL_EMERGENCY;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_CURRENT;
import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
@@ -339,6 +341,20 @@
& DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD) != 0;
}
+ boolean isActivityAllowed(int userId, String packageName, int lockTaskLaunchMode) {
+ if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED) {
+ return true;
+ }
+ switch (lockTaskLaunchMode) {
+ case LOCK_TASK_LAUNCH_MODE_ALWAYS:
+ return true;
+ case LOCK_TASK_LAUNCH_MODE_NEVER:
+ return false;
+ default:
+ }
+ return isPackageWhitelisted(userId, packageName);
+ }
+
private boolean isEmergencyCallTask(Task task) {
final Intent intent = task.intent;
if (intent == null) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a7bf660..2f726e9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1860,14 +1860,6 @@
return null;
}
- boolean isFocusable(ConfigurationContainer container, boolean alwaysFocusable) {
- if (container.inSplitScreenPrimaryWindowingMode() && mIsDockMinimized) {
- return false;
- }
-
- return container.getWindowConfiguration().canReceiveKeys() || alwaysFocusable;
- }
-
boolean isTopDisplayFocusedStack(ActivityStack stack) {
return stack != null && stack == getTopDisplayFocusedStack();
}
@@ -2168,12 +2160,18 @@
mService.continueWindowLayout();
}
+ // TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation.
// Notify the pinned stack controller to prepare the PiP animation, expect callback
- // delivered from SystemUI to WM to start the animation.
- final PinnedStackController pinnedStackController =
+ // delivered from SystemUI to WM to start the animation. Unless we are using
+ // the TaskOrganizer in which case the animation will be entirely handled
+ // on that side.
+ if (mService.mTaskOrganizerController.getTaskOrganizer(WINDOWING_MODE_PINNED)
+ == null) {
+ final PinnedStackController pinnedStackController =
display.mDisplayContent.getPinnedStackController();
- pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
- null /* stackBounds */);
+ pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
+ null /* stackBounds */);
+ }
// TODO: revisit the following statement after the animation is moved from WM to SysUI.
// Update the visibility of all activities after the they have been reparented to the new
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index eaa0ea7..399c5d3 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -16,12 +16,8 @@
package com.android.server.wm;
-import static com.android.server.wm.AnimationSpecProto.ROTATE;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA;
-import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA;
import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING;
import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -29,9 +25,7 @@
import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER;
-import android.animation.ArgbEvaluator;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
@@ -46,9 +40,7 @@
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;
-import com.android.internal.R;
import com.android.server.protolog.common.ProtoLog;
-import com.android.server.wm.utils.RotationAnimationUtils;
import java.io.PrintWriter;
@@ -68,10 +60,10 @@
* animation first rotate the new content into the old orientation to then be able to
* animate to the new orientation
*
- * <li> The Background color frame: <p>
- * To have the animation seem more seamless, we add a color transitioning background behind the
- * exiting and entering layouts. We compute the brightness of the start and end
- * layouts and transition from the two brightness values as grayscale underneath the animation
+ * <li> The exiting Blackframe: <p>
+ * Because the change of orientation might change the width and height of the content (i.e
+ * when rotating from portrait to landscape) we "crop" the new content using black frames
+ * around the screenshot so the new content does not go beyond the screenshot's bounds
*
* <li> The entering Blackframe: <p>
* The enter Blackframe is similar to the exit Blackframe but is only used when a custom
@@ -89,6 +81,8 @@
*/
private static final int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER;
private static final int SCREEN_FREEZE_LAYER_ENTER = SCREEN_FREEZE_LAYER_BASE;
+ private static final int SCREEN_FREEZE_LAYER_SCREENSHOT = SCREEN_FREEZE_LAYER_BASE + 1;
+ private static final int SCREEN_FREEZE_LAYER_EXIT = SCREEN_FREEZE_LAYER_BASE + 2;
private final Context mContext;
private final DisplayContent mDisplayContent;
@@ -96,18 +90,16 @@
private final Transformation mRotateExitTransformation = new Transformation();
private final Transformation mRotateEnterTransformation = new Transformation();
// Complete transformations being applied.
+ private final Transformation mExitTransformation = new Transformation();
private final Transformation mEnterTransformation = new Transformation();
+ private final Matrix mFrameInitialMatrix = new Matrix();
private final Matrix mSnapshotInitialMatrix = new Matrix();
+ private final Matrix mSnapshotFinalMatrix = new Matrix();
+ private final Matrix mExitFrameFinalMatrix = new Matrix();
private final WindowManagerService mService;
- /** Only used for custom animations and not screen rotation. */
private SurfaceControl mEnterBlackFrameLayer;
- /** This layer contains the actual screenshot that is to be faded out. */
- private SurfaceControl mScreenshotLayer;
- /**
- * Only used for screen rotation and not custom animations. Layered behind all other layers
- * to avoid showing any "empty" spots
- */
- private SurfaceControl mBackColorSurface;
+ private SurfaceControl mRotationLayer;
+ private SurfaceControl mSurfaceControl;
private BlackFrame mEnteringBlackFrame;
private int mWidth, mHeight;
@@ -128,11 +120,8 @@
private boolean mFinishAnimReady;
private long mFinishAnimStartTime;
private boolean mForceDefaultOrientation;
+ private BlackFrame mExitingBlackFrame;
private SurfaceRotationAnimationController mSurfaceRotationAnimationController;
- /** Intensity of light/whiteness of the layout before rotation occurs. */
- private float mStartLuma;
- /** Intensity of light/whiteness of the layout after rotation occurs. */
- private float mEndLuma;
public ScreenRotationAnimation(Context context, DisplayContent displayContent,
boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) {
@@ -173,15 +162,9 @@
final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
try {
- mBackColorSurface = displayContent.makeChildSurface(null)
- .setName("BackColorSurface")
- .setColorLayer()
- .build();
-
- mScreenshotLayer = displayContent.makeOverlay()
+ mRotationLayer = displayContent.makeOverlay()
.setName("RotationLayer")
- .setBufferSize(mWidth, mHeight)
- .setSecure(isSecure)
+ .setContainerLayer()
.build();
mEnterBlackFrameLayer = displayContent.makeOverlay()
@@ -189,21 +172,26 @@
.setContainerLayer()
.build();
+ mSurfaceControl = mService.makeSurfaceBuilder(null)
+ .setName("ScreenshotSurface")
+ .setParent(mRotationLayer)
+ .setBufferSize(mWidth, mHeight)
+ .setSecure(isSecure)
+ .build();
+
// In case display bounds change, screenshot buffer and surface may mismatch so set a
// scaling mode.
SurfaceControl.Transaction t2 = mService.mTransactionFactory.get();
- t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW);
+ t2.setOverrideScalingMode(mSurfaceControl, Surface.SCALING_MODE_SCALE_TO_WINDOW);
t2.apply(true /* sync */);
// Capture a screenshot into the surface we just created.
final int displayId = display.getDisplayId();
final Surface surface = mService.mSurfaceFactory.get();
- surface.copyFrom(mScreenshotLayer);
+ surface.copyFrom(mSurfaceControl);
SurfaceControl.ScreenshotGraphicBuffer gb =
mService.mDisplayManagerInternal.screenshot(displayId);
if (gb != null) {
- mStartLuma = RotationAnimationUtils.getAvgBorderLuma(gb.getGraphicBuffer(),
- gb.getColorSpace());
try {
surface.attachAndQueueBufferWithColorSpace(gb.getGraphicBuffer(),
gb.getColorSpace());
@@ -214,15 +202,13 @@
// screenshot surface we display it in also has FLAG_SECURE so that
// the user can not screenshot secure layers via the screenshot surface.
if (gb.containsSecureLayers()) {
- t.setSecure(mScreenshotLayer, true);
+ t.setSecure(mSurfaceControl, true);
}
- t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
- t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
- t.setLayer(mBackColorSurface, -1);
- t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
- t.setAlpha(mBackColorSurface, 1);
- t.show(mScreenshotLayer);
- t.show(mBackColorSurface);
+ t.setLayer(mRotationLayer, SCREEN_FREEZE_LAYER_BASE);
+ t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT);
+ t.setAlpha(mSurfaceControl, 0);
+ t.show(mRotationLayer);
+ t.show(mSurfaceControl);
} else {
Slog.w(TAG, "Unable to take screenshot of display " + displayId);
}
@@ -232,11 +218,32 @@
}
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
- " FREEZE %s: CREATE", mScreenshotLayer);
+ " FREEZE %s: CREATE", mSurfaceControl);
setRotation(t, originalRotation);
t.apply();
}
+ private static void createRotationMatrix(int rotation, int width, int height,
+ Matrix outMatrix) {
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ outMatrix.reset();
+ break;
+ case Surface.ROTATION_90:
+ outMatrix.setRotate(90, 0, 0);
+ outMatrix.postTranslate(height, 0);
+ break;
+ case Surface.ROTATION_180:
+ outMatrix.setRotate(180, 0, 0);
+ outMatrix.postTranslate(width, height);
+ break;
+ case Surface.ROTATION_270:
+ outMatrix.setRotate(270, 0, 0);
+ outMatrix.postTranslate(0, width);
+ break;
+ }
+ }
+
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(STARTED, mStarted);
@@ -245,11 +252,11 @@
}
boolean hasScreenshot() {
- return mScreenshotLayer != null;
+ return mSurfaceControl != null;
}
private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
- if (mScreenshotLayer == null) {
+ if (mRotationLayer == null) {
return;
}
matrix.getValues(mTmpFloats);
@@ -260,19 +267,24 @@
x -= mCurrentDisplayRect.left;
y -= mCurrentDisplayRect.top;
}
- t.setPosition(mScreenshotLayer, x, y);
- t.setMatrix(mScreenshotLayer,
+ t.setPosition(mRotationLayer, x, y);
+ t.setMatrix(mRotationLayer,
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
- t.setAlpha(mScreenshotLayer, (float) 1.0);
- t.show(mScreenshotLayer);
+ t.setAlpha(mSurfaceControl, (float) 1.0);
+ t.setAlpha(mRotationLayer, (float) 1.0);
+ t.show(mRotationLayer);
}
public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer);
+ pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl);
pw.print(" mWidth="); pw.print(mWidth);
pw.print(" mHeight="); pw.println(mHeight);
+ pw.print(prefix); pw.print("mExitingBlackFrame="); pw.println(mExitingBlackFrame);
+ if (mExitingBlackFrame != null) {
+ mExitingBlackFrame.printTo(prefix + " ", pw);
+ }
pw.print(prefix);
pw.print("mEnteringBlackFrame=");
pw.println(mEnteringBlackFrame);
@@ -291,10 +303,20 @@
pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println();
pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation);
pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
+ pw.print(prefix); pw.print("mExitTransformation=");
+ mExitTransformation.printShortString(pw); pw.println();
pw.print(prefix); pw.print("mEnterTransformation=");
mEnterTransformation.printShortString(pw); pw.println();
+ pw.print(prefix); pw.print("mFrameInitialMatrix=");
+ mFrameInitialMatrix.printShortString(pw);
+ pw.println();
pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
- mSnapshotInitialMatrix.printShortString(pw);pw.println();
+ mSnapshotInitialMatrix.printShortString(pw);
+ pw.print(" mSnapshotFinalMatrix="); mSnapshotFinalMatrix.printShortString(pw);
+ pw.println();
+ pw.print(prefix); pw.print("mExitFrameFinalMatrix=");
+ mExitFrameFinalMatrix.printShortString(pw);
+ pw.println();
pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation);
if (mForceDefaultOrientation) {
pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString());
@@ -309,7 +331,7 @@
// to the snapshot to make it stay in the same original position
// with the current screen rotation.
int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0);
- RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
+ createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
setRotationTransform(t, mSnapshotInitialMatrix);
}
@@ -319,7 +341,7 @@
*/
private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration,
float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
- if (mScreenshotLayer == null) {
+ if (mSurfaceControl == null) {
// Can't do animation.
return false;
}
@@ -332,58 +354,89 @@
// Figure out how the screen has moved from the original rotation.
int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation);
+ mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_alpha);
final boolean customAnim;
if (exitAnim != 0 && enterAnim != 0) {
customAnim = true;
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
- mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_alpha);
} else {
customAnim = false;
- switch (delta) { /* Counter-Clockwise Rotations */
+ switch (delta) {
case Surface.ROTATION_0:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_0_exit);
+ com.android.internal.R.anim.screen_rotate_0_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_0_enter);
+ com.android.internal.R.anim.screen_rotate_0_enter);
break;
case Surface.ROTATION_90:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_plus_90_exit);
+ com.android.internal.R.anim.screen_rotate_plus_90_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_plus_90_enter);
+ com.android.internal.R.anim.screen_rotate_plus_90_enter);
break;
case Surface.ROTATION_180:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_180_exit);
+ com.android.internal.R.anim.screen_rotate_180_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_180_enter);
+ com.android.internal.R.anim.screen_rotate_180_enter);
break;
case Surface.ROTATION_270:
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_minus_90_exit);
+ com.android.internal.R.anim.screen_rotate_minus_90_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_minus_90_enter);
+ com.android.internal.R.anim.screen_rotate_minus_90_enter);
break;
}
}
- mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
- mRotateExitAnimation.restrictDuration(maxAnimationDuration);
- mRotateExitAnimation.scaleCurrentDuration(animationScale);
+ // Initialize the animations. This is a hack, redefining what "parent"
+ // means to allow supplying the last and next size. In this definition
+ // "%p" is the original (let's call it "previous") size, and "%" is the
+ // screen's current/new size.
mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
- mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
- mRotateEnterAnimation.scaleCurrentDuration(animationScale);
-
+ mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
mAnimRunning = false;
mFinishAnimReady = false;
mFinishAnimStartTime = -1;
- if (customAnim) {
- mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
- mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
+ mRotateExitAnimation.restrictDuration(maxAnimationDuration);
+ mRotateExitAnimation.scaleCurrentDuration(animationScale);
+ mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
+ mRotateEnterAnimation.scaleCurrentDuration(animationScale);
+ mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
+ mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
+
+ if (!customAnim && mExitingBlackFrame == null) {
+ try {
+ // Compute the transformation matrix that must be applied
+ // the the black frame to make it stay in the initial position
+ // before the new screen rotation. This is different than the
+ // snapshot transformation because the snapshot is always based
+ // of the native orientation of the screen, not the orientation
+ // we were last in.
+ createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix);
+
+ final Rect outer;
+ final Rect inner;
+ if (mForceDefaultOrientation) {
+ // Going from a smaller Display to a larger Display, add curtains to sides
+ // or top and bottom. Going from a larger to smaller display will result in
+ // no BlackSurfaces being constructed.
+ outer = mCurrentDisplayRect;
+ inner = mOriginalDisplayRect;
+ } else {
+ outer = new Rect(-mWidth, -mHeight, mWidth * 2, mHeight * 2);
+ inner = new Rect(0, 0, mWidth, mHeight);
+ }
+ mExitingBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
+ SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation,
+ mRotationLayer);
+ } catch (OutOfResourcesException e) {
+ Slog.w(TAG, "Unable to allocate black surface", e);
+ }
}
if (customAnim && mEnteringBlackFrame == null) {
@@ -398,12 +451,7 @@
}
}
- if (customAnim) {
- mSurfaceRotationAnimationController.startCustomAnimation();
- } else {
- mSurfaceRotationAnimationController.startScreenRotationAnimation();
- }
-
+ mSurfaceRotationAnimationController.startAnimation();
return true;
}
@@ -412,13 +460,11 @@
*/
public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration,
float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
- if (mScreenshotLayer == null) {
+ if (mSurfaceControl == null) {
// Can't do animation.
return false;
}
if (!mStarted) {
- mEndLuma = RotationAnimationUtils.getLumaOfSurfaceControl(mDisplayContent.getDisplay(),
- mDisplayContent.getWindowingLayer());
startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
exitAnim, enterAnim);
}
@@ -434,28 +480,28 @@
mSurfaceRotationAnimationController.cancel();
mSurfaceRotationAnimationController = null;
}
-
- if (mScreenshotLayer != null) {
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mScreenshotLayer);
+ if (mSurfaceControl != null) {
+ ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mSurfaceControl);
+ mSurfaceControl = null;
SurfaceControl.Transaction t = mService.mTransactionFactory.get();
- if (mScreenshotLayer.isValid()) {
- t.remove(mScreenshotLayer);
+ if (mRotationLayer != null) {
+ if (mRotationLayer.isValid()) {
+ t.remove(mRotationLayer);
+ }
+ mRotationLayer = null;
}
- mScreenshotLayer = null;
-
if (mEnterBlackFrameLayer != null) {
if (mEnterBlackFrameLayer.isValid()) {
t.remove(mEnterBlackFrameLayer);
}
mEnterBlackFrameLayer = null;
}
- if (mBackColorSurface != null) {
- t.remove(mBackColorSurface);
- mBackColorSurface = null;
- }
t.apply();
}
-
+ if (mExitingBlackFrame != null) {
+ mExitingBlackFrame.kill();
+ mExitingBlackFrame = null;
+ }
if (mEnteringBlackFrame != null) {
mEnteringBlackFrame.kill();
mEnteringBlackFrame = null;
@@ -491,28 +537,18 @@
* Utility class that runs a {@link ScreenRotationAnimation} on the {@link
* SurfaceAnimationRunner}.
* <p>
- * The rotation animation supports both screen rotation and custom animations
- *
- * For custom animations:
+ * The rotation animation is divided into the following hierarchy:
* <ul>
- * <li>
- * The screenshot layer which has an added animation of it's alpha channel
- * ("screen_rotate_alpha") and that will be applied along with the custom animation.
- * </li>
- * <li> A device layer that is animated with the provided custom animation </li>
- * </ul>
- *
- * For screen rotation:
- * <ul>
- * <li> A rotation layer that is both rotated and faded out during a single animation </li>
- * <li> A device layer that is both rotated and faded in during a single animation </li>
- * <li> A background color layer that transitions colors behind the first two layers </li>
- * </ul>
- *
+ * <li> A first rotation layer, containing the blackframes. This layer is animated by the
+ * "screen_rotate_X_exit" that applies a scale and rotate and where X is value of the rotation.
+ * <ul>
+ * <li> A child layer containing the screenshot on which is added an animation of it's
+ * alpha channel ("screen_rotate_alpha") and that will rotate with his parent layer.</li>
+ * </ul>
+ * <li> A second rotation layer used when custom animations are passed in
* {@link ScreenRotationAnimation#startAnimation(
* SurfaceControl.Transaction, long, float, int, int, int, int)}.
* </ul>
- *
* <p>
* Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of
* this three {@link SurfaceControl}s which then delegates the animation to the
@@ -520,35 +556,22 @@
*/
class SurfaceRotationAnimationController {
private SurfaceAnimator mDisplayAnimator;
+ private SurfaceAnimator mEnterBlackFrameAnimator;
private SurfaceAnimator mScreenshotRotationAnimator;
private SurfaceAnimator mRotateScreenAnimator;
- private SurfaceAnimator mEnterBlackFrameAnimator;
-
- void startCustomAnimation() {
- try {
- mService.mSurfaceAnimationRunner.deferStartingAnimations();
- mRotateScreenAnimator = startScreenshotAlphaAnimation();
- mDisplayAnimator = startDisplayRotation();
- if (mEnteringBlackFrame != null) {
- mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
- }
- } finally {
- mService.mSurfaceAnimationRunner.continueStartingAnimations();
- }
- }
/**
* Start the rotation animation of the display and the screenshot on the
* {@link SurfaceAnimationRunner}.
*/
- void startScreenRotationAnimation() {
- try {
- mService.mSurfaceAnimationRunner.deferStartingAnimations();
- mDisplayAnimator = startDisplayRotation();
+ void startAnimation() {
+ mRotateScreenAnimator = startScreenshotAlphaAnimation();
+ mDisplayAnimator = startDisplayRotation();
+ if (mExitingBlackFrame != null) {
mScreenshotRotationAnimator = startScreenshotRotationAnimation();
- startColorAnimation();
- } finally {
- mService.mSurfaceAnimationRunner.continueStartingAnimations();
+ }
+ if (mEnteringBlackFrame != null) {
+ mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
}
}
@@ -573,8 +596,8 @@
private SurfaceAnimator startScreenshotAlphaAnimation() {
return startAnimation(initializeBuilder()
- .setSurfaceControl(mScreenshotLayer)
- .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
+ .setSurfaceControl(mSurfaceControl)
+ .setAnimationLeashParent(mRotationLayer)
.setWidth(mWidth)
.setHeight(mHeight)
.build(),
@@ -593,67 +616,13 @@
private SurfaceAnimator startScreenshotRotationAnimation() {
return startAnimation(initializeBuilder()
- .setSurfaceControl(mScreenshotLayer)
+ .setSurfaceControl(mRotationLayer)
.setAnimationLeashParent(mDisplayContent.getOverlayLayer())
.build(),
createWindowAnimationSpec(mRotateExitAnimation),
this::onAnimationEnd);
}
-
- /**
- * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a
- * grayscale color
- */
- private void startColorAnimation() {
- int colorTransitionMs = mContext.getResources().getInteger(
- R.integer.config_screen_rotation_color_transition);
- final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner;
- final float[] rgbTmpFloat = new float[3];
- final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
- final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
- final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale();
- final ArgbEvaluator va = ArgbEvaluator.getInstance();
- runner.startAnimation(
- new LocalAnimationAdapter.AnimationSpec() {
- @Override
- public long getDuration() {
- return duration;
- }
-
- @Override
- public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
- long currentPlayTime) {
- float fraction = (float)currentPlayTime / (float)getDuration();
- int color = (Integer) va.evaluate(fraction, startColor, endColor);
- Color middleColor = Color.valueOf(color);
- rgbTmpFloat[0] = middleColor.red();
- rgbTmpFloat[1] = middleColor.green();
- rgbTmpFloat[2] = middleColor.blue();
- if (leash.isValid()) {
- t.setColor(leash, rgbTmpFloat);
- }
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "startLuma=" + mStartLuma
- + " endLuma=" + mEndLuma
- + " durationMs=" + colorTransitionMs);
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(ROTATE);
- proto.write(START_LUMA, mStartLuma);
- proto.write(END_LUMA, mEndLuma);
- proto.write(DURATION_MS, colorTransitionMs);
- proto.end(token);
- }
- },
- mBackColorSurface, mDisplayContent.getPendingTransaction(), null);
- }
-
private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) {
return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */,
false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */);
@@ -677,6 +646,7 @@
LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
animationSpec, mService.mSurfaceAnimationRunner);
+
animator.startAnimation(mDisplayContent.getPendingTransaction(),
localAnimationAdapter, false);
return animator;
@@ -722,6 +692,7 @@
if (mEnterBlackFrameAnimator != null) {
mEnterBlackFrameAnimator.cancelAnimation();
}
+
if (mScreenshotRotationAnimator != null) {
mScreenshotRotationAnimator.cancelAnimation();
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 5633b6b..50cea2e 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -78,10 +78,6 @@
@GuardedBy("mLock")
private boolean mAnimationStartDeferred;
- /**
- * There should only ever be one instance of this class. Usual spot for it is with
- * {@link WindowManagerService}
- */
SurfaceAnimationRunner(Supplier<Transaction> transactionFactory,
PowerManagerInternal powerManagerInternal) {
this(null /* callbackProvider */, null /* animatorFactory */,
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9a140da..5cb7091 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -117,9 +117,11 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -130,6 +132,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
+import android.view.ITaskOrganizer;
import android.view.RemoteAnimationTarget;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -425,6 +428,14 @@
}
/**
+ * The TaskOrganizer which is delegated presentation of this task. If set the Task will
+ * emit an IWindowContainer (allowing access to it's SurfaceControl leash) to the organizers
+ * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished.
+ */
+ ITaskOrganizer mTaskOrganizer;
+
+
+ /**
* Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int,
* ActivityInfo, Intent, TaskDescription)} instead.
*/
@@ -445,6 +456,22 @@
_voiceSession, _voiceInteractor, stack);
}
+ class TaskToken extends RemoteToken {
+ TaskToken(ConfigurationContainer container) {
+ super(container);
+ }
+
+ @Override
+ public SurfaceControl getLeash() {
+ // We need to copy the SurfaceControl instead of returning the original
+ // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls
+ // to release themselves.
+ SurfaceControl sc = new SurfaceControl();
+ sc.copyFrom(getSurfaceControl());
+ return sc;
+ }
+ }
+
/** Don't use constructor directly. This is only used by XML parser. */
Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
Intent _affinityIntent, String _affinity, String _rootAffinity,
@@ -469,7 +496,7 @@
mTaskDescription = _lastTaskDescription;
// Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
setOrientation(SCREEN_ORIENTATION_UNSET);
- mRemoteToken = new RemoteToken(this);
+ mRemoteToken = new TaskToken(this);
affinityIntent = _affinityIntent;
affinity = _affinity;
rootAffinity = _rootAffinity;
@@ -2179,6 +2206,10 @@
void removeImmediately() {
if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask");
+
+ // If applicable let the TaskOrganizer know the Task is vanishing.
+ setTaskOrganizer(null);
+
super.removeImmediately();
}
@@ -2567,6 +2598,12 @@
}
boolean shouldAnimate() {
+ /**
+ * Animations are handled by the TaskOrganizer implementation.
+ */
+ if (isControlledByTaskOrganizer()) {
+ return false;
+ }
// Don't animate while the task runs recents animation but only if we are in the mode
// where we cancel with deferred screenshot, which means that the controller has
// transformed the task.
@@ -3444,4 +3481,91 @@
XmlUtils.skipCurrentTag(in);
}
}
+
+ boolean isControlledByTaskOrganizer() {
+ return mTaskOrganizer != null;
+ }
+
+ @Override
+ protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) {
+ /**
+ * Avoid yanking back control from the TaskOrganizer, which has presumably reparented the
+ * Surface in to its own hierarchy.
+ */
+ if (isControlledByTaskOrganizer()) {
+ return;
+ }
+ super.reparentSurfaceControl(t, newParent);
+ }
+
+ private void sendTaskAppeared() {
+ if (mSurfaceControl != null && mTaskOrganizer != null) {
+ mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this);
+ }
+ }
+
+ private void sendTaskVanished() {
+ if (mTaskOrganizer != null) {
+ mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this);
+ }
+ }
+
+ void setTaskOrganizer(ITaskOrganizer organizer) {
+ // Let the old organizer know it has lost control.
+ if (mTaskOrganizer != null) {
+ sendTaskVanished();
+ }
+ mTaskOrganizer = organizer;
+ sendTaskAppeared();
+ }
+
+ // Called on Binder death.
+ void taskOrganizerDied() {
+ mTaskOrganizer = null;
+ }
+
+ @Override
+ void setSurfaceControl(SurfaceControl sc) {
+ super.setSurfaceControl(sc);
+ // If the TaskOrganizer was set before we created the SurfaceControl, we need to
+ // emit the callbacks now.
+ sendTaskAppeared();
+ }
+
+ @Override
+ public void updateSurfacePosition() {
+ // Avoid fighting with the TaskOrganizer over Surface position.
+ if (isControlledByTaskOrganizer()) {
+ getPendingTransaction().setPosition(mSurfaceControl, 0, 0);
+ scheduleAnimation();
+ return;
+ } else {
+ super.updateSurfacePosition();
+ }
+ }
+
+ @Override
+ void getRelativeDisplayedPosition(Point outPos) {
+ // In addition to updateSurfacePosition, we keep other code that sets
+ // position from fighting with the TaskOrganizer
+ if (isControlledByTaskOrganizer()) {
+ outPos.set(0, 0);
+ return;
+ }
+ super.getRelativeDisplayedPosition(outPos);
+ }
+
+ @Override
+ public void setWindowingMode(int windowingMode) {
+ super.setWindowingMode(windowingMode);
+ windowingMode = getWindowingMode();
+ /*
+ * Different windowing modes may be managed by different task organizers. If
+ * getTaskOrganizer returns null, we still call transferToTaskOrganizer to
+ * make sure we clear it.
+ */
+ final ITaskOrganizer org =
+ mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
+ setTaskOrganizer(org);
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
new file mode 100644
index 0000000..283be40
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.ITaskOrganizer;
+import android.view.SurfaceControl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Stores the TaskOrganizers associated with a given windowing mode and
+ * their associated state.
+ */
+class TaskOrganizerController {
+ private static final String TAG = "TaskOrganizerController";
+
+ private WindowManagerGlobalLock mGlobalLock;
+
+ private class DeathRecipient implements IBinder.DeathRecipient {
+ int mWindowingMode;
+ ITaskOrganizer mTaskOrganizer;
+
+ DeathRecipient(ITaskOrganizer organizer, int windowingMode) {
+ mTaskOrganizer = organizer;
+ mWindowingMode = windowingMode;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mGlobalLock) {
+ final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer);
+ for (int i = 0; i < state.mOrganizedTasks.size(); i++) {
+ state.mOrganizedTasks.get(i).taskOrganizerDied();
+ }
+ mTaskOrganizerStates.remove(mTaskOrganizer);
+ if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) {
+ mTaskOrganizersForWindowingMode.remove(mWindowingMode);
+ }
+ }
+ }
+ };
+
+ class TaskOrganizerState {
+ ITaskOrganizer mOrganizer;
+ DeathRecipient mDeathRecipient;
+
+ ArrayList<Task> mOrganizedTasks = new ArrayList<>();
+
+ void addTask(Task t) {
+ mOrganizedTasks.add(t);
+ }
+
+ void removeTask(Task t) {
+ mOrganizedTasks.remove(t);
+ }
+
+ TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) {
+ mOrganizer = organizer;
+ mDeathRecipient = deathRecipient;
+ }
+ };
+
+
+ final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap();
+ final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap();
+
+ final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap();
+
+ final ActivityTaskManagerService mService;
+
+ TaskOrganizerController(ActivityTaskManagerService atm, WindowManagerGlobalLock lock) {
+ mService = atm;
+ mGlobalLock = lock;
+ }
+
+ private void clearIfNeeded(int windowingMode) {
+ final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode);
+ if (oldState != null) {
+ oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0);
+ }
+ }
+
+ /**
+ * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
+ * If there was already a TaskOrganizer for this windowing mode it will be evicted
+ * and receive taskVanished callbacks in the process.
+ */
+ void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
+ if (windowingMode != WINDOWING_MODE_PINNED) {
+ throw new UnsupportedOperationException(
+ "As of now only Pinned windowing mode is supported for registerTaskOrganizer");
+
+ }
+ clearIfNeeded(windowingMode);
+ DeathRecipient dr = new DeathRecipient(organizer, windowingMode);
+ try {
+ organizer.asBinder().linkToDeath(dr, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "TaskOrganizer failed to register death recipient");
+ }
+
+ final TaskOrganizerState state = new TaskOrganizerState(organizer, dr);
+ mTaskOrganizersForWindowingMode.put(windowingMode, state);
+
+ mTaskOrganizerStates.put(organizer, state);
+ }
+
+ ITaskOrganizer getTaskOrganizer(int windowingMode) {
+ final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode);
+ if (state == null) {
+ return null;
+ }
+ return state.mOrganizer;
+ }
+
+ private void sendTaskAppeared(ITaskOrganizer organizer, Task task) {
+ try {
+ organizer.taskAppeared(task.getRemoteToken(), task.getTaskInfo());
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+ }
+ }
+
+ private void sendTaskVanished(ITaskOrganizer organizer, Task task) {
+ try {
+ organizer.taskVanished(task.getRemoteToken());
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception sending taskVanished callback" + e);
+ }
+ }
+
+ void onTaskAppeared(ITaskOrganizer organizer, Task task) {
+ TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
+
+ state.addTask(task);
+ sendTaskAppeared(organizer, task);
+ }
+
+ void onTaskVanished(ITaskOrganizer organizer, Task task) {
+ final TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
+ sendTaskVanished(organizer, task);
+
+ // This could trigger TaskAppeared for other tasks in the same stack so make sure
+ // we do this AFTER sending taskVanished.
+ state.removeTask(task);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index d36a5d4..38a7000 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -53,7 +53,7 @@
private static final String SNAPSHOTS_DIRNAME = "snapshots";
private static final String REDUCED_POSTFIX = "_reduced";
private static final float REDUCED_SCALE = .5f;
- private static final float LOW_RAM_REDUCED_SCALE = .6f;
+ private static final float LOW_RAM_REDUCED_SCALE = .8f;
static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
private static final long DELAY_MS = 100;
private static final int QUALITY = 95;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8ac212f..3b2d519 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -42,6 +42,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.logWithStack;
+import static com.android.server.wm.WindowManagerService.sHierarchicalAnimations;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
import android.annotation.CallSuper;
@@ -246,6 +247,8 @@
private MagnificationSpec mLastMagnificationSpec;
+ private boolean mIsFocusable = true;
+
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mPendingTransaction = wms.mTransactionFactory.get();
@@ -342,7 +345,7 @@
if (mSurfaceControl == null) {
// If we don't yet have a surface, but we now have a parent, we should
// build a surface.
- mSurfaceControl = makeSurface().build();
+ setSurfaceControl(makeSurface().build());
getPendingTransaction().show(mSurfaceControl);
updateSurfacePosition();
} else {
@@ -496,7 +499,7 @@
mParent.getPendingTransaction().merge(getPendingTransaction());
}
- mSurfaceControl = null;
+ setSurfaceControl(null);
mLastSurfacePosition.set(0, 0);
scheduleAnimation();
}
@@ -850,6 +853,21 @@
return false;
}
+ @Override
+ boolean isFocusable() {
+ return super.isFocusable() && mIsFocusable;
+ }
+
+ /** Set whether this container or its children can be focusable */
+ @Override
+ boolean setFocusable(boolean focusable) {
+ if (mIsFocusable == focusable) {
+ return false;
+ }
+ mIsFocusable = focusable;
+ return true;
+ }
+
/**
* @return Whether this child is on top of the window hierarchy.
*/
@@ -1855,7 +1873,7 @@
// TODO: Remove this and use #getBounds() instead once we set an app transition animation
// on TaskStack.
Rect getAnimationBounds(int appStackClipMode) {
- return getBounds();
+ return getDisplayedBounds();
}
/**
@@ -1929,7 +1947,11 @@
// Separate position and size for use in animators.
mTmpRect.set(getAnimationBounds(appStackClipMode));
- mTmpPoint.set(mTmpRect.left, mTmpRect.top);
+ if (sHierarchicalAnimations) {
+ getRelativeDisplayedPosition(mTmpPoint);
+ } else {
+ mTmpPoint.set(mTmpRect.left, mTmpRect.top);
+ }
mTmpRect.offsetTo(0, 0);
final RemoteAnimationController controller =
@@ -2209,4 +2231,8 @@
}
return mParent.getDimmer();
}
+
+ void setSurfaceControl(SurfaceControl sc) {
+ mSurfaceControl = sc;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 74fdba1..e3b593e9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4602,13 +4602,13 @@
if (newFocus != null) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Gaining focus: %s", newFocus);
- newFocus.reportFocusChangedSerialized(true, mInTouchMode);
+ newFocus.reportFocusChangedSerialized(true);
notifyFocusChanged();
}
if (lastFocus != null) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing focus: %s", lastFocus);
- lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
+ lastFocus.reportFocusChangedSerialized(false);
}
break;
}
@@ -4626,7 +4626,7 @@
for (int i = 0; i < N; i++) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing delayed focus: %s",
losers.get(i));
- losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
+ losers.get(i).reportFocusChangedSerialized(false);
}
break;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ba40f62..c2eb0e4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3339,11 +3339,7 @@
* Report a focus change. Must be called with no locks held, and consistently
* from the same serialized thread (such as dispatched from a handler).
*/
- void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
- try {
- mClient.windowFocusChanged(focused, inTouchMode);
- } catch (RemoteException e) {
- }
+ void reportFocusChangedSerialized(boolean focused) {
if (mFocusCallbacks != null) {
final int N = mFocusCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
diff --git a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
deleted file mode 100644
index 94f6676..0000000
--- a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
+++ /dev/null
@@ -1,97 +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.wm.utils;
-
-import android.graphics.Bitmap;
-import android.graphics.ColorSpace;
-import android.graphics.GraphicBuffer;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.Display;
-import android.view.Surface;
-import android.view.SurfaceControl;
-
-
-/** Helper functions for the {@link com.android.server.wm.ScreenRotationAnimation} class*/
-public class RotationAnimationUtils {
-
- /**
- * Converts the provided {@link GraphicBuffer} and converts it to a bitmap to then sample the
- * luminance at the borders of the bitmap
- * @return the average luminance of all the pixels at the borders of the bitmap
- */
- public static float getAvgBorderLuma(GraphicBuffer graphicBuffer, ColorSpace colorSpace) {
- Bitmap hwBitmap = Bitmap.wrapHardwareBuffer(graphicBuffer, colorSpace);
- if (hwBitmap == null) {
- return 0;
- }
-
- Bitmap swaBitmap = hwBitmap.copy(Bitmap.Config.ARGB_8888, false);
- float totalLuma = 0;
- int height = swaBitmap.getHeight();
- int width = swaBitmap.getWidth();
- int i;
- for (i = 0; i < width; i++) {
- totalLuma += swaBitmap.getColor(i, 0).luminance();
- totalLuma += swaBitmap.getColor(i, height - 1).luminance();
- }
- for (i = 0; i < height; i++) {
- totalLuma += swaBitmap.getColor(0, i).luminance();
- totalLuma += swaBitmap.getColor(width - 1, i).luminance();
- }
- return totalLuma / (2 * width + 2 * height);
- }
-
- /**
- * Gets the average border luma by taking a screenshot of the {@param surfaceControl}.
- * @see #getAvgBorderLuma(GraphicBuffer, ColorSpace)
- */
- public static float getLumaOfSurfaceControl(Display display, SurfaceControl surfaceControl) {
- if (surfaceControl == null) {
- return 0;
- }
-
- Point size = new Point();
- display.getSize(size);
- Rect crop = new Rect(0, 0, size.x, size.y);
- SurfaceControl.ScreenshotGraphicBuffer buffer =
- SurfaceControl.captureLayers(surfaceControl, crop, 1);
- return RotationAnimationUtils.getAvgBorderLuma(buffer.getGraphicBuffer(),
- buffer.getColorSpace());
- }
-
- public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) {
- switch (rotation) {
- case Surface.ROTATION_0:
- outMatrix.reset();
- break;
- case Surface.ROTATION_90:
- outMatrix.setRotate(90, 0, 0);
- outMatrix.postTranslate(height, 0);
- break;
- case Surface.ROTATION_180:
- outMatrix.setRotate(180, 0, 0);
- outMatrix.postTranslate(width, height);
- break;
- case Surface.ROTATION_270:
- outMatrix.setRotate(270, 0, 0);
- outMatrix.postTranslate(0, width);
- break;
- }
- }
-}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 1ad6e86..77d814e 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -13,6 +13,7 @@
],
srcs: [
+ ":graphicsstats_proto",
"BroadcastRadio/JavaRef.cpp",
"BroadcastRadio/NativeCallbackThread.cpp",
"BroadcastRadio/BroadcastRadioService.cpp",
@@ -50,7 +51,7 @@
"com_android_server_VibratorService.cpp",
"com_android_server_PersistentDataBlockService.cpp",
"com_android_server_GraphicsStatsService.cpp",
- "com_android_server_am_AppCompactor.cpp",
+ "com_android_server_am_CachedAppOptimizer.cpp",
"com_android_server_am_LowMemDetector.cpp",
"com_android_server_incremental_IncrementalManagerService.cpp",
"onload.cpp",
@@ -103,6 +104,11 @@
"libinputflinger",
"libinputflinger_base",
"libinputservice",
+ "libprotobuf-cpp-lite",
+ "libprotoutil",
+ "libstatspull",
+ "libstatssocket",
+ "libstatslog",
"libschedulerservicehidl",
"libsensorservice",
"libsensorservicehidl",
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
index d1d253b..9353fbd 100644
--- a/services/core/jni/com_android_server_GraphicsStatsService.cpp
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -23,6 +23,16 @@
#include <nativehelper/ScopedUtfChars.h>
#include <JankTracker.h>
#include <service/GraphicsStatsService.h>
+#include <stats_pull_atom_callback.h>
+#include <stats_event.h>
+#include <statslog.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <android/util/ProtoOutputStream.h>
+#include "android/graphics/Utils.h"
+#include "core_jni_helpers.h"
+#include "protos/graphicsstats.pb.h"
+#include <cstring>
+#include <memory>
namespace android {
@@ -77,6 +87,20 @@
GraphicsStatsService::finishDump(dump);
}
+static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) {
+ GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+ std::vector<uint8_t>* result = new std::vector<uint8_t>();
+ GraphicsStatsService::finishDumpInMemory(dump,
+ [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) {
+ std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2);
+ if (outBuffer->size() < totalSize) {
+ outBuffer->resize(totalSize);
+ }
+ std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize);
+ }, env, result);
+ return reinterpret_cast<jlong>(result);
+}
+
static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
ScopedByteArrayRO buffer(env, jdata);
@@ -93,19 +117,173 @@
GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
}
+static jobject gGraphicsStatsServiceObject = nullptr;
+static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID;
+
+static JNIEnv* getJNIEnv() {
+ JavaVM* vm = AndroidRuntime::getJavaVM();
+ JNIEnv* env = nullptr;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
+ }
+ }
+ return env;
+}
+
+using namespace google::protobuf;
+
+// Field ids taken from FrameTimingHistogram message in atoms.proto
+#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
+#define FRAME_COUNTS_FIELD_NUMBER 2
+
+static void writeCpuHistogram(stats_event* event,
+ const uirenderer::protos::GraphicsStatsProto& stat) {
+ util::ProtoOutputStream proto;
+ for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+ auto& bucket = stat.histogram(bucketIndex);
+ proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+ TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+ (int)bucket.render_millis());
+ }
+ for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+ auto& bucket = stat.histogram(bucketIndex);
+ proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+ FRAME_COUNTS_FIELD_NUMBER /* field id */,
+ (long long)bucket.frame_count());
+ }
+ std::vector<uint8_t> outVector;
+ proto.serializeToVector(&outVector);
+ stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+static void writeGpuHistogram(stats_event* event,
+ const uirenderer::protos::GraphicsStatsProto& stat) {
+ util::ProtoOutputStream proto;
+ for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+ auto& bucket = stat.gpu_histogram(bucketIndex);
+ proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+ TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+ (int)bucket.render_millis());
+ }
+ for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+ auto& bucket = stat.gpu_histogram(bucketIndex);
+ proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+ FRAME_COUNTS_FIELD_NUMBER /* field id */,
+ (long long)bucket.frame_count());
+ }
+ std::vector<uint8_t> outVector;
+ proto.serializeToVector(&outVector);
+ stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom.
+static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* data,
+ const void* cookie) {
+ JNIEnv* env = getJNIEnv();
+ if (!env) {
+ return false;
+ }
+ if (gGraphicsStatsServiceObject == nullptr) {
+ ALOGE("Failed to get graphicsstats service");
+ return false;
+ }
+
+ for (bool lastFullDay : {true, false}) {
+ jlong jdata = (jlong) env->CallLongMethod(
+ gGraphicsStatsServiceObject,
+ gGraphicsStatsService_pullGraphicsStatsMethodID,
+ (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE));
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ ALOGE("Failed to invoke graphicsstats service");
+ return false;
+ }
+ if (!jdata) {
+ // null means data is not available for that day.
+ continue;
+ }
+ android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump;
+ std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata);
+ std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer);
+ int dataSize = buffer->size();
+ if (!dataSize) {
+ // Data is not available for that day.
+ continue;
+ }
+ io::ArrayInputStream input{buffer->data(), dataSize};
+ bool success = serviceDump.ParseFromZeroCopyStream(&input);
+ if (!success) {
+ ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'",
+ serviceDump.InitializationErrorString().c_str(), dataSize);
+ return false;
+ }
+
+ for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
+ auto& stat = serviceDump.stats(stat_index);
+ stats_event* event = add_stats_event_to_pull_data(data);
+ stats_event_set_atom_id(event, android::util::GRAPHICS_STATS);
+ stats_event_write_string8(event, stat.package_name().c_str());
+ stats_event_write_int64(event, (int64_t)stat.version_code());
+ stats_event_write_int64(event, (int64_t)stat.stats_start());
+ stats_event_write_int64(event, (int64_t)stat.stats_end());
+ stats_event_write_int32(event, (int32_t)stat.pipeline());
+ stats_event_write_int32(event, (int32_t)stat.summary().total_frames());
+ stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count());
+ stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count());
+ stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count());
+ stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
+ stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count());
+ stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count());
+ writeCpuHistogram(event, stat);
+ writeGpuHistogram(event, stat);
+ // TODO: fill in UI mainline module version, when the feature is available.
+ stats_event_write_int64(event, (int64_t)0);
+ stats_event_write_bool(event, !lastFullDay);
+ stats_event_build(event);
+ }
+ }
+ return true;
+}
+
+// Register a puller for GRAPHICS_STATS atom with the statsd service.
+static void nativeInit(JNIEnv* env, jobject javaObject) {
+ gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject);
+ pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds
+ .timeout_ns = 2 * NS_PER_SEC, // 2 seconds
+ .additive_fields = nullptr,
+ .additive_fields_size = 0};
+ register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback,
+ &metadata, nullptr);
+}
+
+static void nativeDestructor(JNIEnv* env, jobject javaObject) {
+ //TODO: Unregister the puller callback when a new API is available.
+ env->DeleteGlobalRef(gGraphicsStatsServiceObject);
+ gGraphicsStatsServiceObject = nullptr;
+}
+
static const JNINativeMethod sMethods[] = {
{ "nGetAshmemSize", "()I", (void*) getAshmemSize },
{ "nCreateDump", "(IZ)J", (void*) createDump },
{ "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump },
{ "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump },
{ "nFinishDump", "(J)V", (void*) finishDump },
+ { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory },
{ "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer },
+ { "nativeInit", "()V", (void*) nativeInit },
+ { "nativeDestructor", "()V", (void*)nativeDestructor }
};
int register_android_server_GraphicsStatsService(JNIEnv* env)
{
+ jclass graphicsStatsService_class = FindClassOrDie(env,
+ "com/android/server/GraphicsStatsService");
+ gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env,
+ graphicsStatsService_class, "pullGraphicsStats", "(Z)J");
return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService",
sMethods, NELEM(sMethods));
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/core/jni/com_android_server_am_AppCompactor.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
similarity index 89%
rename from services/core/jni/com_android_server_am_AppCompactor.cpp
rename to services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index de6aa8b..6a6da0e 100644
--- a/services/core/jni/com_android_server_am_AppCompactor.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "AppCompactor"
+#define LOG_TAG "CachedAppOptimizer"
//#define LOG_NDEBUG 0
#include <dirent.h>
@@ -42,7 +42,7 @@
// or potentially some mainline modules. The only process that should definitely
// not be compacted is system_server, since compacting system_server around the
// time of BOOT_COMPLETE could result in perceptible issues.
-static void com_android_server_am_AppCompactor_compactSystem(JNIEnv *, jobject) {
+static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, jobject) {
std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
struct dirent* current;
while ((current = readdir(proc.get()))) {
@@ -76,12 +76,12 @@
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
- {"compactSystem", "()V", (void*)com_android_server_am_AppCompactor_compactSystem},
+ {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
};
-int register_android_server_am_AppCompactor(JNIEnv* env)
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
{
- return jniRegisterNativeMethods(env, "com/android/server/am/AppCompactor",
+ return jniRegisterNativeMethods(env, "com/android/server/am/CachedAppOptimizer",
sMethods, NELEM(sMethods));
}
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 6504e31..00436bb 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -129,7 +129,6 @@
using android::hardware::hidl_string;
using android::hardware::hidl_death_recipient;
-using android::hardware::gnss::V1_0::GnssConstellationType;
using android::hardware::gnss::V1_0::GnssLocationFlags;
using android::hardware::gnss::V1_0::IAGnssRilCallback;
using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
@@ -149,6 +148,8 @@
using android::hidl::base::V1_0::IBase;
+using GnssConstellationType_V1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
+using GnssConstellationType_V2_0 = android::hardware::gnss::V2_0::GnssConstellationType;
using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation;
using GnssLocation_V2_0 = android::hardware::gnss::V2_0::GnssLocation;
using IGnss_V1_0 = android::hardware::gnss::V1_0::IGnss;
@@ -161,6 +162,7 @@
using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration;
using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration;
using IGnssConfiguration_V2_0 = android::hardware::gnss::V2_0::IGnssConfiguration;
+using IGnssConfiguration_V2_1 = android::hardware::gnss::V2_1::IGnssConfiguration;
using IGnssDebug_V1_0 = android::hardware::gnss::V1_0::IGnssDebug;
using IGnssDebug_V2_0 = android::hardware::gnss::V2_0::IGnssDebug;
using IGnssMeasurement_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurement;
@@ -202,6 +204,7 @@
// Must match the value from GnssMeasurement.java
static const uint32_t ADR_STATE_HALF_CYCLE_REPORTED = (1<<4);
+static const uint32_t SVID_FLAGS_HAS_BASEBAND_CN0 = (1<<4);
sp<GnssDeathRecipient> gnssHalDeathRecipient = nullptr;
sp<IGnss_V1_0> gnssHal = nullptr;
@@ -221,6 +224,7 @@
sp<IGnssConfiguration_V1_0> gnssConfigurationIface = nullptr;
sp<IGnssConfiguration_V1_1> gnssConfigurationIface_V1_1 = nullptr;
sp<IGnssConfiguration_V2_0> gnssConfigurationIface_V2_0 = nullptr;
+sp<IGnssConfiguration_V2_1> gnssConfigurationIface_V2_1 = nullptr;
sp<IGnssNi> gnssNiIface = nullptr;
sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr;
sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr;
@@ -631,6 +635,16 @@
template<class T>
Return<void> gnssSvStatusCbImpl(const T& svStatus);
+ template<class T>
+ uint32_t getHasBasebandCn0DbHzFlag(const T& svStatus) {
+ return 0;
+ }
+
+ template<class T>
+ double getBasebandCn0DbHz(const T& svStatus, size_t i) {
+ return 0.0;
+ }
+
uint32_t getGnssSvInfoListSize(const IGnssCallback_V1_0::GnssSvStatus& svStatus) {
return svStatus.numSvs;
}
@@ -655,8 +669,6 @@
const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex(
const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) {
- // TODO(b/144850155): fill baseband CN0 after it's available in Java object.
- ALOGD("getGnssSvInfoOfIndex %d: baseband C/N0: %f", (int) i, svInfoList[i].basebandCN0DbHz);
return svInfoList[i].v2_0.v1_0;
}
@@ -718,6 +730,18 @@
return Void();
}
+template<>
+uint32_t GnssCallback::getHasBasebandCn0DbHzFlag(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>&
+ svStatus) {
+ return SVID_FLAGS_HAS_BASEBAND_CN0;
+}
+
+template<>
+double GnssCallback::getBasebandCn0DbHz(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList,
+ size_t i) {
+ return svInfoList[i].basebandCN0DbHz;
+}
+
template<class T>
Return<void> GnssCallback::gnssSvStatusCbImpl(const T& svStatus) {
JNIEnv* env = getJniEnv();
@@ -755,8 +779,8 @@
elev[i] = info.elevationDegrees;
azim[i] = info.azimuthDegrees;
carrierFreq[i] = info.carrierFrequencyHz;
- // TODO(b/144850155): fill svidWithFlags with hasBasebandCn0DbHz based on HAL versions
- basebandCn0s[i] = 0.0;
+ svidWithFlags[i] |= getHasBasebandCn0DbHzFlag(svStatus);
+ basebandCn0s[i] = getBasebandCn0DbHz(svStatus, i);
}
env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0);
@@ -1182,8 +1206,8 @@
const IGnssMeasurementCallback_V2_1::GnssMeasurement* measurement_V2_1,
JavaObject& object) {
translateSingleGnssMeasurement(&(measurement_V2_1->v2_0), object);
- // TODO(b/144850155): fill baseband CN0 after it's available in Java object
- ALOGD("baseband CN0DbHz = %f\n", measurement_V2_1->basebandCN0DbHz);
+
+ SET(BasebandCn0DbHz, measurement_V2_1->basebandCN0DbHz);
}
template<class T>
@@ -1888,7 +1912,17 @@
gnssNiIface = gnssNi;
}
- if (gnssHal_V2_0 != nullptr) {
+ if (gnssHal_V2_1 != nullptr) {
+ auto gnssConfiguration = gnssHal_V2_1->getExtensionGnssConfiguration_2_1();
+ if (!gnssConfiguration.isOk()) {
+ ALOGD("Unable to get a handle to GnssConfiguration_V2_1");
+ } else {
+ gnssConfigurationIface_V2_1 = gnssConfiguration;
+ gnssConfigurationIface_V2_0 = gnssConfigurationIface_V2_1;
+ gnssConfigurationIface_V1_1 = gnssConfigurationIface_V2_1;
+ gnssConfigurationIface = gnssConfigurationIface_V2_1;
+ }
+ } else if (gnssHal_V2_0 != nullptr) {
auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0();
if (!gnssConfiguration.isOk()) {
ALOGD("Unable to get a handle to GnssConfiguration_V2_0");
@@ -1962,7 +1996,11 @@
static jobject android_location_GnssConfiguration_get_gnss_configuration_version(
JNIEnv* env, jclass /* jclazz */) {
jint major, minor;
- if (gnssConfigurationIface_V2_0 != nullptr) {
+ if (gnssConfigurationIface_V2_1 != nullptr) {
+ major = 2;
+ minor = 1;
+ }
+ else if (gnssConfigurationIface_V2_0 != nullptr) {
major = 2;
minor = 0;
} else if (gnssConfigurationIface_V1_1 != nullptr) {
@@ -2768,7 +2806,7 @@
SingleSatCorrection singleSatCorrection = {
.singleSatCorrectionFlags = corrFlags,
- .constellation = static_cast<GnssConstellationType>(constType),
+ .constellation = static_cast<GnssConstellationType_V1_0>(constType),
.svid = static_cast<uint16_t>(satId),
.carrierFrequencyHz = carrierFreqHz,
.probSatIsLos = probSatIsLos,
@@ -2863,8 +2901,8 @@
static jboolean android_location_GnssConfiguration_set_supl_es(JNIEnv*,
jobject,
jint suplEs) {
- if (gnssConfigurationIface_V2_0 != nullptr) {
- ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0.");
+ if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) {
+ ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0 and higher.");
return JNI_FALSE;
}
@@ -2892,7 +2930,7 @@
static jboolean android_location_GnssConfiguration_set_gps_lock(JNIEnv*,
jobject,
jint gpsLock) {
- if (gnssConfigurationIface_V2_0 != nullptr) {
+ if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) {
ALOGI("Config parameter GPS_LOCK is deprecated in IGnssConfiguration.hal version 2.0.");
return JNI_FALSE;
}
@@ -2932,7 +2970,7 @@
static jboolean android_location_GnssConfiguration_set_satellite_blacklist(
JNIEnv* env, jobject, jintArray constellations, jintArray sv_ids) {
- if (gnssConfigurationIface_V1_1 == nullptr) {
+ if (gnssConfigurationIface_V1_1 == nullptr && gnssConfigurationIface_V2_1 == nullptr) {
ALOGI("IGnssConfiguration interface does not support satellite blacklist.");
return JNI_FALSE;
}
@@ -2955,11 +2993,24 @@
return JNI_FALSE;
}
+ if (gnssConfigurationIface_V2_1 != nullptr) {
+ hidl_vec<IGnssConfiguration_V2_1::BlacklistedSource> sources;
+ sources.resize(length);
+
+ for (int i = 0; i < length; i++) {
+ sources[i].constellation = static_cast<GnssConstellationType_V2_0>(constellation_array[i]);
+ sources[i].svid = sv_id_array[i];
+ }
+
+ auto result = gnssConfigurationIface_V2_1->setBlacklist_2_1(sources);
+ return checkHidlReturn(result, "IGnssConfiguration_V2_1 setBlacklist_2_1() failed.");
+ }
+
hidl_vec<IGnssConfiguration_V1_1::BlacklistedSource> sources;
sources.resize(length);
for (int i = 0; i < length; i++) {
- sources[i].constellation = static_cast<GnssConstellationType>(constellation_array[i]);
+ sources[i].constellation = static_cast<GnssConstellationType_V1_0>(constellation_array[i]);
sources[i].svid = sv_id_array[i];
}
diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
index 4696dd0..0275f3e 100644
--- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -33,7 +33,6 @@
#include "bpf/BpfUtils.h"
#include "netdbpf/BpfNetworkStats.h"
-using android::bpf::Stats;
using android::bpf::bpfGetUidStats;
using android::bpf::bpfGetIfaceStats;
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index c0a6e4e..19fa062 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -54,7 +54,7 @@
int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
int register_android_server_net_NetworkStatsService(JNIEnv* env);
int register_android_server_security_VerityUtils(JNIEnv* env);
-int register_android_server_am_AppCompactor(JNIEnv* env);
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
JNIEnv* env);
@@ -106,7 +106,7 @@
register_android_server_net_NetworkStatsFactory(env);
register_android_server_net_NetworkStatsService(env);
register_android_server_security_VerityUtils(env);
- register_android_server_am_AppCompactor(env);
+ register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_LowMemDetector(env);
register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d5ff280..7fdb4c4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23,7 +23,6 @@
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
-import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED;
import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE;
import static android.app.admin.DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.CODE_HAS_DEVICE_OWNER;
@@ -129,6 +128,7 @@
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DeviceStateCache;
+import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.NetworkEvent;
import android.app.admin.PasswordMetrics;
import android.app.admin.PasswordPolicy;
@@ -268,6 +268,7 @@
import com.android.internal.widget.PasswordValidationError;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
+import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
@@ -1006,6 +1007,8 @@
private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
"cross-profile-calendar-packages-null";
private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages";
+ private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+ "factory_reset_protection_policy";
DeviceAdminInfo info;
@@ -1016,6 +1019,9 @@
@NonNull
PasswordPolicy mPasswordPolicy = new PasswordPolicy();
+ @Nullable
+ FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null;
+
static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
@@ -1351,6 +1357,11 @@
mCrossProfileCalendarPackages);
}
writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
+ if (mFactoryResetProtectionPolicy != null) {
+ out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ mFactoryResetProtectionPolicy.writeToXml(out);
+ out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ }
}
void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
@@ -1584,6 +1595,9 @@
mCrossProfileCalendarPackages = null;
} else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
mCrossProfilePackages = readPackageList(parser, tag);
+ } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) {
+ mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml(
+ parser);
} else {
Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -2036,6 +2050,10 @@
return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));
}
+ PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+ return LocalServices.getService(PersistentDataBlockManagerInternal.class);
+ }
+
LockSettingsInternal getLockSettingsInternal() {
return LocalServices.getService(LockSettingsInternal.class);
}
@@ -4099,6 +4117,12 @@
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) {
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userHandle);
}
+ // When a device owner is set, the system automatically restricts adding a managed profile.
+ // Remove this restriction when the device owner is cleared.
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, userHandle)) {
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false,
+ userHandle);
+ }
}
/**
@@ -6736,6 +6760,67 @@
}
@Override
+ public void setFactoryResetProtectionPolicy(ComponentName who,
+ @Nullable FactoryResetProtectionPolicy policy) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+ final int userId = mInjector.userHandleGetCallingUserId();
+ synchronized (getLockObject()) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ admin.mFactoryResetProtectionPolicy = policy;
+ saveSettingsLocked(userId);
+ }
+
+ mInjector.binderWithCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(
+ new Intent(DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ UserHandle.getUserHandleForUid(frpManagementAgentUid)));
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_FACTORY_RESET_PROTECTION)
+ .setAdmin(who)
+ .write();
+ }
+
+ @Override
+ public FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+ @Nullable ComponentName who) {
+ if (!mHasFeature) {
+ return null;
+ }
+
+ final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+ ActiveAdmin admin;
+ synchronized (getLockObject()) {
+ if (who == null) {
+ if ((frpManagementAgentUid != mInjector.binderGetCallingUid())) {
+ throw new SecurityException(
+ "Must be called by the FRP management agent on device");
+ }
+ admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+ UserHandle.getUserId(frpManagementAgentUid));
+ } else {
+ admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ }
+ }
+ return admin != null ? admin.mFactoryResetProtectionPolicy : null;
+ }
+
+ private int getFrpManagementAgentUidOrThrow() {
+ PersistentDataBlockManagerInternal pdb = mInjector.getPersistentDataBlockManagerInternal();
+ if ((pdb == null) || (pdb.getAllowedUid() == -1)) {
+ throw new UnsupportedOperationException(
+ "The persistent data block service is not supported on this device");
+ }
+ return pdb.getAllowedUid();
+ }
+
+ @Override
public void getRemoveWarning(ComponentName comp, final RemoteCallback result, int userHandle) {
if (!mHasFeature) {
return;
@@ -7976,10 +8061,19 @@
updateDeviceOwnerLocked();
setDeviceOwnerSystemPropertyLocked();
- // TODO Send to system too?
- mInjector.binderWithCleanCallingIdentity(
- () -> sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED,
- userId));
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ // Restrict adding a managed profile when a device owner is set on the device.
+ // That is to prevent the co-existence of a managed profile and a device owner
+ // on the same device.
+ // Instead, the device may be provisioned with an organization-owned managed
+ // profile, such that the admin on that managed profile has extended management
+ // capabilities that can affect the entire device (but not access private data
+ // on the primary profile).
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
+ UserHandle.of(userId));
+ // TODO Send to system too?
+ sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
+ });
mDeviceAdminServiceController.startServiceForOwner(
admin.getPackageName(), userId, "set-device-owner");
@@ -8131,6 +8225,14 @@
return null;
}
+ ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(int userId) {
+ ActiveAdmin admin = getDeviceOwnerAdminLocked();
+ if (admin == null) {
+ admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
+ }
+ return admin;
+ }
+
@Override
public void clearDeviceOwner(String packageName) {
Objects.requireNonNull(packageName, "packageName is null");
@@ -8234,6 +8336,17 @@
throw new IllegalArgumentException("Not active admin: " + who);
}
+ final int parentUserId = getProfileParentId(userHandle);
+ // When trying to set a profile owner on a new user, it may be that this user is
+ // a profile - but it may not be a managed profile if there's a restriction on the
+ // parent to add managed profiles (e.g. if the device has a device owner).
+ if (parentUserId != userHandle && mUserManager.hasUserRestriction(
+ UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+ UserHandle.of(parentUserId))) {
+ Slog.i(LOG_TAG, "Cannot set profile owner because of restriction.");
+ return false;
+ }
+
if (isAdb()) {
// Log profile owner provisioning was started using adb.
MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_PROFILE_OWNER);
@@ -8611,7 +8724,6 @@
if (!mHasFeature) {
return false;
}
- enforceManageUsers();
return mInjector.binderWithCleanCallingIdentity(() -> {
for (UserInfo ui : mUserManager.getUsers()) {
@@ -12212,6 +12324,7 @@
case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
return checkManagedProfileProvisioningPreCondition(packageName, callingUserId);
case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
+ case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
return checkDeviceOwnerProvisioningPreCondition(callingUserId);
case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
return checkManagedUserProvisioningPreCondition(callingUserId);
@@ -12298,25 +12411,41 @@
final long ident = mInjector.binderClearCallingIdentity();
try {
final UserHandle callingUserHandle = UserHandle.of(callingUserId);
- final ComponentName ownerAdmin = getOwnerComponent(packageName, callingUserId);
- if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
- callingUserHandle)) {
- // An admin can initiate provisioning if it has set the restriction.
- if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
- UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) {
- return CODE_ADD_MANAGED_PROFILE_DISALLOWED;
- }
+ final boolean hasDeviceOwner;
+ synchronized (getLockObject()) {
+ hasDeviceOwner = getDeviceOwnerAdminLocked() != null;
}
- boolean canRemoveProfile = true;
- if (mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
- callingUserHandle)) {
- // We can remove a profile if the admin itself has set the restriction.
- if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
- UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
- callingUserId)) {
- canRemoveProfile = false;
- }
+
+ final boolean addingProfileRestricted = mUserManager.hasUserRestriction(
+ UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle);
+
+ UserInfo parentUser = mUserManager.getProfileParent(callingUserId);
+ final boolean addingProfileRestrictedOnParent = (parentUser != null)
+ && mUserManager.hasUserRestriction(
+ UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+ UserHandle.of(parentUser.id));
+
+ Slog.i(LOG_TAG, String.format(
+ "When checking for managed profile provisioning: Has device owner? %b, adding"
+ + " profile restricted? %b, adding profile restricted on parent? %b",
+ hasDeviceOwner, addingProfileRestricted, addingProfileRestrictedOnParent));
+
+ // If there's a device owner, the restriction on adding a managed profile must be set
+ // somewhere.
+ if (hasDeviceOwner && !addingProfileRestricted && !addingProfileRestrictedOnParent) {
+ Slog.wtf(LOG_TAG, "Has a device owner but no restriction on adding a profile.");
}
+
+ // Do not allow adding a managed profile if there's a restriction, either on the current
+ // user or its parent user.
+ if (addingProfileRestricted || addingProfileRestrictedOnParent) {
+ return CODE_CANNOT_ADD_MANAGED_PROFILE;
+ }
+ // If there's a restriction on removing the managed profile then we have to take it
+ // into account when checking whether more profiles can be added.
+ boolean canRemoveProfile =
+ !mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ callingUserHandle);
if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) {
return CODE_CANNOT_ADD_MANAGED_PROFILE;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b6a8ca4..0bd7392 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -71,11 +71,11 @@
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Slog;
+import android.util.StatsLog;
import android.view.WindowManager;
import android.view.contentcapture.ContentCaptureManager;
import com.android.internal.R;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
import com.android.internal.util.ConcurrentUtils;
@@ -213,6 +213,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_APEX_PATH =
+ "/apex/com.android.os.statsd/javalib/service-statsd.jar";
private static final String STATS_COMPANION_LIFECYCLE_CLASS =
"com.android.server.stats.StatsCompanion$Lifecycle";
private static final String STATS_PULL_ATOM_SERVICE_CLASS =
@@ -441,10 +443,12 @@
// Here we go!
Slog.i(TAG, "Entered the Android system server!");
- int uptimeMillis = (int) SystemClock.elapsedRealtime();
+ final long uptimeMillis = SystemClock.elapsedRealtime();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
if (!mRuntimeRestart) {
- MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis);
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_INIT_START,
+ uptimeMillis);
}
// In case the runtime switched since last boot (such as when
@@ -553,10 +557,12 @@
StrictMode.initVmDefaults(null);
if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
- int uptimeMillis = (int) SystemClock.elapsedRealtime();
- MetricsLogger.histogram(null, "boot_system_server_ready", uptimeMillis);
- final int MAX_UPTIME_MILLIS = 60 * 1000;
- if (uptimeMillis > MAX_UPTIME_MILLIS) {
+ final long uptimeMillis = SystemClock.elapsedRealtime();
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_READY,
+ uptimeMillis);
+ final long maxUptimeMillis = 60 * 1000;
+ if (uptimeMillis > maxUptimeMillis) {
Slog.wtf(SYSTEM_SERVER_TIMING_TAG,
"SystemServer init took too long. uptimeMillis=" + uptimeMillis);
}
@@ -789,8 +795,9 @@
// Start the package manager.
if (!mRuntimeRestart) {
- MetricsLogger.histogram(null, "boot_package_manager_init_start",
- (int) SystemClock.elapsedRealtime());
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_START,
+ SystemClock.elapsedRealtime());
}
t.traceBegin("StartPackageManagerService");
@@ -806,8 +813,9 @@
mPackageManager = mSystemContext.getPackageManager();
t.traceEnd();
if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
- MetricsLogger.histogram(null, "boot_package_manager_init_ready",
- (int) SystemClock.elapsedRealtime());
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_READY,
+ SystemClock.elapsedRealtime());
}
// Manages A/B OTA dexopting. This is a bootstrap service as we need it to rename
// A/B artifacts after boot, before anything else might touch/need them.
@@ -1980,7 +1988,8 @@
// Statsd helper
t.traceBegin("StartStatsCompanion");
- mSystemServiceManager.startService(STATS_COMPANION_LIFECYCLE_CLASS);
+ mSystemServiceManager.startServiceFromJar(
+ STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH);
t.traceEnd();
// Statsd pulled atoms
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 9c7cfc1..cf84bdf 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -23,7 +23,6 @@
name: "services-tethering-shared-srcs",
srcs: [
":framework-annotations",
- "java/android/net/util/NetdService.java",
"java/android/net/util/NetworkConstants.java",
],
visibility: ["//frameworks/base/packages/Tethering"],
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index ef3da60..9569c6e 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -137,5 +137,14 @@
Slog.e(TAG, "Failed to calling callback" + e);
}
}
+
+ @Override
+ public byte[] backupConversationInfos(int userId) {
+ return new byte[0];
+ }
+
+ @Override
+ public void restoreConversationInfos(int userId, String key, byte[] payload) {
+ }
}
}
diff --git a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
index a8a258f..9c5d4ad 100644
--- a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
+++ b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
@@ -3,6 +3,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import android.os.Looper;
import android.os.SystemClock;
@@ -52,8 +53,10 @@
@Test
public void handleInjectNtpTime_cachedAgeLow_injectTime() throws InterruptedException {
- doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(mMockNtpTrustedTime).getCacheAge();
- doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();
+ NtpTrustedTime.TimeResult result = mock(NtpTrustedTime.TimeResult.class);
+ doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(result).getAgeMillis();
+ doReturn(MOCK_NTP_TIME).when(result).getTimeMillis();
+ doReturn(result).when(mMockNtpTrustedTime).getCachedTimeResult();
mNtpTimeHelper.retrieveAndInjectNtpTime();
@@ -64,7 +67,9 @@
@Test
public void handleInjectNtpTime_injectTimeFailed_injectTimeDelayed()
throws InterruptedException {
- doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(mMockNtpTrustedTime).getCacheAge();
+ NtpTrustedTime.TimeResult result1 = mock(NtpTrustedTime.TimeResult.class);
+ doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(result1).getAgeMillis();
+ doReturn(result1).when(mMockNtpTrustedTime).getCachedTimeResult();
doReturn(false).when(mMockNtpTrustedTime).forceRefresh();
mNtpTimeHelper.retrieveAndInjectNtpTime();
@@ -72,8 +77,10 @@
assertThat(mCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse();
doReturn(true).when(mMockNtpTrustedTime).forceRefresh();
- doReturn(1L).when(mMockNtpTrustedTime).getCacheAge();
- doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();
+ NtpTrustedTime.TimeResult result2 = mock(NtpTrustedTime.TimeResult.class);
+ doReturn(1L).when(result2).getAgeMillis();
+ doReturn(MOCK_NTP_TIME).when(result2).getTimeMillis();
+ doReturn(result2).when(mMockNtpTrustedTime).getCachedTimeResult();
SystemClock.sleep(NtpTimeHelper.RETRY_INTERVAL);
waitForTasksToBePostedOnHandlerAndRunThem();
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 556f96a..6a5de84 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -345,8 +345,8 @@
}
/**
- * Lowers quotas to make testing feasible.
- * Careful while calling as this will replace any existing settings for the calling test.
+ * Lowers quotas to make testing feasible. Careful while calling as this will replace any
+ * existing settings for the calling test.
*/
private void setTestableQuotas() {
final StringBuilder constantsBuilder = new StringBuilder();
@@ -981,6 +981,25 @@
assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
}
+ @Test
+ public void alarmCountOnListenerBinderDied() {
+ final int numAlarms = 10;
+ final IAlarmListener[] listeners = new IAlarmListener[numAlarms];
+ for (int i = 0; i < numAlarms; i++) {
+ listeners[i] = new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+ }
+ };
+ setTestAlarmWithListener(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + i, listeners[i]);
+ }
+ assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ for (int i = 0; i < numAlarms; i++) {
+ mService.mListenerDeathRecipient.binderDied(listeners[i].asBinder());
+ assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ }
+ }
+
@After
public void tearDown() {
if (mMockingSession != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
deleted file mode 100644
index 48e459f..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
+++ /dev/null
@@ -1,680 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import static com.android.server.am.ActivityManagerService.Injector;
-import static com.android.server.am.AppCompactor.compactActionIntToString;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.platform.test.annotations.Presubmit;
-import android.provider.DeviceConfig;
-import android.text.TextUtils;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.ServiceThread;
-import com.android.server.appop.AppOpsService;
-import com.android.server.testables.TestableDeviceConfig;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for {@link AppCompactor}.
- *
- * Build/Install/Run:
- * atest FrameworksMockingServicesTests:AppCompactorTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner.class)
-public final class AppCompactorTest {
-
- private ServiceThread mThread;
-
- @Mock
- private AppOpsService mAppOpsService;
- private AppCompactor mCompactorUnderTest;
- private HandlerThread mHandlerThread;
- private Handler mHandler;
- private CountDownLatch mCountDown;
-
- @Rule
- public TestableDeviceConfig.TestableDeviceConfigRule
- mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
-
- @Before
- public void setUp() {
- mHandlerThread = new HandlerThread("");
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
-
- mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
- true /* allowIo */);
- mThread.start();
-
- ActivityManagerService ams = new ActivityManagerService(
- new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
- mThread);
- mCompactorUnderTest = new AppCompactor(ams,
- new AppCompactor.PropertyChangedCallbackForTest() {
- @Override
- public void onPropertyChanged() {
- if (mCountDown != null) {
- mCountDown.countDown();
- }
- }
- });
- }
-
- @After
- public void tearDown() {
- mHandlerThread.quit();
- mThread.quit();
- mCountDown = null;
- }
-
- @Test
- public void init_setsDefaults() {
- mCompactorUnderTest.init();
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE);
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-
- Set<Integer> expected = new HashSet<>();
- for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
- expected.add(Integer.parseInt(s));
- }
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- }
-
- @Test
- public void init_withDeviceConfigSetsParameters() {
- // When the DeviceConfig already has a flag value stored (note this test will need to
- // change if the default value changes from false).
- assertThat(AppCompactor.DEFAULT_USE_COMPACTION).isFalse();
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "true", false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1,
- Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2,
- Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_1,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_2,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_3,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_4,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_5,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_6,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
-
- // Then calling init will read and set that flag.
- mCompactorUnderTest.init();
- assertThat(mCompactorUnderTest.useCompaction()).isTrue();
- assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue();
-
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
- }
-
- @Test
- public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- // When we call init and change some the flag value...
- mCompactorUnderTest.init();
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "true", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that new flag value is updated in the implementation.
- assertThat(mCompactorUnderTest.useCompaction()).isTrue();
- assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue();
-
- // And again, setting the flag the other way.
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "false", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.useCompaction()).isFalse();
- }
-
- @Test
- public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- mCompactorUnderTest.init();
-
- // When we push an invalid flag value...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "foobar", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then we set the default.
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- }
-
- @Test
- public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override new values for the compaction action with reasonable values...
-
- // There are four possible values for compactAction[Some|Full].
- for (int i = 1; i < 5; i++) {
- mCountDown = new CountDownLatch(2);
- int expectedSome = (AppCompactor.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
- int expectedFull = (AppCompactor.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the updates are reflected in the flags.
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(expectedSome));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(expectedFull));
- }
- }
-
- @Test
- public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override new values for the compaction action with bad values ...
- mCountDown = new CountDownLatch(2);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1, "foo", false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the default values are reflected in the flag
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
-
- mCountDown = new CountDownLatch(2);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1, "", false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2, "", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
- }
-
- @Test
- public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override new reasonable throttle values after init...
- mCountDown = new CountDownLatch(6);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_1,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_2,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_3,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_4,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_5,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_6,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then those flags values are reflected in the compactor.
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
- }
-
- @Test
- public void compactThrottle_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When one of the throttles is overridden with a bad value...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_1, "foo", false);
- // Then all the throttles have the defaults set.
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- // Repeat for each of the throttle keys.
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_2, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_3, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_4, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_5, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_6, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
- }
-
- @Test
- public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with a reasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
- }
-
- @Test
- public void statsdSampleRate_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an unreasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE);
- }
-
- @Test
- public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an value outside of [0..1]...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(-1.0f), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the values is capped in the range.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(0.0f);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(1.01f), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the values is capped in the range.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(1.0f);
- }
-
- @Test
- public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with a reasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
- }
-
- @Test
- public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an unreasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
- }
-
- @Test
- public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with a reasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
- }
-
- @Test
- public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an unreasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
- }
-
- @Test
- public void procStateThrottle_listensToDeviceConfigChanges()
- throws InterruptedException {
- mCompactorUnderTest.init();
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).isEmpty();
- }
-
- @Test
- public void procStateThrottle_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- Set<Integer> expected = new HashSet<>();
- for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
- expected.add(Integer.parseInt(s));
- }
-
- // Not numbers
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-
- // Empty splits
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- }
-
- private class TestInjector extends Injector {
-
- TestInjector(Context context) {
- super(context);
- }
-
- @Override
- public AppOpsService getAppOpsService(File file, Handler handler) {
- return mAppOpsService;
- }
-
- @Override
- public Handler getUiHandler(ActivityManagerService service) {
- return mHandler;
- }
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
new file mode 100644
index 0000000..f037692
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+import static com.android.server.am.CachedAppOptimizer.compactActionIntToString;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link CachedAppOptimizer}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:CachedAppOptimizerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public final class CachedAppOptimizerTest {
+
+ private ServiceThread mThread;
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ private CachedAppOptimizer mCachedAppOptimizerUnderTest;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private CountDownLatch mCountDown;
+
+ @Rule
+ public TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ @Before
+ public void setUp() {
+ mHandlerThread = new HandlerThread("");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
+ true /* allowIo */);
+ mThread.start();
+
+ ActivityManagerService ams = new ActivityManagerService(
+ new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
+ mThread);
+ mCachedAppOptimizerUnderTest = new CachedAppOptimizer(ams,
+ new CachedAppOptimizer.PropertyChangedCallbackForTest() {
+ @Override
+ public void onPropertyChanged() {
+ if (mCountDown != null) {
+ mCountDown.countDown();
+ }
+ }
+ });
+ }
+
+ @After
+ public void tearDown() {
+ mHandlerThread.quit();
+ mThread.quit();
+ mCountDown = null;
+ }
+
+ @Test
+ public void init_setsDefaults() {
+ mCachedAppOptimizerUnderTest.init();
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+ Set<Integer> expected = new HashSet<>();
+ for (String s : TextUtils.split(
+ CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+ expected.add(Integer.parseInt(s));
+ }
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void init_withDeviceConfigSetsParameters() {
+ // When the DeviceConfig already has a flag value stored (note this test will need to
+ // change if the default value changes from false).
+ assertThat(CachedAppOptimizer.DEFAULT_USE_COMPACTION).isFalse();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1,
+ Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2,
+ Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_2,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_3,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_4,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_5,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_6,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+ Long.toString(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
+
+ // Then calling init will read and set that flag.
+ mCachedAppOptimizerUnderTest.init();
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
+
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(
+ (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(
+ (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
+ }
+
+ @Test
+ public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ // When we call init and change some the flag value...
+ mCachedAppOptimizerUnderTest.init();
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that new flag value is updated in the implementation.
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
+
+ // And again, setting the flag the other way.
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "false", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isFalse();
+ }
+
+ @Test
+ public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we push an invalid flag value...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "foobar", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then we set the default.
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ }
+
+ @Test
+ public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override new values for the compaction action with reasonable values...
+
+ // There are four possible values for compactAction[Some|Full].
+ for (int i = 1; i < 5; i++) {
+ mCountDown = new CountDownLatch(2);
+ int expectedSome = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
+ int expectedFull = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the updates are reflected in the flags.
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(expectedSome));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(expectedFull));
+ }
+ }
+
+ @Test
+ public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override new values for the compaction action with bad values ...
+ mCountDown = new CountDownLatch(2);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1, "foo", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the default values are reflected in the flag
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+
+ mCountDown = new CountDownLatch(2);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1, "", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2, "", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+ }
+
+ @Test
+ public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override new reasonable throttle values after init...
+ mCountDown = new CountDownLatch(6);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_2,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_3,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_4,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_5,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_6,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then those flags values are reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+ }
+
+ @Test
+ public void compactThrottle_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When one of the throttles is overridden with a bad value...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_1, "foo", false);
+ // Then all the throttles have the defaults set.
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ // Repeat for each of the throttle keys.
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_2, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_3, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_4, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_5, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_6, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+ }
+
+ @Test
+ public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with a reasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
+ }
+
+ @Test
+ public void statsdSampleRate_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an unreasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
+ }
+
+ @Test
+ public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an value outside of [0..1]...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(-1.0f), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the values is capped in the range.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(0.0f);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(1.01f), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the values is capped in the range.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(1.0f);
+ }
+
+ @Test
+ public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with a reasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+ }
+
+ @Test
+ public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an unreasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+ }
+
+ @Test
+ public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with a reasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+ Long.toString(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
+ }
+
+ @Test
+ public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an unreasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+ }
+
+ @Test
+ public void procStateThrottle_listensToDeviceConfigChanges()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).isEmpty();
+ }
+
+ @Test
+ public void procStateThrottle_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ Set<Integer> expected = new HashSet<>();
+ for (String s : TextUtils.split(
+ CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+ expected.add(Integer.parseInt(s));
+ }
+
+ // Not numbers
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+
+ // Empty splits
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ }
+
+ private class TestInjector extends Injector {
+
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandler;
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java
new file mode 100644
index 0000000..06fb102
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.location;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UserInfoStoreTest {
+
+ private static final int USER1_ID = 1;
+ private static final int USER1_MANAGED_ID = 11;
+ private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID};
+ private static final int USER2_ID = 2;
+ private static final int USER2_MANAGED_ID = 12;
+ private static final int[] USER2_PROFILES = new int[]{USER2_ID, USER2_MANAGED_ID};
+
+ @Mock private Context mContext;
+ @Mock private UserManager mUserManager;
+
+ private StaticMockitoSession mMockingSession;
+ private List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
+
+ private UserInfoStore mStore;
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .spyStatic(ActivityManager.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+
+ doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
+ doAnswer(invocation -> {
+ mBroadcastReceivers.add(invocation.getArgument(0));
+ return null;
+ }).when(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), any(
+ UserHandle.class), any(IntentFilter.class), isNull(), any(Handler.class));
+ doReturn(USER1_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER1_ID);
+ doReturn(USER2_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER2_ID);
+ doReturn(new UserInfo(USER1_ID, "", 0)).when(mUserManager).getProfileParent(
+ USER1_MANAGED_ID);
+ doReturn(new UserInfo(USER2_ID, "", 0)).when(mUserManager).getProfileParent(
+ USER2_MANAGED_ID);
+
+ doReturn(USER1_ID).when(ActivityManager::getCurrentUser);
+
+ mStore = new UserInfoStore(mContext);
+ mStore.onSystemReady();
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private void switchUser(int userId) {
+ doReturn(userId).when(ActivityManager::getCurrentUser);
+ Intent intent = new Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE,
+ userId);
+ for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) {
+ broadcastReceiver.onReceive(mContext, intent);
+ }
+ }
+
+ @Test
+ public void testListeners() {
+ UserInfoStore.UserChangedListener listener = mock(UserInfoStore.UserChangedListener.class);
+ mStore.addListener(listener);
+
+ switchUser(USER1_ID);
+ verify(listener, never()).onUserChanged(anyInt(), anyInt());
+
+ switchUser(USER2_ID);
+ verify(listener).onUserChanged(USER1_ID, USER2_ID);
+
+ switchUser(USER1_ID);
+ verify(listener).onUserChanged(USER2_ID, USER1_ID);
+ }
+
+ @Test
+ public void testCurrentUser() {
+ assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID);
+
+ switchUser(USER2_ID);
+
+ assertThat(mStore.getCurrentUserId()).isEqualTo(USER2_ID);
+
+ switchUser(USER1_ID);
+
+ assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID);
+ }
+
+ @Test
+ public void testIsCurrentUserOrProfile() {
+ assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isTrue();
+ assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isTrue();
+ assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isFalse();
+ assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isFalse();
+
+ switchUser(USER2_ID);
+
+ assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isFalse();
+ assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isTrue();
+ assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isFalse();
+ assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isTrue();
+ }
+
+ @Test
+ public void testGetParentUserId() {
+ assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
+ assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
+ assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
+ assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+
+ switchUser(USER2_ID);
+
+ assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
+ assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
+ assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
+ assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java b/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
index 50437b4..d367f71 100644
--- a/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
@@ -36,7 +36,7 @@
public void test1() {
assertTrue("dynamic_system service available", mService != null);
try {
- mService.startInstallation();
+ mService.startInstallation("dsu");
fail("DynamicSystemService did not throw SecurityException as expected");
} catch (SecurityException e) {
// expected
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 1829fb7..e609adc 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -16,7 +16,7 @@
package com.android.server;
-import static android.net.NetworkScoreManager.CACHE_FILTER_NONE;
+import static android.net.NetworkScoreManager.SCORE_FILTER_NONE;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -29,6 +29,7 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
@@ -56,7 +57,6 @@
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -182,8 +182,8 @@
}
private ScanResult createScanResult(String ssid, String bssid) {
- ScanResult result = new ScanResult();
- result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid);
+ ScanResult result = mock(ScanResult.class);
+ result.SSID = ssid;
result.BSSID = bssid;
return result;
}
@@ -306,7 +306,7 @@
bindToScorer(true /*callerIsScorer*/);
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
- mNetworkScoreCache, CACHE_FILTER_NONE);
+ mNetworkScoreCache, SCORE_FILTER_NONE);
mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
@@ -321,9 +321,9 @@
bindToScorer(true /*callerIsScorer*/);
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
- mNetworkScoreCache, CACHE_FILTER_NONE);
+ mNetworkScoreCache, SCORE_FILTER_NONE);
mNetworkScoreService.registerNetworkScoreCache(
- NetworkKey.TYPE_WIFI, mNetworkScoreCache2, CACHE_FILTER_NONE);
+ NetworkKey.TYPE_WIFI, mNetworkScoreCache2, SCORE_FILTER_NONE);
// updateScores should update both caches
mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
@@ -378,7 +378,7 @@
bindToScorer(true /*callerIsScorer*/);
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
- CACHE_FILTER_NONE);
+ SCORE_FILTER_NONE);
mNetworkScoreService.clearScores();
verify(mNetworkScoreCache).clearScores();
@@ -392,7 +392,7 @@
.thenReturn(PackageManager.PERMISSION_GRANTED);
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
- CACHE_FILTER_NONE);
+ SCORE_FILTER_NONE);
mNetworkScoreService.clearScores();
verify(mNetworkScoreCache).clearScores();
@@ -472,7 +472,7 @@
try {
mNetworkScoreService.registerNetworkScoreCache(
- NetworkKey.TYPE_WIFI, mNetworkScoreCache, CACHE_FILTER_NONE);
+ NetworkKey.TYPE_WIFI, mNetworkScoreCache, SCORE_FILTER_NONE);
fail("SecurityException expected");
} catch (SecurityException e) {
// expected
@@ -615,7 +615,7 @@
new ArrayList<>(scoredNetworkList),
NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
- consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE);
verify(mNetworkScoreCache).updateScores(scoredNetworkList);
verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
@@ -656,7 +656,7 @@
Collections.emptyList(),
NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
- consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE);
verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter);
}
@@ -673,7 +673,7 @@
List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
filteredList.remove(SCORED_NETWORK);
when(mCurrentNetworkFilter.apply(scoredNetworkList)).thenReturn(filteredList);
- consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK);
verify(mNetworkScoreCache).updateScores(filteredList);
verifyZeroInteractions(mScanResultsFilter);
@@ -691,7 +691,7 @@
List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
filteredList.remove(SCORED_NETWORK);
when(mScanResultsFilter.apply(scoredNetworkList)).thenReturn(filteredList);
- consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS);
verify(mNetworkScoreCache).updateScores(filteredList);
verifyZeroInteractions(mCurrentNetworkFilter);
@@ -794,7 +794,7 @@
@Test
public void testScanResultsScoreCacheFilter_invalidScanResults() throws Exception {
List<ScanResult> invalidScanResults = Lists.newArrayList(
- new ScanResult(),
+ mock(ScanResult.class),
createScanResult("", SCORED_NETWORK.networkKey.wifiKey.bssid),
createScanResult(WifiManager.UNKNOWN_SSID, SCORED_NETWORK.networkKey.wifiKey.bssid),
createScanResult(SSID, null),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 591c3a3..e1e9b7e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -75,6 +75,7 @@
import android.os.IPowerManager;
import android.os.PowerManager;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.testing.DexmakerShareClassLoaderRule;
import android.view.Display;
@@ -693,6 +694,18 @@
assertThat(result, is(false));
}
+ @Test
+ public void takeScreenshot_returnNull() {
+ // no canTakeScreenshot, should return null.
+ when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false);
+ assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
+
+ // no checkAccessibilityAccess, should return null.
+ when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true);
+ when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false);
+ assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
+ }
+
private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType,
int feedbackType, int flags, String[] packageNames, int notificationTimeout) {
serviceInfo.eventTypes = eventType;
@@ -804,6 +817,11 @@
}
@Override
+ public boolean switchToInputMethod(String imeId) {
+ return false;
+ }
+
+ @Override
public boolean isAccessibilityButtonAvailable() throws RemoteException {
return false;
}
@@ -827,5 +845,8 @@
@Override
public void onFingerprintGesture(int gesture) {}
+
+ @Override
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
index 04ac7fe..1504097 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
@@ -346,6 +346,14 @@
}
@Test
+ public void canTakeScreenshot_hasCapability_returnTrue() {
+ when(mMockA11yServiceConnection.getCapabilities())
+ .thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT);
+
+ assertTrue(mA11ySecurityPolicy.canTakeScreenshotLocked(mMockA11yServiceConnection));
+ }
+
+ @Test
public void resolveProfileParent_userIdIsCurrentUser_returnCurrentUser() {
final int currentUserId = 10;
final int userId = currentUserId;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 96d9c47..ac5169c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -113,7 +113,6 @@
mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString());
mUserState.setTouchExplorationEnabledLocked(true);
mUserState.setDisplayMagnificationEnabledLocked(true);
- mUserState.setNavBarMagnificationEnabledLocked(true);
mUserState.setAutoclickEnabledLocked(true);
mUserState.setUserNonInteractiveUiTimeoutLocked(30);
mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -132,7 +131,6 @@
assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
- assertFalse(mUserState.isNavBarMagnificationEnabledLocked());
assertFalse(mUserState.isAutoclickEnabledLocked());
assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 0f11566..39a3aae 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -212,7 +212,8 @@
String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
- Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+ Account[] accounts = mAms.getAccountsAsUser(null,
+ UserHandle.getCallingUserId(), mContext.getOpPackageName());
Arrays.sort(accounts, new AccountSorter());
assertEquals(6, accounts.length);
assertEquals(a11, accounts[0]);
@@ -222,8 +223,8 @@
assertEquals(a22, accounts[4]);
assertEquals(a32, accounts[5]);
- accounts = mAms.getAccounts(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
- mContext.getOpPackageName());
+ accounts = mAms.getAccountsAsUser(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ UserHandle.getCallingUserId(), mContext.getOpPackageName());
Arrays.sort(accounts, new AccountSorter());
assertEquals(3, accounts.length);
assertEquals(a11, accounts[0]);
@@ -232,8 +233,8 @@
mAms.removeAccountInternal(a21);
- accounts = mAms.getAccounts(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
- mContext.getOpPackageName());
+ accounts = mAms.getAccountsAsUser(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ UserHandle.getCallingUserId(), mContext.getOpPackageName());
Arrays.sort(accounts, new AccountSorter());
assertEquals(2, accounts.length);
assertEquals(a11, accounts[0]);
@@ -373,7 +374,8 @@
unlockSystemUser();
String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
- Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+ Account[] accounts = mAms.getAccountsAsUser(null, UserHandle.getCallingUserId(),
+ mContext.getOpPackageName());
assertEquals("1 account should be migrated", 1, accounts.length);
assertEquals(PreNTestDatabaseHelper.ACCOUNT_NAME, accounts[0].name);
assertEquals(PreNTestDatabaseHelper.ACCOUNT_PASSWORD, mAms.getPassword(accounts[0]));
@@ -2980,7 +2982,8 @@
Log.d(TAG, logPrefix + " getAccounts started");
long ti = System.currentTimeMillis();
try {
- Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+ Account[] accounts = mAms.getAccountsAsUser(null,
+ UserHandle.getCallingUserId(), mContext.getOpPackageName());
if (accounts == null || accounts.length != 1
|| !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals(
accounts[0].type)) {
@@ -3051,7 +3054,8 @@
Log.d(TAG, logPrefix + " getAccounts started");
long ti = System.currentTimeMillis();
try {
- Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+ Account[] accounts = mAms.getAccountsAsUser(null,
+ UserHandle.getCallingUserId(), mContext.getOpPackageName());
if (accounts == null || accounts.length != 1
|| !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals(
accounts[0].type)) {
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 79cc3db..f122014 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -31,6 +31,7 @@
import static com.google.android.collect.Lists.newArrayList;
import static com.google.android.collect.Sets.newHashSet;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
@@ -54,6 +55,7 @@
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.IUserSwitchObserver;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -74,10 +76,10 @@
import android.platform.test.annotations.Presubmit;
import android.util.Log;
-
import androidx.test.filters.SmallTest;
import com.android.server.FgThread;
+import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -107,6 +109,7 @@
private static final int TEST_USER_ID = 100;
private static final int TEST_USER_ID1 = 101;
private static final int TEST_USER_ID2 = 102;
+ private static final int TEST_USER_ID3 = 103;
private static final int NONEXIST_USER_ID = 2;
private static final int TEST_PRE_CREATED_USER_ID = 103;
@@ -120,6 +123,8 @@
private TestInjector mInjector;
private final HashMap<Integer, UserState> mUserStates = new HashMap<>();
+ private final KeyEvictedCallback mKeyEvictedCallback = (userId) -> { /* ignore */ };
+
private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList(
Intent.ACTION_USER_STARTED,
Intent.ACTION_USER_SWITCHED,
@@ -153,6 +158,7 @@
doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt());
+ // All UserController params are set to default.
mUserController = new UserController(mInjector);
setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated=*/ true);
@@ -187,7 +193,9 @@
@Test
public void testStartUserUIDisabled() {
- mUserController.mUserSwitchUiEnabled = false;
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
mUserController.startUser(TEST_USER_ID, true /* foreground */);
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
@@ -245,7 +253,9 @@
@Test
public void testFailedStartUserInForeground() {
- mUserController.mUserSwitchUiEnabled = false;
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
mUserController.startUserInForeground(NONEXIST_USER_ID);
verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
verify(mInjector.getWindowManager()).setSwitchingUser(false);
@@ -326,7 +336,9 @@
@Test
public void testContinueUserSwitchUIDisabled() throws RemoteException {
- mUserController.mUserSwitchUiEnabled = false;
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, true);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -389,11 +401,13 @@
* Test stopping of user from max running users limit.
*/
@Test
- public void testUserStoppingForMultipleUsersNormalMode()
+ public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking()
throws InterruptedException, RemoteException {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
- mUserController.mMaxRunningUsers = 3;
int numerOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
numerOfUserSwitches, false);
@@ -430,12 +444,13 @@
* all middle steps which takes too much work to mock.
*/
@Test
- public void testUserStoppingForMultipleUsersDelayedLockingMode()
+ public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
throws InterruptedException, RemoteException {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
- mUserController.mMaxRunningUsers = 3;
- mUserController.mDelayUserDataLocking = true;
int numerOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
numerOfUserSwitches, false);
@@ -456,7 +471,7 @@
// Skip all other steps and test unlock delaying only
UserState uss = mUserStates.get(TEST_USER_ID);
uss.setState(UserState.STATE_SHUTDOWN); // necessary state change from skipped part
- mUserController.finishUserStopped(uss);
+ mUserController.finishUserStopped(uss, /* allowDelayedLocking= */ true);
// Cannot mock FgThread handler, so confirm that there is no posted message left before
// checking.
waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
@@ -473,12 +488,87 @@
mUserController.getRunningUsersLU());
UserState ussUser1 = mUserStates.get(TEST_USER_ID1);
ussUser1.setState(UserState.STATE_SHUTDOWN);
- mUserController.finishUserStopped(ussUser1);
+ mUserController.finishUserStopped(ussUser1, /* allowDelayedLocking= */ true);
waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
verify(mInjector.mStorageManagerMock, times(1))
.lockUserKey(TEST_USER_ID);
}
+ /**
+ * Test locking user with mDelayUserDataLocking false.
+ */
+ @Test
+ public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
+ setUpAndStartUserInBackground(TEST_USER_ID);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID1);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID2);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID3);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+ }
+
+ /**
+ * Test conditional delayed locking with mDelayUserDataLocking true.
+ */
+ @Test
+ public void testUserLockingForDelayedLockingMode() throws Exception {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
+ // delayedLocking set and no KeyEvictedCallback, so it should not lock.
+ setUpAndStartUserInBackground(TEST_USER_ID);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ false);
+
+ setUpAndStartUserInBackground(TEST_USER_ID1);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID2);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID3);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+ }
+
+ private void setUpAndStartUserInBackground(int userId) throws Exception {
+ setUpUser(userId, 0);
+ mUserController.startUser(userId, /* foreground= */ false);
+ verify(mInjector.mStorageManagerMock, times(1))
+ .unlockUserKey(TEST_USER_ID, 0, null, null);
+ mUserStates.put(userId, mUserController.getStartedUserState(userId));
+ }
+
+ private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean delayedLocking,
+ KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception {
+ int r = mUserController.stopUser(userId, /* force= */ true, /* delayedLocking= */
+ delayedLocking, null, keyEvictedCallback);
+ assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
+ // fake all interim steps
+ UserState ussUser = mUserStates.get(userId);
+ ussUser.setState(UserState.STATE_SHUTDOWN);
+ // Passing delayedLocking invalidates incorrect internal data passing but currently there is
+ // no easy way to get that information passed through lambda.
+ mUserController.finishUserStopped(ussUser, delayedLocking);
+ waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
+ verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0))
+ .lockUserKey(userId);
+ }
+
private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
int expectedNumberOfCalls, boolean expectOldUserStopping)
throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index d44476e..3de006c 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -57,7 +57,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
@@ -523,7 +522,6 @@
* Test that {@link BackupManagerService#dump()} dumps system user information before non-system
* user information.
*/
-
@Test
public void testDump_systemUserFirst() {
String[] args = new String[0];
@@ -539,16 +537,31 @@
}
@Test
- @Ignore("b/147012496")
- public void testGetUserForAncestralSerialNumber() {
+ public void testGetUserForAncestralSerialNumber_forSystemUser() {
BackupManagerServiceTestable.sBackupDisabled = false;
BackupManagerService backupManagerService =
new BackupManagerServiceTestable(mContextMock, mUserServices);
+ when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+ .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
- assertThat(user).isEqualTo(UserHandle.of(1));
+ assertThat(user).isEqualTo(UserHandle.of(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testGetUserForAncestralSerialNumber_forNonSystemUser() {
+ BackupManagerServiceTestable.sBackupDisabled = false;
+ BackupManagerService backupManagerService =
+ new BackupManagerServiceTestable(mContextMock, mUserServices);
+ when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+ .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
+ when(mNonSystemUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
+
+ UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
+
+ assertThat(user).isEqualTo(UserHandle.of(NON_USER_SYSTEM));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index f96d996..bec265e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -411,7 +411,8 @@
// HAT sent to keystore
verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
// Send onAuthenticated to client
- verify(mReceiver1).onAuthenticationSucceeded();
+ verify(mReceiver1).onAuthenticationSucceeded(
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
// Current session becomes null
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -461,7 +462,8 @@
BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
waitForIdle();
verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
- verify(mReceiver1).onAuthenticationSucceeded();
+ verify(mReceiver1).onAuthenticationSucceeded(
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index abe39f0..312ff2c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -223,4 +223,22 @@
Utils.biometricConstantsToBiometricManager(testCases[i][0]));
}
}
+
+ @Test
+ public void testGetAuthenticationTypeForResult_getsCorrectType() {
+ assertEquals(Utils.getAuthenticationTypeForResult(
+ BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL);
+ assertEquals(Utils.getAuthenticationTypeForResult(
+ BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED),
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
+ assertEquals(Utils.getAuthenticationTypeForResult(
+ BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED),
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetAuthResultType_throwsForInvalidReason() {
+ Utils.getAuthenticationTypeForResult(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index ac555fd..3a8258b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -51,6 +51,7 @@
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import java.io.File;
@@ -223,6 +224,11 @@
}
@Override
+ PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+ return services.persistentDataBlockManagerInternal;
+ }
+
+ @Override
Looper getMyLooper() {
return Looper.getMainLooper();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 175c756..2cb7103 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -31,7 +31,10 @@
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
import static com.android.server.testutils.TestUtils.assertExpectException;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
@@ -62,6 +65,7 @@
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.PasswordMetrics;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
@@ -272,6 +276,29 @@
}).when(getServices().userManager).getApplicationRestrictions(
anyString(), any(UserHandle.class));
+ // Emulate UserManager.setUserRestriction/getUserRestrictions
+ final Map<UserHandle, Bundle> userRestrictions = new HashMap<>();
+
+ doAnswer((Answer<Void>) invocation -> {
+ String key = (String) invocation.getArguments()[0];
+ boolean value = (Boolean) invocation.getArguments()[1];
+ UserHandle user = (UserHandle) invocation.getArguments()[2];
+ Bundle userBundle = userRestrictions.getOrDefault(user, new Bundle());
+ userBundle.putBoolean(key, value);
+
+ userRestrictions.put(user, userBundle);
+ return null;
+ }).when(getServices().userManager).setUserRestriction(
+ anyString(), anyBoolean(), any(UserHandle.class));
+
+ doAnswer((Answer<Boolean>) invocation -> {
+ String key = (String) invocation.getArguments()[0];
+ UserHandle user = (UserHandle) invocation.getArguments()[1];
+ Bundle userBundle = userRestrictions.getOrDefault(user, new Bundle());
+ return userBundle.getBoolean(key);
+ }).when(getServices().userManager).hasUserRestriction(
+ anyString(), any(UserHandle.class));
+
// Add the first secondary user.
getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
UserManager.USER_TYPE_FULL_SECONDARY);
@@ -819,10 +846,8 @@
final int MANAGED_PROFILE_ADMIN_UID =
UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
- // Setup device owner.
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
mContext.packageName = admin1.getPackageName();
- setupDeviceOwner();
// Add a managed profile belonging to the system user.
addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
@@ -830,18 +855,13 @@
// Change the parent user's password.
dpm.reportPasswordChanged(UserHandle.USER_SYSTEM);
- // Both the device owner and the managed profile owner should receive this broadcast.
+ // The managed profile owner should receive this broadcast.
final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED);
intent.setComponent(admin1);
intent.putExtra(Intent.EXTRA_USER, UserHandle.of(UserHandle.USER_SYSTEM));
verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
MockUtils.checkIntent(intent),
- MockUtils.checkUserHandle(UserHandle.USER_SYSTEM),
- eq(null),
- any(Bundle.class));
- verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
- MockUtils.checkIntent(intent),
MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID),
eq(null),
any(Bundle.class));
@@ -861,12 +881,11 @@
final int MANAGED_PROFILE_ADMIN_UID =
UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
- // Setup device owner.
+ // Configure system as having separate profile challenge.
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
mContext.packageName = admin1.getPackageName();
doReturn(true).when(getServices().lockPatternUtils)
.isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID);
- setupDeviceOwner();
// Add a managed profile belonging to the system user.
addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
@@ -951,6 +970,10 @@
verify(getServices().iactivityManager, times(1)).updateDeviceOwner(
eq(admin1.getPackageName()));
+ verify(getServices().userManager, times(1)).setUserRestriction(
+ eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+ eq(true), eq(UserHandle.SYSTEM));
+
verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
MockUtils.checkIntentAction(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
@@ -1945,6 +1968,29 @@
// TODO Make sure restrictions are written to the file.
}
+ private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
+ Sets.newSet(
+ UserManager.DISALLOW_CONFIG_DATE_TIME,
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_BLUETOOTH,
+ UserManager.DISALLOW_BLUETOOTH_SHARING,
+ UserManager.DISALLOW_CONFIG_BLUETOOTH,
+ UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+ UserManager.DISALLOW_CONFIG_LOCATION,
+ UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+ UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
+ UserManager.DISALLOW_CONFIG_TETHERING,
+ UserManager.DISALLOW_CONFIG_WIFI,
+ UserManager.DISALLOW_CONTENT_CAPTURE,
+ UserManager.DISALLOW_CONTENT_SUGGESTIONS,
+ UserManager.DISALLOW_DATA_ROAMING,
+ UserManager.DISALLOW_DEBUGGING_FEATURES,
+ UserManager.DISALLOW_SAFE_BOOT,
+ UserManager.DISALLOW_SHARE_LOCATION,
+ UserManager.DISALLOW_SMS,
+ UserManager.DISALLOW_USB_FILE_TRANSFER
+ );
+
public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception {
final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
final int MANAGED_PROFILE_ADMIN_UID =
@@ -1957,15 +2003,9 @@
when(getServices().userManager.getProfileParent(MANAGED_PROFILE_USER_ID))
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
- parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
- verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(MANAGED_PROFILE_USER_ID),
- MockUtils.checkUserRestrictions(UserManager.DISALLOW_CONFIG_DATE_TIME),
- eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
- reset(getServices().userManagerInternal);
-
- parentDpm.clearUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
- reset(getServices().userManagerInternal);
+ for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS) {
+ addAndRemoveUserRestrictionOnParentDpm(restriction);
+ }
parentDpm.setCameraDisabled(admin1, true);
verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
@@ -1982,6 +2022,20 @@
reset(getServices().userManagerInternal);
}
+ private void addAndRemoveUserRestrictionOnParentDpm(String restriction) {
+ parentDpm.addUserRestriction(admin1, restriction);
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(DpmMockContext.CALLER_USER_HANDLE),
+ MockUtils.checkUserRestrictions(restriction),
+ eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
+ parentDpm.clearUserRestriction(admin1, restriction);
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(),
+ parentDpm.getUserRestrictions(admin1)
+ );
+ reset(getServices().userManagerInternal);
+ }
+
public void testNoDefaultEnabledUserRestrictions() throws Exception {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_USERS);
@@ -2002,12 +2056,11 @@
assertNoDeviceOwnerRestrictions();
- // Initialize DPMS again and check that the user restriction wasn't enabled again.
reset(getServices().userManagerInternal);
- initializeDpms();
- assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName()));
- assertNotNull(dpms.getDeviceOwnerAdminLocked());
+ // Ensure the DISALLOW_REMOVE_MANAGED_PROFILES restriction doesn't show up as a
+ // restriction to the device owner.
+ dpm.addUserRestriction(admin1, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE);
assertNoDeviceOwnerRestrictions();
}
@@ -2022,6 +2075,116 @@
);
}
+ public void testSetFactoryResetProtectionPolicyWithDO() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+
+ when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+ DpmMockContext.CALLER_UID);
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(new ArrayList<>())
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+ dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+ FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+ assertThat(result).isEqualTo(policy);
+ assertPoliciesAreEqual(policy, result);
+
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
+ public void testSetFactoryResetProtectionPolicyFailWithPO() throws Exception {
+ setupProfileOwner();
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ assertExpectException(SecurityException.class, null,
+ () -> dpm.setFactoryResetProtectionPolicy(admin1, policy));
+ }
+
+ public void testSetFactoryResetProtectionPolicyWithPOOfOrganizationOwnedDevice()
+ throws Exception {
+ setupProfileOwner();
+ configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+ when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+ DpmMockContext.CALLER_UID);
+
+ List<String> accounts = new ArrayList<>();
+ accounts.add("Account 1");
+ accounts.add("Account 2");
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(accounts)
+ .build();
+
+ dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+ FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+ assertThat(result).isEqualTo(policy);
+ assertPoliciesAreEqual(policy, result);
+
+ verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
+ public void testGetFactoryResetProtectionPolicyWithFrpManagementAgent()
+ throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+ DpmMockContext.CALLER_UID);
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(new ArrayList<>())
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+ mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpm.setActiveAdmin(admin1, /*replace=*/ false);
+ FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(null);
+ assertThat(result).isEqualTo(policy);
+ assertPoliciesAreEqual(policy, result);
+
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
+ private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+ FactoryResetProtectionPolicy actualPolicy) {
+ assertThat(actualPolicy.isFactoryResetProtectionDisabled()).isEqualTo(
+ expectedPolicy.isFactoryResetProtectionDisabled());
+ assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+ actualPolicy.getFactoryResetProtectionAccounts());
+ }
+
+ private void assertAccountsAreEqual(List<String> expectedAccounts,
+ List<String> actualAccounts) {
+ assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts);
+ }
+
public void testGetMacAddress() throws Exception {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
@@ -2845,6 +3008,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false);
@@ -2856,6 +3020,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(
@@ -2882,6 +3048,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false);
@@ -2890,6 +3057,7 @@
// Test again when split user is on
when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
true);
@@ -2901,6 +3069,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(
@@ -2913,6 +3083,8 @@
when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(
@@ -2938,6 +3110,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false /* because of non-split user */);
@@ -2951,6 +3124,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
@@ -2981,7 +3156,6 @@
setup_nonSplitUser_withDo_primaryUser();
final int MANAGED_PROFILE_USER_ID = 18;
final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308);
- addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
false /* we can't remove a managed profile */)).thenReturn(false);
when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
@@ -2995,6 +3169,8 @@
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
false/* because of completed device setup */);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ false/* because of completed device setup */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false/* because of non-split user */);
@@ -3008,6 +3184,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
@@ -3024,43 +3202,21 @@
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false);
- // COMP mode is allowed.
+ // COMP mode NOT is allowed.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
- // And other DPCs can also provision a managed profile (DO + BYOD case).
+ // And other DPCs can NOT provision a managed profile.
assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DpmMockContext.ANOTHER_PACKAGE_NAME,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
- DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
- }
-
- public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedByDo() throws Exception {
- setup_nonSplitUser_withDo_primaryUser();
- mContext.packageName = admin1.getPackageName();
- mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
- // The DO should be allowed to initiate provisioning if it set the restriction itself, but
- // other packages should be forbidden.
- when(getServices().userManager.hasUserRestriction(
- eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
- eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
- .thenReturn(true);
- when(getServices().userManager.getUserRestrictionSource(
- eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
- eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
- .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
- assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
- assertCheckProvisioningPreCondition(
- DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DpmMockContext.ANOTHER_PACKAGE_NAME,
- DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
}
@@ -3081,31 +3237,46 @@
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DpmMockContext.ANOTHER_PACKAGE_NAME,
- DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
}
- public void testCheckProvisioningPreCondition_nonSplitUser_comp() throws Exception {
+ public void testCheckCannotSetProfileOwnerWithDeviceOwner() throws Exception {
+ setup_nonSplitUser_withDo_primaryUser();
+ final int managedProfileUserId = 18;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 1308);
+
+ final int userId = UserHandle.getUserId(managedProfileAdminUid);
+ getServices().addUser(userId, 0, UserManager.USER_TYPE_PROFILE_MANAGED,
+ UserHandle.USER_SYSTEM);
+ mContext.callerPermissions.addAll(OWNER_SETUP_PERMISSIONS);
+ setUpPackageManagerForFakeAdmin(admin1, managedProfileAdminUid, admin1);
+ dpm.setActiveAdmin(admin1, false, userId);
+ assertFalse(dpm.setProfileOwner(admin1, null, userId));
+ mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS);
+ }
+
+ public void testCheckProvisioningPreCondition_nonSplitUser_attemptingComp() throws Exception {
setup_nonSplitUser_withDo_primaryUser_ManagedProfile();
mContext.packageName = admin1.getPackageName();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
// We can delete the managed profile to create a new one, so provisioning is allowed.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DpmMockContext.ANOTHER_PACKAGE_NAME,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
}
@@ -3133,8 +3304,8 @@
// But the device owner can still do it because it has set the restriction itself.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
}
private void setup_splitUser_firstBoot_systemUser() throws Exception {
@@ -3153,6 +3324,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
false /* because canAddMoreManagedProfiles returns false */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -3167,6 +3339,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER);
assertCheckProvisioningPreCondition(
@@ -3193,6 +3367,8 @@
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
true/* it's undefined behavior. Can be changed into false in the future */);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ true/* it's undefined behavior. Can be changed into false in the future */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
false /* because canAddMoreManagedProfiles returns false */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -3207,6 +3383,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER);
assertCheckProvisioningPreCondition(
@@ -3232,6 +3410,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
true);
@@ -3244,6 +3423,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
@@ -3271,6 +3452,8 @@
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
true/* it's undefined behavior. Can be changed into false in the future */);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ true/* it's undefined behavior. Can be changed into false in the future */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
true/* it's undefined behavior. Can be changed into false in the future */);
@@ -3284,6 +3467,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
@@ -3329,6 +3514,8 @@
when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManager.getProfileParent(DpmMockContext.CALLER_USER_HANDLE))
+ .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
true)).thenReturn(true);
setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE);
@@ -3341,7 +3528,7 @@
setup_provisionManagedProfileWithDeviceOwner_primaryUser();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
mContext.packageName = admin1.getPackageName();
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
}
public void testCheckProvisioningPreCondition_provisionManagedProfileWithDeviceOwner_primaryUser()
@@ -3349,9 +3536,9 @@
setup_provisionManagedProfileWithDeviceOwner_primaryUser();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
- // COMP mode is allowed.
+ // COMP mode is NOT allowed.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_OK);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
}
private void setup_provisionManagedProfileCantRemoveUser_primaryUser() throws Exception {
@@ -3645,11 +3832,7 @@
when(getServices().userManager.getUsers())
.thenReturn(Arrays.asList(managedProfileUserInfo));
- // Any caller without the MANAGE_USERS permission should get a security exception.
- assertExpectException(SecurityException.class, null, () ->
- dpm.isOrganizationOwnedDeviceWithManagedProfile());
- // But when the right permission is granted, this should succeed.
- mContext.permissions.add(android.Manifest.permission.MANAGE_USERS);
+ // Any caller should be able to call this method.
assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile());
configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile());
@@ -3868,11 +4051,6 @@
List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
MoreAsserts.assertEmpty(targetUsers);
- // Setup a managed profile managed by the same admin.
- final int MANAGED_PROFILE_USER_ID = 15;
- final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
- addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
-
// Add a secondary user, it should never talk with.
final int ANOTHER_USER_ID = 36;
getServices().addUser(ANOTHER_USER_ID, 0, UserManager.USER_TYPE_FULL_SECONDARY);
@@ -3882,30 +4060,11 @@
targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
MoreAsserts.assertEmpty(targetUsers);
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
- MoreAsserts.assertEmpty(targetUsers);
-
// Setting affiliation ids
final Set<String> userAffiliationIds = Collections.singleton("some.affiliation-id");
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
dpm.setAffiliationIds(admin1, userAffiliationIds);
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- dpm.setAffiliationIds(admin1, userAffiliationIds);
-
- // Calling from device owner admin, the result list should just contain the managed
- // profile user id.
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
- MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.of(MANAGED_PROFILE_USER_ID));
-
- // Calling from managed profile admin, the result list should just contain the system
- // user id.
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
- MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.SYSTEM);
-
// Changing affiliation ids in one
dpm.setAffiliationIds(admin1, Collections.singleton("some-different-affiliation-id"));
@@ -3919,38 +4078,6 @@
MoreAsserts.assertEmpty(targetUsers);
}
- public void testGetBindDeviceAdminTargetUsers_differentPackage() throws Exception {
- // Setup a device owner.
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- setupDeviceOwner();
-
- // Set up a managed profile managed by different package.
- final int MANAGED_PROFILE_USER_ID = 15;
- final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
- final ComponentName adminDifferentPackage =
- new ComponentName("another.package", "whatever.class");
- addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
-
- // Setting affiliation ids
- final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id");
- dpm.setAffiliationIds(admin1, userAffiliationIds);
-
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- dpm.setAffiliationIds(adminDifferentPackage, userAffiliationIds);
-
- // Calling from device owner admin, we should get zero bind device admin target users as
- // their packages are different.
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
- MoreAsserts.assertEmpty(targetUsers);
-
- // Calling from managed profile admin, we should still get zero target users for the same
- // reason.
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- targetUsers = dpm.getBindDeviceAdminTargetUsers(adminDifferentPackage);
- MoreAsserts.assertEmpty(targetUsers);
- }
-
private void verifyLockTaskState(int userId) throws Exception {
verifyLockTaskState(userId, new String[0],
DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS);
@@ -3987,79 +4114,6 @@
() -> dpm.setLockTaskFeatures(who, flags));
}
- public void testLockTaskPolicyAllowedForAffiliatedUsers() throws Exception {
- // Setup a device owner.
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- setupDeviceOwner();
- // Lock task policy is updated when loading user data.
- verifyLockTaskState(UserHandle.USER_SYSTEM);
-
- // Set up a managed profile managed by different package (package name shouldn't matter)
- final int MANAGED_PROFILE_USER_ID = 15;
- final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
- final ComponentName adminDifferentPackage =
- new ComponentName("another.package", "whatever.class");
- addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
- verifyLockTaskState(MANAGED_PROFILE_USER_ID);
-
- // Setup a PO on the secondary user
- mContext.binder.callingUid = DpmMockContext.CALLER_UID;
- setAsProfileOwner(admin3);
- verifyLockTaskState(DpmMockContext.CALLER_USER_HANDLE);
-
- // The DO can still set lock task packages
- final String[] doPackages = {"doPackage1", "doPackage2"};
- final int flags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
- | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
- | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
- verifyCanSetLockTask(DpmMockContext.CALLER_SYSTEM_USER_UID, UserHandle.USER_SYSTEM, admin1, doPackages, flags);
-
- final String[] secondaryPoPackages = {"secondaryPoPackage1", "secondaryPoPackage2"};
- final int secondaryPoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
- | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
- | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
- verifyCanNotSetLockTask(DpmMockContext.CALLER_UID, admin3, secondaryPoPackages, secondaryPoFlags);
-
- // Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- final String[] poPackages = {"poPackage1", "poPackage2"};
- final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
- | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
- | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
- verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, poPackages, poFlags);
-
- // Setting same affiliation ids
- final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id");
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- dpm.setAffiliationIds(admin1, userAffiliationIds);
-
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- dpm.setAffiliationIds(adminDifferentPackage, userAffiliationIds);
-
- // Now the managed profile can set lock task packages.
- dpm.setLockTaskPackages(adminDifferentPackage, poPackages);
- MoreAsserts.assertEquals(poPackages, dpm.getLockTaskPackages(adminDifferentPackage));
- assertTrue(dpm.isLockTaskPermitted("poPackage1"));
- assertFalse(dpm.isLockTaskPermitted("doPackage2"));
- // And it can set lock task features.
- dpm.setLockTaskFeatures(adminDifferentPackage, poFlags);
- verifyLockTaskState(MANAGED_PROFILE_USER_ID, poPackages, poFlags);
-
- // Unaffiliate the profile, lock task mode no longer available on the profile.
- dpm.setAffiliationIds(adminDifferentPackage, Collections.emptySet());
- assertFalse(dpm.isLockTaskPermitted("poPackage1"));
- // Lock task packages cleared when loading user data and when the user becomes unaffiliated.
- verify(getServices().iactivityManager, times(2)).updateLockTaskPackages(
- MANAGED_PROFILE_USER_ID, new String[0]);
- verify(getServices().iactivityTaskManager, times(2)).updateLockTaskFeatures(
- MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
-
- // Verify that lock task packages were not cleared for the DO
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- assertTrue(dpm.isLockTaskPermitted("doPackage1"));
-
- }
-
public void testLockTaskPolicyForProfileOwner() throws Exception {
// Setup a PO
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
new file mode 100644
index 0000000..bc853c6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.os.Parcel;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link android.app.admin.FactoryResetProtectionPolicy}.
+ *
+ * atest com.android.server.devicepolicy.FactoryResetProtectionPolicyTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class FactoryResetProtectionPolicyTest {
+
+ private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+ "factory_reset_protection_policy";
+
+ @Test
+ public void testNonDefaultFactoryResetProtectionPolicyObject() throws Exception {
+ List<String> accounts = new ArrayList<>();
+ accounts.add("Account 1");
+ accounts.add("Account 2");
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(accounts)
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ testParcelAndUnparcel(policy);
+ testSerializationAndDeserialization(policy);
+ }
+
+ @Test
+ public void testInvalidXmlFactoryResetProtectionPolicyObject() throws Exception {
+ List<String> accounts = new ArrayList<>();
+ accounts.add("Account 1");
+ accounts.add("Account 2");
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(accounts)
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ testParcelAndUnparcel(policy);
+ testInvalidXmlSerializationAndDeserialization(policy);
+ }
+
+ private void testParcelAndUnparcel(FactoryResetProtectionPolicy policy) {
+ Parcel parcel = Parcel.obtain();
+ policy.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ FactoryResetProtectionPolicy actualPolicy =
+ FactoryResetProtectionPolicy.CREATOR.createFromParcel(parcel);
+ assertPoliciesAreEqual(policy, actualPolicy);
+ parcel.recycle();
+ }
+
+ private void testSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+ throws Exception {
+ ByteArrayOutputStream outStream = serialize(policy);
+ ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new InputStreamReader(inStream));
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+
+ assertPoliciesAreEqual(policy, policy.readFromXml(parser));
+ }
+
+ private void testInvalidXmlSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+ throws Exception {
+ ByteArrayOutputStream outStream = serialize(policy);
+ ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+ XmlPullParser parser = mock(XmlPullParser.class);
+ when(parser.next()).thenThrow(XmlPullParserException.class);
+ parser.setInput(new InputStreamReader(inStream));
+
+ // If deserialization fails, then null is returned.
+ assertNull(policy.readFromXml(parser));
+ }
+
+ private ByteArrayOutputStream serialize(FactoryResetProtectionPolicy policy)
+ throws IOException {
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ final XmlSerializer outXml = new FastXmlSerializer();
+ outXml.setOutput(outStream, StandardCharsets.UTF_8.name());
+ outXml.startDocument(null, true);
+ outXml.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ policy.writeToXml(outXml);
+ outXml.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ outXml.endDocument();
+ outXml.flush();
+ return outStream;
+ }
+
+ private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+ FactoryResetProtectionPolicy actualPolicy) {
+ assertEquals(expectedPolicy.isFactoryResetProtectionDisabled(),
+ actualPolicy.isFactoryResetProtectionDisabled());
+ assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+ actualPolicy.getFactoryResetProtectionAccounts());
+ }
+
+ private void assertAccountsAreEqual(List<String> expectedAccounts,
+ List<String> actualAccounts) {
+ assertEquals(expectedAccounts.size(), actualAccounts.size());
+ for (int i = 0; i < expectedAccounts.size(); i++) {
+ assertEquals(expectedAccounts.get(i), actualAccounts.get(i));
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 919a3f6..6c2c144 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -68,6 +68,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -117,6 +118,7 @@
public final TimeDetector timeDetector;
public final TimeZoneDetector timeZoneDetector;
public final KeyChain.KeyChainConnection keyChainConnection;
+ public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
public final BuildMock buildMock = new BuildMock();
@@ -160,6 +162,7 @@
timeDetector = mock(TimeDetector.class);
timeZoneDetector = mock(TimeZoneDetector.class);
keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
+ persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
index 63189e7..5aed194 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
@@ -16,14 +16,7 @@
package com.android.server.integrity;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasItems;
-import static org.junit.Assert.assertThat;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import android.content.integrity.AppInstallMetadata;
import android.content.integrity.AtomicFormula;
@@ -33,26 +26,26 @@
import android.content.integrity.Rule;
import android.util.Slog;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.integrity.parser.RuleXmlParser;
-import com.android.server.integrity.serializer.RuleXmlSerializer;
+import com.android.server.integrity.parser.RuleBinaryParser;
+import com.android.server.integrity.serializer.RuleBinarySerializer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/** Unit test for {@link IntegrityFileManager} */
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnit4.class)
public class IntegrityFileManagerTest {
private static final String TAG = "IntegrityFileManagerTest";
@@ -72,7 +65,7 @@
// Use Xml Parser/Serializer to help with debugging since we can just print the file.
mIntegrityFileManager =
new IntegrityFileManager(
- new RuleXmlParser(), new RuleXmlSerializer(), mTmpDir);
+ new RuleBinaryParser(), new RuleBinarySerializer(), mTmpDir);
Files.walk(mTmpDir.toPath())
.forEach(
path -> {
@@ -97,12 +90,19 @@
@Test
public void testGetMetadata() throws Exception {
- assertNull(mIntegrityFileManager.readMetadata());
+ assertThat(mIntegrityFileManager.readMetadata()).isNull();
mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
- assertNotNull(mIntegrityFileManager.readMetadata());
- assertEquals(VERSION, mIntegrityFileManager.readMetadata().getVersion());
- assertEquals(RULE_PROVIDER, mIntegrityFileManager.readMetadata().getRuleProvider());
+ assertThat(mIntegrityFileManager.readMetadata()).isNotNull();
+ assertThat(mIntegrityFileManager.readMetadata().getVersion()).isEqualTo(VERSION);
+ assertThat(mIntegrityFileManager.readMetadata().getRuleProvider()).isEqualTo(RULE_PROVIDER);
+ }
+
+ @Test
+ public void testIsInitialized() throws Exception {
+ assertThat(mIntegrityFileManager.initialized()).isFalse();
+ mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
+ assertThat(mIntegrityFileManager.initialized()).isTrue();
}
@Test
@@ -110,20 +110,8 @@
String packageName = "package";
String packageCert = "cert";
int version = 123;
- Rule packageNameRule =
- new Rule(
- new StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- Rule.DENY);
- Rule packageCertRule =
- new Rule(
- new StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- packageCert,
- /* isHashedValue= */ false),
- Rule.DENY);
+ Rule packageNameRule = getPackageNameIndexedRule(packageName);
+ Rule packageCertRule = getAppCertificateIndexedRule(packageCert);
Rule versionCodeRule =
new Rule(
new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, version),
@@ -142,9 +130,7 @@
AtomicFormula.LE,
version))),
Rule.DENY);
- // We will test the specifics of indexing in other classes. Here, we just require that all
- // rules that are related to the given AppInstallMetadata are returned and do not assert
- // anything on other rules.
+
List<Rule> rules =
Arrays.asList(packageNameRule, packageCertRule, versionCodeRule, randomRule);
mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
@@ -159,17 +145,77 @@
.build();
List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
- assertThat(rulesFetched, hasItems(
- equalTo(packageNameRule),
- equalTo(packageCertRule),
- equalTo(versionCodeRule)
- ));
+ assertThat(rulesFetched)
+ .containsExactly(packageNameRule, packageCertRule, versionCodeRule, randomRule);
}
@Test
- public void testIsInitialized() throws Exception {
- assertFalse(mIntegrityFileManager.initialized());
- mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
- assertTrue(mIntegrityFileManager.initialized());
+ public void testGetRules_indexedForManyRules() throws Exception {
+ String packageName = "package";
+ String installerName = "installer";
+ String appCertificate = "cert";
+
+ // Create a rule set with 2500 package name indexed, 2500 app certificate indexed and
+ // 500 unindexed rules.
+ List<Rule> rules = new ArrayList<>();
+
+ for (int i = 0; i < 2500; i++) {
+ rules.add(getPackageNameIndexedRule(String.format("%s%04d", packageName, i)));
+ rules.add(getAppCertificateIndexedRule(String.format("%s%04d", appCertificate, i)));
+ }
+
+ for (int i = 0; i < 70; i++) {
+ rules.add(getInstallerCertificateRule(String.format("%s%04d", installerName, i)));
+ }
+
+ // Write the rules and get them indexed.
+ mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
+
+ // Read the rules for a specific rule.
+ String installedPackageName = String.format("%s%04d", packageName, 264);
+ String installedAppCertificate = String.format("%s%04d", appCertificate, 1264);
+ AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder()
+ .setPackageName(installedPackageName)
+ .setAppCertificate(installedAppCertificate)
+ .setVersionCode(250)
+ .setInstallerName("abc")
+ .setInstallerCertificate("abc")
+ .setIsPreInstalled(true)
+ .build();
+ List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
+
+ // Verify that we do not load all the rules and we have the necessary rules to evaluate.
+ assertThat(rulesFetched.size()).isEqualTo(270);
+ assertThat(rulesFetched)
+ .containsAllOf(
+ getPackageNameIndexedRule(installedPackageName),
+ getAppCertificateIndexedRule(installedAppCertificate));
+ }
+
+ private Rule getPackageNameIndexedRule(String packageName) {
+ return new Rule(
+ new StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ packageName,
+ /* isHashedValue= */ false),
+ Rule.DENY);
+ }
+
+ private Rule getAppCertificateIndexedRule(String appCertificate) {
+ return new Rule(
+ new StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE,
+ appCertificate,
+ /* isHashedValue= */ false),
+ Rule.DENY);
+ }
+
+ private Rule getInstallerCertificateRule(String installerCert) {
+ return new Rule(
+ new StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ installerCert,
+ /* isHashedValue= */ false),
+ Rule.DENY);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
index 7a070ee..eda2b70 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -188,7 +188,7 @@
}
@Test
- public void testEvaluateRules_ruleNotInDNF_ignoreAndAllow() {
+ public void testEvaluateRules_orRules() {
CompoundFormula compoundFormula =
new CompoundFormula(
CompoundFormula.OR,
@@ -206,11 +206,11 @@
IntegrityCheckResult result =
RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
- assertEquals(ALLOW, result.getEffect());
+ assertEquals(DENY, result.getEffect());
}
@Test
- public void testEvaluateRules_compoundFormulaWithNot_allow() {
+ public void testEvaluateRules_compoundFormulaWithNot_deny() {
CompoundFormula openSubFormula =
new CompoundFormula(
CompoundFormula.AND,
@@ -230,7 +230,7 @@
IntegrityCheckResult result =
RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
- assertEquals(ALLOW, result.getEffect());
+ assertEquals(DENY, result.getEffect());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java
deleted file mode 100644
index 1eb5eb5..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.model;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-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.utils.TestUtils.getBits;
-import static com.android.server.integrity.utils.TestUtils.getBytes;
-import static com.android.server.integrity.utils.TestUtils.getValueBits;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.Rule;
-
-import com.android.server.integrity.parser.BinaryFileOperations;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.IOException;
-
-@RunWith(JUnit4.class)
-public class BitTrackedInputStreamTest {
- private static final String COMPOUND_FORMULA_START_BITS =
- getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
- private static final String COMPOUND_FORMULA_END_BITS =
- getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS);
- private static final String ATOMIC_FORMULA_START_BITS =
- getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS);
- private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS);
- private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
- private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
- private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
-
- private static final String IS_NOT_HASHED = "0";
- private static final String START_BIT = "1";
- private static final String END_BIT = "1";
-
- @Test
- public void testBitOperationsCountBitsCorrectly() throws IOException {
- String packageName = "com.test.app";
- byte[] testInput =
- getBytes(
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + NOT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT);
-
- BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
-
- // Right after construction, the read bits count should be 0.
- assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0);
-
- // Get next 10 bits should result with 10 bits read.
- bitTrackedInputStream.getNext(10);
- assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(10);
-
- // When we move the cursor 8 bytes, we should point to 64 bits.
- bitTrackedInputStream.setCursorToByteLocation(8);
- assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(64);
-
- // Read until the end and the total size of the input stream should be available.
- while (bitTrackedInputStream.hasNext()) {
- bitTrackedInputStream.getNext(1);
- }
- assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(128);
- }
-
- @Test
- public void testBitInputStreamOperationsStillWork() throws IOException {
- String packageName = "com.test.app";
- byte[] testInput =
- getBytes(
- IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName));
-
- BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
- assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0);
-
- // Read until the string parameter.
- String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream);
-
- // Verify that the read bytes are counted.
- assertThat(stringValue).isEqualTo(packageName);
- assertThat(bitTrackedInputStream.getReadBitsCount()).isGreaterThan(0);
- }
-
- @Test
- public void testBitTrackedInputStream_moveCursorForwardFailsIfAlreadyRead() throws IOException {
- String packageName = "com.test.app";
- byte[] testInput =
- getBytes(
- IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName));
-
- BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
-
- // Read more than two bytes.
- bitTrackedInputStream.getNext(20);
-
- // Ask to move the cursor to the second byte.
- assertThrows(
- IllegalStateException.class,
- () -> bitTrackedInputStream.setCursorToByteLocation(2));
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
index c7cc343..57274bf 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
@@ -53,17 +53,17 @@
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
- BitOutputStream bitOutputStream = new BitOutputStream();
+ BitOutputStream bitOutputStream = new BitOutputStream(byteTrackedOutputStream);
bitOutputStream.setNext(/* numOfBits= */5, /* value= */1);
- byteTrackedOutputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
// Even though we wrote 5 bits, this will complete to 1 byte.
assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(1);
// Add a bit less than 2 bytes (10 bits).
- bitOutputStream.clear();
bitOutputStream.setNext(/* numOfBits= */10, /* value= */1);
- byteTrackedOutputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
+ assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(3);
assertThat(outputStream.toByteArray().length).isEqualTo(3);
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
index 94f68a5..cfa1de3 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
@@ -33,8 +33,8 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@RunWith(JUnit4.class)
@@ -52,9 +52,7 @@
IS_NOT_HASHED
+ getBits(PACKAGE_NAME.length(), VALUE_SIZE_BITS)
+ getValueBits(PACKAGE_NAME));
- ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
- rule.put(stringBytes);
- BitInputStream inputStream = new BitInputStream(rule.array());
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(stringBytes));
String resultString = getStringValue(inputStream);
@@ -68,9 +66,7 @@
IS_HASHED
+ getBits(APP_CERTIFICATE.length(), VALUE_SIZE_BITS)
+ getValueBits(APP_CERTIFICATE));
- ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
- rule.put(ruleBytes);
- BitInputStream inputStream = new BitInputStream(rule.array());
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
String resultString = getStringValue(inputStream);
@@ -82,9 +78,7 @@
@Test
public void testGetStringValue_withSizeAndHashingInfo() throws IOException {
byte[] ruleBytes = getBytes(getValueBits(PACKAGE_NAME));
- ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
- rule.put(ruleBytes);
- BitInputStream inputStream = new BitInputStream(rule.array());
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
String resultString = getStringValue(inputStream,
PACKAGE_NAME.length(), /* isHashedValue= */false);
@@ -96,9 +90,7 @@
public void testGetIntValue() throws IOException {
int randomValue = 15;
byte[] ruleBytes = getBytes(getBits(randomValue, /* numOfBits= */ 32));
- ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
- rule.put(ruleBytes);
- BitInputStream inputStream = new BitInputStream(rule.array());
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
assertThat(getIntValue(inputStream)).isEqualTo(randomValue);
}
@@ -107,9 +99,7 @@
public void testGetBooleanValue_true() throws IOException {
String booleanValue = "1";
byte[] ruleBytes = getBytes(booleanValue);
- ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
- rule.put(ruleBytes);
- BitInputStream inputStream = new BitInputStream(rule.array());
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
assertThat(getBooleanValue(inputStream)).isEqualTo(true);
}
@@ -118,9 +108,7 @@
public void testGetBooleanValue_false() throws IOException {
String booleanValue = "0";
byte[] ruleBytes = getBytes(booleanValue);
- ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
- rule.put(ruleBytes);
- BitInputStream inputStream = new BitInputStream(rule.array());
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
assertThat(getBooleanValue(inputStream)).isEqualTo(false);
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
index 51f5c75..e0b2e22 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
@@ -44,11 +44,8 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -122,7 +119,6 @@
rule.put(DEFAULT_FORMAT_VERSION_BYTES);
rule.put(ruleBytes);
RuleParser binaryParser = new RuleBinaryParser();
- InputStream inputStream = new ByteArrayInputStream(rule.array());
Rule expectedRule =
new Rule(
new CompoundFormula(
@@ -134,7 +130,8 @@
/* isHashedValue= */ false))),
Rule.DENY);
- List<Rule> rules = binaryParser.parse(inputStream, NO_INDEXING);
+ List<Rule> rules =
+ binaryParser.parse(RandomAccessObject.ofBytes(rule.array()), NO_INDEXING);
assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
}
@@ -324,6 +321,7 @@
ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
rule.put(DEFAULT_FORMAT_VERSION_BYTES);
rule.put(ruleBytes);
+
RuleParser binaryParser = new RuleBinaryParser();
Rule expectedRule =
new Rule(
@@ -413,7 +411,7 @@
assertExpectException(
RuleParseException.class,
- /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit.",
+ /* expectedExceptionMessageRegex= */ "A rule must end with a '1' bit.",
() -> binaryParser.parse(rule.array()));
}
@@ -440,7 +438,7 @@
assertExpectException(
RuleParseException.class,
- /* expectedExceptionMessageRegex */ "Invalid byte index",
+ /* expectedExceptionMessageRegex */ "",
() -> binaryParser.parse(rule.array()));
}
@@ -656,65 +654,4 @@
/* expectedExceptionMessageRegex */ "A rule must end with a '1' bit",
() -> binaryParser.parse(rule.array()));
}
-
- @Test
- public void testBinaryStream_multipleRules_indexingIdentifiesParsesIndexRangeCorrectly()
- throws Exception {
- String packageName2 = "com.test.2";
-
- byte[] ruleBytes1 = getBytes(getRulesWithPackageName("com.test.1"));
- byte[] ruleBytes2 = getBytes(getRulesWithPackageName(packageName2));
- byte[] ruleBytes3 = getBytes(getRulesWithPackageName("com.test.3"));
-
- ByteBuffer rule =
- ByteBuffer.allocate(
- DEFAULT_FORMAT_VERSION_BYTES.length
- + ruleBytes1.length
- + ruleBytes2.length
- + ruleBytes3.length);
- rule.put(DEFAULT_FORMAT_VERSION_BYTES);
- rule.put(ruleBytes1);
- rule.put(ruleBytes2);
- rule.put(ruleBytes3);
- InputStream inputStream = new ByteArrayInputStream(rule.array());
-
- RuleParser binaryParser = new RuleBinaryParser();
-
- List<RuleIndexRange> indexRanges = new ArrayList<>();
- indexRanges.add(
- new RuleIndexRange(
- DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length,
- DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length
- + ruleBytes2.length));
- List<Rule> rules = binaryParser.parse(inputStream, indexRanges);
-
- Rule expectedRule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Collections.singletonList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName2,
- /* isHashedValue= */ false))),
- Rule.DENY);
-
- assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
- }
-
- private static String getRulesWithPackageName(String packageName) {
- return START_BIT
- + COMPOUND_FORMULA_START_BITS
- + NOT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
-
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
index 6944aee..0f0dee9 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
@@ -28,8 +28,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
@@ -61,7 +59,6 @@
+ "</R>"
+ "</RL>";
RuleParser xmlParser = new RuleXmlParser();
- InputStream inputStream = new ByteArrayInputStream(ruleXmlCompoundFormula.getBytes());
Rule expectedRule =
new Rule(
new CompoundFormula(
@@ -73,7 +70,7 @@
/* isHashedValue= */ false))),
Rule.DENY);
- List<Rule> rules = xmlParser.parse(inputStream, Collections.emptyList());
+ List<Rule> rules = xmlParser.parse(ruleXmlCompoundFormula.getBytes());
assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
}
@@ -617,13 +614,12 @@
/* tag= */ "AF", atomicFormulaAttrs, /* closed= */ true)
+ "</OF>"
+ "</R>";
- InputStream inputStream = new ByteArrayInputStream(ruleXmlWithNoRuleList.getBytes());
RuleParser xmlParser = new RuleXmlParser();
assertExpectException(
RuleParseException.class,
/* expectedExceptionMessageRegex */ "Rules must start with RuleList <RL> tag",
- () -> xmlParser.parse(inputStream, Collections.emptyList()));
+ () -> xmlParser.parse(ruleXmlWithNoRuleList.getBytes()));
}
private String generateTagWithAttribute(
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
index eb6698b..8ee5f5f 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
@@ -135,15 +135,16 @@
.isEqualTo(expectedRuleOutputStream.toByteArray());
ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
+ String serializedIndexingBytes =
+ SERIALIZED_START_INDEXING_KEY
+ + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
+ + SERIALIZED_END_INDEXING_KEY
+ + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
byte[] expectedIndexingBytes =
getBytes(
- SERIALIZED_START_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
- + SERIALIZED_END_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */
- 32));
- expectedIndexingOutputStream.write(expectedIndexingBytes);
- expectedIndexingOutputStream.write(expectedIndexingBytes);
+ serializedIndexingBytes
+ + serializedIndexingBytes
+ + serializedIndexingBytes);
expectedIndexingOutputStream.write(expectedIndexingBytes);
assertThat(indexingOutputStream.toByteArray())
.isEqualTo(expectedIndexingOutputStream.toByteArray());
@@ -197,15 +198,19 @@
+ getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
+ SERIALIZED_END_INDEXING_KEY
+ getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForIndexed));
- expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForIndexed));
String expectedIndexingBitsForUnindexed =
SERIALIZED_START_INDEXING_KEY
+ getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
+ SERIALIZED_END_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(
- expectedBits).length, /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForUnindexed));
+ + getBits(
+ DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length,
+ /* numOfBits= */ 32);
+ expectedIndexingOutputStream.write(
+ getBytes(
+ expectedIndexingBitsForIndexed
+ + expectedIndexingBitsForIndexed
+ + expectedIndexingBitsForUnindexed));
+
assertThat(indexingOutputStream.toByteArray())
.isEqualTo(expectedIndexingOutputStream.toByteArray());
}
@@ -513,16 +518,19 @@
// and 225 non-indexed rules..
List<Rule> ruleList = new ArrayList();
for (int count = 0; count < ruleCount; count++) {
- ruleList.add(getRuleWithPackageNameAndSampleInstallerName(
- String.format("%s%04d", packagePrefix, count)));
+ ruleList.add(
+ getRuleWithPackageNameAndSampleInstallerName(
+ String.format("%s%04d", packagePrefix, count)));
}
for (int count = 0; count < ruleCount; count++) {
- ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(
- String.format("%s%04d", appCertificatePrefix, count)));
+ ruleList.add(
+ getRuleWithAppCertificateAndSampleInstallerName(
+ String.format("%s%04d", appCertificatePrefix, count)));
}
for (int count = 0; count < ruleCount; count++) {
- ruleList.add(getNonIndexedRuleWithInstallerName(
- String.format("%s%04d", installerNamePrefix, count)));
+ ruleList.add(
+ getNonIndexedRuleWithInstallerName(
+ String.format("%s%04d", installerNamePrefix, count)));
}
// Serialize the rules.
@@ -543,8 +551,7 @@
int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length;
String expectedIndexingBytesForPackageNameIndexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
for (int count = 0; count < ruleCount; count++) {
String packageName = String.format("%s%04d", packagePrefix, count);
if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
@@ -556,19 +563,17 @@
}
byte[] bytesForPackage =
- getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
- packageName));
+ getBytes(
+ getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+ packageName));
expectedOrderedRuleOutputStream.write(bytesForPackage);
totalBytesWritten += bytesForPackage.length;
}
expectedIndexingBytesForPackageNameIndexed +=
- SERIALIZED_END_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForPackageNameIndexed));
+ SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
String expectedIndexingBytesForAppCertificateIndexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
for (int count = 0; count < ruleCount; count++) {
String appCertificate = String.format("%s%04d", appCertificatePrefix, count);
if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
@@ -580,31 +585,32 @@
}
byte[] bytesForPackage =
- getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
- appCertificate));
+ getBytes(
+ getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+ appCertificate));
expectedOrderedRuleOutputStream.write(bytesForPackage);
totalBytesWritten += bytesForPackage.length;
}
expectedIndexingBytesForAppCertificateIndexed +=
- SERIALIZED_END_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForAppCertificateIndexed));
+ SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
String expectedIndexingBytesForUnindexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
for (int count = 0; count < ruleCount; count++) {
byte[] bytesForPackage =
- getBytes(getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
- String.format("%s%04d", installerNamePrefix, count)));
+ getBytes(
+ getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
+ String.format("%s%04d", installerNamePrefix, count)));
expectedOrderedRuleOutputStream.write(bytesForPackage);
totalBytesWritten += bytesForPackage.length;
}
expectedIndexingBytesForUnindexed +=
- SERIALIZED_END_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForUnindexed));
-
+ SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ expectedIndexingOutputStream.write(
+ getBytes(
+ expectedIndexingBytesForPackageNameIndexed
+ + expectedIndexingBytesForAppCertificateIndexed
+ + expectedIndexingBytesForUnindexed));
assertThat(ruleOutputStream.toByteArray())
.isEqualTo(expectedOrderedRuleOutputStream.toByteArray());
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
index 55fada4..1674422 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
@@ -38,10 +38,8 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.TreeMap;
/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
@RunWith(JUnit4.class)
@@ -140,7 +138,7 @@
List<Rule> ruleList = new ArrayList();
ruleList.add(RULE_WITH_PACKAGE_NAME);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
// Verify the resulting map content.
assertThat(result.keySet())
@@ -157,7 +155,7 @@
List<Rule> ruleList = new ArrayList();
ruleList.add(RULE_WITH_APP_CERTIFICATE);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
assertThat(result.keySet())
.containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -174,7 +172,7 @@
List<Rule> ruleList = new ArrayList();
ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
assertThat(result.keySet())
.containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -189,7 +187,7 @@
List<Rule> ruleList = new ArrayList();
ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
assertThat(result.keySet())
.containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -215,7 +213,7 @@
List<Rule> ruleList = new ArrayList();
ruleList.add(negatedRule);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
assertThat(result.keySet())
.containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -225,7 +223,7 @@
}
@Test
- public void getIndexType_allRulesTogetherInValidOrder() {
+ public void getIndexType_allRulesTogetherSplitCorrectly() {
Rule packageNameRuleA = getRuleWithPackageName("aaa");
Rule packageNameRuleB = getRuleWithPackageName("bbb");
Rule packageNameRuleC = getRuleWithPackageName("ccc");
@@ -243,38 +241,20 @@
ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
assertThat(result.keySet())
.containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
// We check asserts this way to ensure ordering based on package name.
assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc");
- Iterator<String> keySetIterator = result.get(PACKAGE_NAME_INDEXED).keySet().iterator();
- assertThat(keySetIterator.next()).isEqualTo("aaa");
- assertThat(keySetIterator.next()).isEqualTo("bbb");
- assertThat(keySetIterator.next()).isEqualTo("ccc");
- assertThat(result.get(PACKAGE_NAME_INDEXED).get("aaa")).containsExactly(packageNameRuleA);
- assertThat(result.get(PACKAGE_NAME_INDEXED).get("bbb")).containsExactly(packageNameRuleB);
- assertThat(result.get(PACKAGE_NAME_INDEXED).get("ccc")).containsExactly(packageNameRuleC);
// We check asserts this way to ensure ordering based on app certificate.
assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2",
"cert3");
- keySetIterator = result.get(APP_CERTIFICATE_INDEXED).keySet().iterator();
- assertThat(keySetIterator.next()).isEqualTo("cert1");
- assertThat(keySetIterator.next()).isEqualTo("cert2");
- assertThat(keySetIterator.next()).isEqualTo("cert3");
- assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert1")).containsExactly(
- certificateRule1);
- assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert2")).containsExactly(
- certificateRule2);
- assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert3")).containsExactly(
- certificateRule3);
assertThat(result.get(NOT_INDEXED).get("N/A"))
- .containsExactly(
- RULE_WITH_INSTALLER_RESTRICTIONS,
+ .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS,
RULE_WITH_NONSTRING_RESTRICTIONS);
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index 821d97a..670bd81 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -67,13 +67,25 @@
public class PlatformKeyManagerTest {
private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
- private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
+ private static final int MIN_GENERATION_ID = 1000000;
+ private static final int PRIMARY_USER_ID_FIXTURE = 0;
private static final int USER_ID_FIXTURE = 42;
private static final long USER_SID = 4200L;
private static final String KEY_ALGORITHM = "AES";
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias";
+ private static final String ENCRYPTION_KEY_ALIAS_1 =
+ "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/encrypt";
+ private static final String DECRYPTION_KEY_ALIAS_1 =
+ "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/decrypt";
+ private static final String DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1 =
+ "com.android.server.locksettings.recoverablekeystore/platform/0/1000000/decrypt";
+ private static final String ENCRYPTION_KEY_ALIAS_2 =
+ "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/encrypt";
+ private static final String DECRYPTION_KEY_ALIAS_2 =
+ "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/decrypt";
+
@Mock private Context mContext;
@Mock private KeyStoreProxy mKeyStoreProxy;
@Mock private KeyguardManager mKeyguardManager;
@@ -114,7 +126,7 @@
mPlatformKeyManager.init(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_1),
any(),
any());
}
@@ -156,7 +168,7 @@
mPlatformKeyManager.init(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any(),
any());
}
@@ -187,19 +199,33 @@
}
@Test
- public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception {
- mPlatformKeyManager.init(USER_ID_FIXTURE);
+ public void init_primaryUser_createsDecryptKeyWithUnlockedDeviceRequired() throws Exception {
+ mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE);
- assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
+ assertTrue(getDecryptKeyProtectionForPrimaryUser().isUnlockedDeviceRequired());
}
@Test
- public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception {
+ public void init_primaryUser_createsDecryptKeyWithoutAuthenticationRequired() throws Exception {
+ mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE);
+
+ assertFalse(getDecryptKeyProtectionForPrimaryUser().isUserAuthenticationRequired());
+ }
+
+ @Test
+ public void init_secondaryUser_createsDecryptKeyWithoutUnlockedDeviceRequired()
+ throws Exception {
mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(
- USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS,
- getDecryptKeyProtection().getUserAuthenticationValidityDurationSeconds());
+ assertFalse(getDecryptKeyProtection().isUnlockedDeviceRequired());
+ }
+
+ @Test
+ public void init_secondaryUserUser_createsDecryptKeyWithAuthenticationRequired()
+ throws Exception {
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
+
+ assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
}
@Test
@@ -219,7 +245,7 @@
mPlatformKeyManager.init(USER_ID_FIXTURE);
verify(mKeyStoreProxy, never()).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any(),
any());
}
@@ -231,7 +257,7 @@
expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE));
verify(mKeyStoreProxy, never()).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any(),
any());
}
@@ -251,15 +277,15 @@
public void init_savesGenerationIdToDatabase() throws Exception {
mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(1,
+ assertEquals(MIN_GENERATION_ID,
mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE));
}
@Test
- public void init_setsGenerationIdTo1() throws Exception {
+ public void init_setsGenerationId() throws Exception {
mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+ assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
@@ -268,22 +294,20 @@
mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+ assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception {
mPlatformKeyManager.init(USER_ID_FIXTURE);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+ assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
@@ -294,226 +318,194 @@
@Test
public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy.getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any())).thenReturn(generateAndroidKeyStoreKey());
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any());
}
@Test
public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(false); // was removed
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
when(mKeyStoreProxy.getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any())).thenReturn(generateAndroidKeyStoreKey());
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).containsAlias(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+ eq(DECRYPTION_KEY_ALIAS_1));
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(false); // was removed
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).containsAlias(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+ eq(ENCRYPTION_KEY_ALIAS_1));
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_1),
any());
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_1),
any());
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any());
when(mKeyStoreProxy.getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any())).thenReturn(generateAndroidKeyStoreKey());
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).containsAlias(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+ eq(DECRYPTION_KEY_ALIAS_1));
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception {
doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any());
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(false); // was removed
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(false); // was removed
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).containsAlias(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+ eq(ENCRYPTION_KEY_ALIAS_1));
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(false); // was removed
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).containsAlias(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+ eq(ENCRYPTION_KEY_ALIAS_1));
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_1),
any());
}
@@ -523,7 +515,7 @@
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
- assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+ assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
@@ -533,17 +525,17 @@
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
verify(mKeyStoreProxy).deleteEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+ eq(ENCRYPTION_KEY_ALIAS_1));
verify(mKeyStoreProxy).deleteEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+ eq(DECRYPTION_KEY_ALIAS_1));
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
// Removes second generation keys.
verify(mKeyStoreProxy).deleteEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"));
+ eq(ENCRYPTION_KEY_ALIAS_2));
verify(mKeyStoreProxy).deleteEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"));
+ eq(DECRYPTION_KEY_ALIAS_2));
}
@Test
@@ -553,7 +545,7 @@
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_2),
any(),
any());
}
@@ -565,14 +557,14 @@
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any(),
any());
}
private KeyProtection getEncryptKeyProtection() throws Exception {
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_1),
any(),
mProtectionParameterCaptor.capture());
return (KeyProtection) mProtectionParameterCaptor.getValue();
@@ -580,7 +572,15 @@
private KeyProtection getDecryptKeyProtection() throws Exception {
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
+ any(),
+ mProtectionParameterCaptor.capture());
+ return (KeyProtection) mProtectionParameterCaptor.getValue();
+ }
+
+ private KeyProtection getDecryptKeyProtectionForPrimaryUser() throws Exception {
+ verify(mKeyStoreProxy).setEntry(
+ eq(DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1),
any(),
mProtectionParameterCaptor.capture());
return (KeyProtection) mProtectionParameterCaptor.getValue();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 6890017..ac74470 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -29,6 +29,7 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
@@ -84,7 +85,7 @@
import java.util.ArrayList;
import java.util.Map;
import java.util.Random;
-import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -157,7 +158,7 @@
@Mock private PlatformKeyManager mPlatformKeyManager;
@Mock private ApplicationKeyStorage mApplicationKeyStorage;
@Mock private CleanupManager mCleanupManager;
- @Mock private ExecutorService mExecutorService;
+ @Mock private ScheduledExecutorService mExecutorService;
@Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
@@ -1253,7 +1254,7 @@
mRecoverableKeyStoreManager.lockScreenSecretAvailable(
LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11);
- verify(mExecutorService).execute(any());
+ verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
}
@Test
@@ -1263,7 +1264,7 @@
"password".getBytes(),
11);
- verify(mExecutorService).execute(any());
+ verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
}
private static byte[] encryptedApplicationKey(
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index c7dbad8..355cada 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -37,6 +37,7 @@
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.SET_ALL;
import static android.net.NetworkStats.TAG_ALL;
+import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.NetworkTemplate.buildTemplateWifi;
import static android.net.TrafficStats.MB_IN_BYTES;
@@ -111,7 +112,7 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.os.Binder;
import android.os.Handler;
import android.os.INetworkManagementService;
@@ -1758,6 +1759,59 @@
}
/**
+ * Test that when StatsProvider triggers limit reached, new limit will be calculated and
+ * re-armed.
+ */
+ @Test
+ public void testStatsProviderLimitReached() throws Exception {
+ final int CYCLE_DAY = 15;
+
+ final NetworkStats stats = new NetworkStats(0L, 1);
+ stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
+ 2999, 1, 2000, 1, 0);
+ when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats.getTotalBytes());
+ when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats);
+
+ // Get active mobile network in place
+ expectMobileDefaults();
+ mService.updateNetworks();
+ verify(mStatsService).setStatsProviderLimit(TEST_IFACE, Long.MAX_VALUE);
+
+ // Set limit to 10KB.
+ setNetworkPolicies(new NetworkPolicy(
+ sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L,
+ true));
+ postMsgAndWaitForCompletion();
+
+ // Verifies that remaining quota is set to providers.
+ verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L);
+
+ reset(mStatsService);
+
+ // Increase the usage.
+ stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
+ 1000, 1, 999, 1, 0);
+ when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats.getTotalBytes());
+ when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats);
+
+ // Simulates that limit reached fires earlier by provider, but actually the quota is not
+ // yet reached.
+ final NetworkPolicyManagerInternal npmi = LocalServices
+ .getService(NetworkPolicyManagerInternal.class);
+ npmi.onStatsProviderLimitReached("TEST");
+
+ // Verifies that the limit reached leads to a force update and new limit should be set.
+ postMsgAndWaitForCompletion();
+ verify(mStatsService).forceUpdate();
+ postMsgAndWaitForCompletion();
+ verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L - 1999L);
+ }
+
+ /**
* Exhaustively test isUidNetworkingBlocked to output the expected results based on external
* conditions.
*/
@@ -1859,7 +1913,8 @@
if (!roaming) {
nc.addCapability(NET_CAPABILITY_NOT_ROAMING);
}
- nc.setNetworkSpecifier(new StringNetworkSpecifier(String.valueOf(subId)));
+ nc.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(subId).build());
return nc;
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 82bbdcb..cb9d816 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -28,10 +28,12 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.parsing.AndroidPackage;
+import android.content.pm.parsing.ComponentParseUtils;
import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo;
import android.content.pm.parsing.PackageImpl;
import android.content.pm.parsing.ParsingPackage;
+import android.net.Uri;
import android.os.Build;
import android.os.Process;
import android.util.ArrayMap;
@@ -49,6 +51,7 @@
import org.mockito.MockitoAnnotations;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -116,6 +119,15 @@
.addActivity(activity);
}
+ private static ParsingPackage pkgWithProvider(String packageName, String authority) {
+ ComponentParseUtils.ParsedProvider provider = new ComponentParseUtils.ParsedProvider();
+ provider.setPackageName(packageName);
+ provider.setExported(true);
+ provider.setAuthority(authority);
+ return pkg(packageName)
+ .addProvider(provider);
+ }
+
@Before
public void setup() throws Exception {
mExisting = new ArrayMap<>();
@@ -149,6 +161,55 @@
}
@Test
+ public void testQueriesProvider_FilterMatches() {
+ final AppsFilter appsFilter =
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
+
+ PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
+ PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package",
+ new Intent().setData(Uri.parse("content://com.some.authority"))),
+ DUMMY_CALLING_UID);
+
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+ }
+
+ @Test
+ public void testQueriesDifferentProvider_Filters() {
+ final AppsFilter appsFilter =
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
+
+ PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
+ PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package",
+ new Intent().setData(Uri.parse("content://com.some.other.authority"))),
+ DUMMY_CALLING_UID);
+
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+ }
+
+ @Test
+ public void testQueriesProviderWithSemiColon_FilterMatches() {
+ final AppsFilter appsFilter =
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
+
+ PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"),
+ DUMMY_TARGET_UID);
+ PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package",
+ new Intent().setData(Uri.parse("content://com.some.authority"))),
+ DUMMY_CALLING_UID);
+
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+ }
+
+ @Test
public void testQueriesAction_NoMatchingAction_Filters() {
final AppsFilter appsFilter =
new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index f08044c..f5e5e2a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -160,6 +160,31 @@
}
@Test
+ public void testNotifyPrimaryAndSecondary() {
+ List<String> dexFiles = mFooUser0.getBaseAndSplitDexPaths();
+ List<String> secondaries = mFooUser0.getSecondaryDexPaths();
+ int baseAndSplitCount = dexFiles.size();
+ dexFiles.addAll(secondaries);
+
+ notifyDexLoad(mFooUser0, dexFiles, mUser0);
+
+ PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+ assertIsUsedByOtherApps(mFooUser0, pui, false);
+ assertEquals(secondaries.size(), pui.getDexUseInfoMap().size());
+
+ String[] allExpectedContexts = DexoptUtils.processContextForDexLoad(
+ Arrays.asList(mFooUser0.mClassLoader),
+ Arrays.asList(String.join(File.pathSeparator, dexFiles)));
+ String[] secondaryExpectedContexts = Arrays.copyOfRange(allExpectedContexts,
+ baseAndSplitCount, dexFiles.size());
+
+ assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0,
+ secondaryExpectedContexts);
+
+ assertHasDclInfo(mFooUser0, mFooUser0, secondaries);
+ }
+
+ @Test
public void testNotifySecondaryForeign() {
// Foo loads bar secondary files.
List<String> barSecondaries = mBarUser0.getSecondaryDexPaths();
diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
index a83d940..f871203 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -98,7 +98,7 @@
final int[] installedUsers) {
return new PackageRollbackInfo(
new VersionedPackage(packageName, 2), new VersionedPackage(packageName, 1),
- new IntArray(), new ArrayList<>(), false, IntArray.wrap(installedUsers),
+ new IntArray(), new ArrayList<>(), false, false, IntArray.wrap(installedUsers),
new SparseLongArray());
}
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
index 757a884..d0d2edc 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
@@ -87,13 +87,15 @@
+ "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55},"
+ "'versionRolledBackTo':{'packageName':'blah1','longVersionCode':50},'pendingBackups':"
+ "[59,1245,124544],'pendingRestores':[{'userId':498,'appId':32322,'seInfo':'wombles'},"
- + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'installedUsers':"
+ + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'isApkInApex':false,"
+ + "'installedUsers':"
+ "[498468432,1111,98464],'ceSnapshotInodes':[{'userId':1,'ceSnapshotInode':-6},"
+ "{'userId':2222,'ceSnapshotInode':81641654445},{'userId':546546,"
+ "'ceSnapshotInode':345689375}]},{'versionRolledBackFrom':{'packageName':'chips',"
+ "'longVersionCode':28},'versionRolledBackTo':{'packageName':'com.chips.test',"
+ "'longVersionCode':48},'pendingBackups':[5],'pendingRestores':[{'userId':18,"
- + "'appId':-12,'seInfo':''}],'isApex':false,'installedUsers':[55,79],"
+ + "'appId':-12,'seInfo':''}],'isApex':false,'isApkInApex':false,"
+ + "'installedUsers':[55,79],"
+ "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello',"
+ "'longVersionCode':23},{'packageName':'something','longVersionCode':999}],"
+ "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z',"
@@ -155,7 +157,7 @@
PackageRollbackInfo pkgInfo1 =
new PackageRollbackInfo(new VersionedPackage("com.made.up", 18),
new VersionedPackage("com.something.else", 5), new IntArray(),
- new ArrayList<>(), false, new IntArray(), new SparseLongArray());
+ new ArrayList<>(), false, false, new IntArray(), new SparseLongArray());
pkgInfo1.getPendingBackups().add(8);
pkgInfo1.getPendingBackups().add(888);
pkgInfo1.getPendingBackups().add(88885);
@@ -175,7 +177,7 @@
PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(
new VersionedPackage("another.package", 2),
new VersionedPackage("com.test.ing", 48888), new IntArray(), new ArrayList<>(),
- false, new IntArray(), new SparseLongArray());
+ false, false, new IntArray(), new SparseLongArray());
pkgInfo2.getPendingBackups().add(57);
pkgInfo2.getPendingRestores().add(
@@ -205,7 +207,7 @@
PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55),
new VersionedPackage("blah1", 50), new IntArray(), new ArrayList<>(),
- false, new IntArray(), new SparseLongArray());
+ false, false, new IntArray(), new SparseLongArray());
pkgInfo1.getPendingBackups().add(59);
pkgInfo1.getPendingBackups().add(1245);
pkgInfo1.getPendingBackups().add(124544);
@@ -224,7 +226,7 @@
PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(new VersionedPackage("chips", 28),
new VersionedPackage("com.chips.test", 48), new IntArray(), new ArrayList<>(),
- false, new IntArray(), new SparseLongArray());
+ false, false, new IntArray(), new SparseLongArray());
pkgInfo2.getPendingBackups().add(5);
pkgInfo2.getPendingRestores().add(
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
index e368d63..164c883 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -295,7 +295,8 @@
String packageName, long fromVersion, long toVersion, boolean isApex) {
return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
new VersionedPackage(packageName, toVersion),
- new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray());
+ new IntArray(), new ArrayList<>(), isApex, false, new IntArray(),
+ new SparseLongArray());
}
private static class PackageRollbackInfoForPackage implements
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index f8915c0..c56034a 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -37,6 +37,7 @@
import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
import android.media.audio.common.AudioChannelMask;
import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger_middleware.AudioCapabilities;
import android.media.soundtrigger_middleware.ConfidenceLevel;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -59,6 +60,7 @@
import android.os.IHwBinder;
import android.os.IHwInterface;
import android.os.RemoteException;
+import android.util.Pair;
import org.junit.Before;
import org.junit.Test;
@@ -155,7 +157,19 @@
return properties;
}
- private static void validateDefaultProperties(SoundTriggerModuleProperties properties,
+ private static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3(
+ boolean supportConcurrentCapture) {
+ android.hardware.soundtrigger.V2_3.Properties properties =
+ new android.hardware.soundtrigger.V2_3.Properties();
+ properties.base = createDefaultProperties(supportConcurrentCapture);
+ properties.supportedModelArch = "supportedModelArch";
+ properties.audioCapabilities =
+ android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION
+ | android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION;
+ return properties;
+ }
+
+ private void validateDefaultProperties(SoundTriggerModuleProperties properties,
boolean supportConcurrentCapture) {
assertEquals("implementor", properties.implementor);
assertEquals("description", properties.description);
@@ -173,8 +187,23 @@
assertEquals(supportConcurrentCapture, properties.concurrentCapture);
assertTrue(properties.triggerInEvent);
assertEquals(432, properties.powerConsumptionMw);
+
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ assertEquals("supportedModelArch", properties.supportedModelArch);
+ assertEquals(AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION,
+ properties.audioCapabilities);
+ } else {
+ assertEquals("", properties.supportedModelArch);
+ assertEquals(0, properties.audioCapabilities);
+ }
}
+ private void verifyNotGetProperties() throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+ never()).getProperties(any());
+ }
+ }
private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
int hwHandle,
@@ -290,15 +319,38 @@
properties);
return null;
}).when(mHalDriver).getProperties(any());
- mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
+
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_3.Properties properties =
+ createDefaultProperties_2_3(
+ supportConcurrentCapture);
+ ((android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getProperties_2_3Callback)
+ invocation.getArgument(
+ 0)).onValues(0,
+ properties);
+ return null;
+ }).when(driver).getProperties_2_3(any());
+ }
+
+ mService = new SoundTriggerMiddlewareImpl(() -> {
+ return mHalDriver;
+ }, mAudioSessionProvider);
}
- private int loadGenericModel_2_0(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
SoundModel model = createGenericSoundModel();
ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor =
ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
invocation.getArgument(1);
@@ -317,7 +369,8 @@
modelEvent.model = hwHandle;
callback.soundModelCallback(modelEvent, callbackCookie);
return null;
- }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -334,17 +387,23 @@
assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadGenericModel_2_1(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_1(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
(android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
SoundModel model = createGenericSoundModel();
ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
invocation.getArgument(1);
@@ -363,7 +422,8 @@
modelEvent.header.model = hwHandle;
callback.soundModelCallback_2_1(modelEvent, callbackCookie);
return null;
- }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -381,10 +441,12 @@
assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadGenericModel(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
return loadGenericModel_2_1(module, hwHandle);
} else {
@@ -392,12 +454,17 @@
}
}
- private int loadPhraseModel_2_0(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_0(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
PhraseSoundModel model = createPhraseSoundModel();
ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
modelCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
invocation.getArgument(
@@ -419,7 +486,8 @@
modelEvent.model = hwHandle;
callback.soundModelCallback(modelEvent, callbackCookie);
return null;
- }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -451,11 +519,12 @@
| android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
hidlPhrase.recognitionModes);
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_1(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
(android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -463,6 +532,11 @@
ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
modelCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
invocation.getArgument(
@@ -484,7 +558,8 @@
modelEvent.header.model = hwHandle;
callback.soundModelCallback_2_1(modelEvent, callbackCookie);
return null;
- }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -517,10 +592,12 @@
| android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
hidlPhrase.recognitionModes);
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel(
+ ISoundTriggerModule module, int hwHandle) throws RemoteException {
if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
return loadPhraseModel_2_1(module, hwHandle);
} else {
@@ -535,18 +612,14 @@
verify(mAudioSessionProvider).releaseSession(101);
}
- private SoundTriggerHwCallback startRecognition_2_0(ISoundTriggerModule module, int handle,
+ private void startRecognition_2_0(ISoundTriggerModule module, int handle,
int hwHandle) throws RemoteException {
ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig>
configCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class);
- ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
- ArgumentCaptor.forClass(
- android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
- ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
- when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(),
- callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+ when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+ .thenReturn(0);
RecognitionConfig config = createRecognitionConfig();
@@ -569,11 +642,9 @@
assertEquals(234, halLevel.userId);
assertEquals(34, halLevel.levelPercent);
assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray());
-
- return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
}
- private SoundTriggerHwCallback startRecognition_2_1(ISoundTriggerModule module, int handle,
+ private void startRecognition_2_1(ISoundTriggerModule module, int handle,
int hwHandle) throws RemoteException {
android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
(android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -581,13 +652,9 @@
ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig>
configCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class);
- ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
- ArgumentCaptor.forClass(
- android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
- ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
- when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(),
- callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+ when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+ .thenReturn(0);
RecognitionConfig config = createRecognitionConfig();
@@ -611,16 +678,56 @@
assertEquals(34, halLevel.levelPercent);
assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data));
-
- return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
}
- private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle,
+ private void startRecognition_2_3(ISoundTriggerModule module, int handle,
int hwHandle) throws RemoteException {
- if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
- return startRecognition_2_1(module, handle, hwHandle);
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ ArgumentCaptor<android.hardware.soundtrigger.V2_3.RecognitionConfig>
+ configCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_3.RecognitionConfig.class);
+
+ when(driver.startRecognition_2_3(eq(hwHandle), configCaptor.capture())).thenReturn(0);
+
+ RecognitionConfig config = createRecognitionConfig();
+
+ module.startRecognition(handle, config);
+ verify(driver).startRecognition_2_3(eq(hwHandle), any());
+
+ android.hardware.soundtrigger.V2_3.RecognitionConfig halConfigExtended =
+ configCaptor.getValue();
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig_2_1 =
+ halConfigExtended.base;
+
+ assertTrue(halConfig_2_1.header.captureRequested);
+ assertEquals(102, halConfig_2_1.header.captureHandle);
+ assertEquals(103, halConfig_2_1.header.captureDevice);
+ assertEquals(1, halConfig_2_1.header.phrases.size());
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+ halConfig_2_1.header.phrases.get(0);
+ assertEquals(123, halPhraseExtra.id);
+ assertEquals(4, halPhraseExtra.confidenceLevel);
+ assertEquals(5, halPhraseExtra.recognitionModes);
+ assertEquals(1, halPhraseExtra.levels.size());
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+ assertEquals(234, halLevel.userId);
+ assertEquals(34, halLevel.levelPercent);
+ assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
+ HidlMemoryUtil.hidlMemoryToByteArray(halConfig_2_1.data));
+ assertEquals(AudioCapabilities.ECHO_CANCELLATION
+ | AudioCapabilities.NOISE_SUPPRESSION, halConfigExtended.audioCapabilities);
+ }
+
+ private void startRecognition(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ startRecognition_2_3(module, handle, hwHandle);
+ } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ startRecognition_2_1(module, handle, hwHandle);
} else {
- return startRecognition_2_0(module, handle, hwHandle);
+ startRecognition_2_0(module, handle, hwHandle);
}
}
@@ -635,6 +742,8 @@
config.phraseRecognitionExtras[0].levels[0].userId = 234;
config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
config.data = new byte[]{5, 4, 3, 2, 1};
+ config.audioCapabilities = AudioCapabilities.ECHO_CANCELLATION
+ | AudioCapabilities.NOISE_SUPPRESSION;
return config;
}
@@ -650,6 +759,9 @@
if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver,
never()).startRecognition_2_1(anyInt(), any(), any(), anyInt());
+ } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+ never()).startRecognition_2_3(anyInt(), any());
}
}
@@ -688,12 +800,12 @@
@Override
public boolean linkToDeath(DeathRecipient recipient, long cookie) {
- throw new UnsupportedOperationException();
+ return true;
}
@Override
public boolean unlinkToDeath(DeathRecipient recipient) {
- throw new UnsupportedOperationException();
+ return true;
}
};
@@ -716,6 +828,7 @@
SoundTriggerModuleProperties properties = allDescriptors[0].properties;
validateDefaultProperties(properties, true);
+ verifyNotGetProperties();
}
@Test
@@ -760,7 +873,7 @@
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 7;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
unloadModel(module, handle, hwHandle);
module.detach();
}
@@ -772,7 +885,7 @@
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 73;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
unloadModel(module, handle, hwHandle);
module.detach();
}
@@ -785,7 +898,7 @@
// Load the model.
final int hwHandle = 7;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -806,7 +919,7 @@
// Load the model.
final int hwHandle = 67;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -827,10 +940,12 @@
// Load the model.
final int hwHandle = 7;
- int handle = loadGenericModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Signal a capture from the driver.
hwCallback.sendRecognitionEvent(hwHandle,
@@ -856,10 +971,12 @@
// Load the model.
final int hwHandle = 7;
- int handle = loadPhraseModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Signal a capture from the driver.
hwCallback.sendPhraseRecognitionEvent(hwHandle,
@@ -892,10 +1009,12 @@
// Load the model.
final int hwHandle = 17;
- int handle = loadGenericModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Force a trigger.
module.forceRecognitionEvent(handle);
@@ -935,10 +1054,12 @@
// Load the model.
final int hwHandle = 17;
- int handle = loadPhraseModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Force a trigger.
module.forceRecognitionEvent(handle);
@@ -975,7 +1096,7 @@
// Load the model.
final int hwHandle = 11;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1023,7 +1144,7 @@
// Load the model.
final int hwHandle = 11;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1071,7 +1192,7 @@
// Load the model.
final int hwHandle = 13;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1107,7 +1228,7 @@
// Load the model.
final int hwHandle = 13;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1144,7 +1265,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 12;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
doAnswer((Answer<Void>) invocation -> {
android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1180,7 +1301,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 13;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
ModelParameter.THRESHOLD_FACTOR);
@@ -1201,7 +1322,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 13;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1234,7 +1355,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 14;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
@@ -1266,7 +1387,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 17;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
when(driver.setParameter(hwHandle,
android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index aaf9799..d940a6a 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -18,7 +18,6 @@
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;
@@ -26,7 +25,6 @@
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
import android.icu.util.Calendar;
import android.icu.util.GregorianCalendar;
import android.icu.util.TimeZone;
@@ -78,8 +76,7 @@
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
- mScript.verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion);
}
@@ -118,8 +115,7 @@
mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime());
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
}
@@ -146,8 +142,7 @@
mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime());
mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis3, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion3);
}
}
@@ -175,8 +170,7 @@
mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phone1Id, null)
.assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
}
@@ -193,8 +187,7 @@
mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion);
}
@@ -227,8 +220,7 @@
mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
}
}
@@ -265,8 +257,7 @@
mScript.simulateTimePassing();
long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1);
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
// The UTC time increment should be larger than the system clock update threshold so we
@@ -304,8 +295,7 @@
PhoneTimeSuggestion timeSuggestion4 =
createPhoneTimeSuggestion(phoneId, utcTime4);
mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis4, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion4);
}
@@ -339,8 +329,7 @@
// Turn on auto time detection.
mScript.simulateAutoTimeDetectionToggle()
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
// Turn off auto time detection.
@@ -367,8 +356,7 @@
// Turn on auto time detection.
mScript.simulateAutoTimeDetectionToggle()
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis2, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
}
@@ -388,7 +376,7 @@
mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phoneSuggestion)
.verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectedNetworkBroadcast */)
+ expectedSystemClockMillis /* expectedNetworkBroadcast */)
.assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
// Look inside and check what the strategy considers the current best phone suggestion.
@@ -416,8 +404,7 @@
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
mScript.simulateManualTimeSuggestion(timeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
}
@Test
@@ -439,8 +426,7 @@
long expectedAutoClockMillis =
mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Simulate the passage of time.
@@ -463,8 +449,7 @@
long expectedManualClockMillis =
mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime());
mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedManualClockMillis, false /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Simulate the passage of time.
@@ -475,8 +460,7 @@
expectedAutoClockMillis =
mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
- mScript.verifySystemClockWasSetAndResetCallTracking(
- expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+ mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Switch back to manual - nothing should happen to the clock.
@@ -514,8 +498,7 @@
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
mScript.simulateNetworkTimeSuggestion(timeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
}
@Test
@@ -550,8 +533,7 @@
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulateNetworkTimeSuggestion(networkTimeSuggestion1)
.verifySystemClockWasSetAndResetCallTracking(
- mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()),
- false /* expectNetworkBroadcast */);
+ mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()));
// Check internal state.
mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null)
@@ -570,8 +552,7 @@
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(
- mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()),
- true /* expectNetworkBroadcast */);
+ mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()));
// Check internal state.
mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
@@ -622,8 +603,7 @@
// Verify the latest network time now wins.
mScript.verifySystemClockWasSetAndResetCallTracking(
- mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()),
- false /* expectNetworkTimeBroadcast */);
+ mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()));
// Check internal state.
mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
@@ -645,7 +625,6 @@
// Tracking operations.
private boolean mSystemClockWasSet;
- private Intent mBroadcastSent;
@Override
public int systemClockUpdateThresholdMillis() {
@@ -672,7 +651,6 @@
@Override
public long systemClockMillis() {
- assertWakeLockAcquired();
return mSystemClockMillis;
}
@@ -689,12 +667,6 @@
mWakeLockAcquired = false;
}
- @Override
- public void sendStickyBroadcast(Intent intent) {
- assertNotNull(intent);
- mBroadcastSent = intent;
- }
-
// Methods below are for managing the fake's behavior.
void pokeSystemClockUpdateThreshold(int thresholdMillis) {
@@ -739,17 +711,8 @@
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
}
- void verifyIntentWasBroadcast() {
- assertTrue(mBroadcastSent != null);
- }
-
- void verifyIntentWasNotBroadcast() {
- assertNull(mBroadcastSent);
- }
-
void resetCallTracking() {
mSystemClockWasSet = false;
- mBroadcastSent = null;
}
private void assertWakeLockAcquired() {
@@ -832,17 +795,12 @@
Script verifySystemClockWasNotSetAndResetCallTracking() {
mFakeCallback.verifySystemClockNotSet();
- mFakeCallback.verifyIntentWasNotBroadcast();
mFakeCallback.resetCallTracking();
return this;
}
- Script verifySystemClockWasSetAndResetCallTracking(
- long expectedSystemClockMillis, boolean expectNetworkBroadcast) {
+ Script verifySystemClockWasSetAndResetCallTracking(long expectedSystemClockMillis) {
mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis);
- if (expectNetworkBroadcast) {
- mFakeCallback.verifyIntentWasBroadcast();
- }
mFakeCallback.resetCallTracking();
return this;
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 1dd7e64..6aca58f 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -23,7 +23,8 @@
import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION;
import static android.app.usage.UsageEvents.Event.USER_INTERACTION;
import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
@@ -501,12 +502,18 @@
// Can force to NEVER
mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
- REASON_MAIN_FORCED);
+ REASON_MAIN_FORCED_BY_USER);
assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
- // Prediction can't override FORCED reason
+ // Prediction can't override FORCED reasons
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_FORCED_BY_SYSTEM);
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+ REASON_MAIN_PREDICTED);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
- REASON_MAIN_FORCED);
+ REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
@@ -631,7 +638,7 @@
mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100;
// Make sure app is in NEVER bucket
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
- REASON_MAIN_FORCED);
+ REASON_MAIN_FORCED_BY_USER);
mController.checkIdleStates(USER_ID);
assertBucket(STANDBY_BUCKET_NEVER);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index eb45960..f5af3ec 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -20,7 +20,6 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static org.mockito.Matchers.any;
@@ -65,11 +64,11 @@
NotificationChannel updatedChannel =
new NotificationChannel("a", "", IMPORTANCE_HIGH);
- when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(false)))
+ when(mConfig.getConversationNotificationChannel(
+ any(), anyInt(), eq("a"), eq(null), eq(true), eq(false)))
.thenReturn(updatedChannel);
assertNull(extractor.process(r));
assertEquals(updatedChannel, r.getChannel());
}
-
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 172df99..2ac4642 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -112,6 +112,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -133,6 +134,7 @@
import android.testing.TestablePermissions;
import android.testing.TestableResources;
import android.text.Html;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -469,16 +471,17 @@
mTestableLooper.processAllMessages();
}
- private void setUpPrefsForBubbles(boolean globalEnabled, boolean pkgEnabled,
- boolean channelEnabled) {
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
- when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
- when(mPreferencesHelper.getNotificationChannel(
- anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
- mTestNotificationChannel);
- when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
- mTestNotificationChannel.getImportance());
+ private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled,
+ boolean pkgEnabled, boolean channelEnabled) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_BUBBLES, globalEnabled ? 1 : 0);
+ mService.mPreferencesHelper.updateBubblesEnabled();
+ assertEquals(globalEnabled, mService.mPreferencesHelper.bubblesEnabled());
+ try {
+ mBinderService.setBubblesAllowed(pkg, uid, pkgEnabled);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
mTestNotificationChannel.setAllowBubbles(channelEnabled);
}
@@ -1761,8 +1764,8 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mPreferencesHelper, times(1)).getNotificationChannel(
- anyString(), anyInt(), eq("foo"), anyBoolean());
+ verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
+ anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
}
@Test
@@ -1776,8 +1779,9 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
0, generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mPreferencesHelper, times(1)).getNotificationChannel(
- anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
+ verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
+ anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
+ anyBoolean(), anyBoolean());
}
@Test
@@ -3210,9 +3214,11 @@
final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, 2, true);
mService.mNotificationDelegate.onNotificationVisibilityChanged(
new NotificationVisibility[] {nv}, new NotificationVisibility[]{});
+ verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(true));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
mService.mNotificationDelegate.onNotificationVisibilityChanged(
new NotificationVisibility[] {}, new NotificationVisibility[]{nv});
+ verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(false));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
}
@@ -4463,6 +4469,16 @@
}
@Test
+ public void testOnPanelRevealedAndHidden() {
+ int items = 5;
+ mService.mNotificationDelegate.onPanelRevealed(false, items);
+ verify(mAssistants, times(1)).onPanelRevealed(eq(items));
+
+ mService.mNotificationDelegate.onPanelHidden();
+ verify(mAssistants, times(1)).onPanelHidden();
+ }
+
+ @Test
public void testOnNotificationSmartReplySent() {
final int replyIndex = 2;
final String reply = "Hello";
@@ -4744,7 +4760,7 @@
@Test
public void testFlagBubble() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr =
generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble");
@@ -4763,7 +4779,7 @@
@Test
public void testFlagBubble_noFlag_appNotAllowed() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, false /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubble_noFlag_appNotAllowed");
@@ -4782,7 +4798,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_whenAppForeground() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif with bubble metadata but not our other misc requirements
Notification.Builder nb = new Notification.Builder(mContext,
@@ -4810,7 +4826,7 @@
@Test
public void testFlagBubbleNotifs_flag_messaging() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_flag_messaging");
@@ -4827,7 +4843,7 @@
@Test
public void testFlagBubbleNotifs_flag_phonecall() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4863,7 +4879,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noForegroundService() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4896,7 +4912,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noPerson() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4927,7 +4943,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noCategory() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4962,7 +4978,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_messaging_appNotAllowed() throws RemoteException {
// Bubbles are NOT allowed!
- setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_noFlag_messaging_appNotAllowed");
@@ -4980,7 +4996,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_notBubble() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Messaging notif WITHOUT bubble metadata
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
@@ -5004,7 +5020,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed() throws RemoteException {
// Bubbles are allowed except on this channel
- setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed");
@@ -5022,7 +5038,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_notAllowed() throws RemoteException {
// Bubbles are not allowed!
- setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, false /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -5044,10 +5060,9 @@
nb.build(), new UserHandle(mUid), null, 0);
// Make sure it has foreground service
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
- nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
waitForIdle();
// yes phone call, yes person, yes foreground service, but not allowed, no bubble
@@ -5058,7 +5073,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed() throws RemoteException {
// Bubbles are allowed, but not on channel.
- setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -5266,7 +5281,7 @@
@Test
public void testNotificationBubbleChanged_false() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif with bubble metadata
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
@@ -5297,7 +5312,7 @@
@Test
public void testNotificationBubbleChanged_true() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif that is not a bubble
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
@@ -5334,7 +5349,7 @@
@Test
public void testNotificationBubbleChanged_true_notAllowed() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif that is not a bubble
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
@@ -5533,7 +5548,7 @@
@Test
public void testNotificationBubbles_disabled_lowRamDevice() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// And we are low ram
when(mActivityManager.isLowRamDevice()).thenReturn(true);
@@ -5617,7 +5632,7 @@
public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testNotificationBubbles_flagAutoExpandForeground_fails_notForeground");
@@ -5647,7 +5662,7 @@
public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground()
throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground");
@@ -5677,7 +5692,7 @@
public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
true /* summaryAutoCancel */);
@@ -5700,7 +5715,7 @@
public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
true /* summaryAutoCancel */);
@@ -5802,4 +5817,79 @@
verify(mHistoryManager, times(1)).addNotification(any());
}
+
+ @Test
+ public void createConversationNotificationChannel() throws Exception {
+ NotificationChannel original = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ original.setAllowBubbles(!original.canBubble());
+ original.setShowBadge(!original.canShowBadge());
+
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ NotificationChannel orig = NotificationChannel.CREATOR.createFromParcel(parcel);
+ assertEquals(original, orig);
+ assertFalse(TextUtils.isEmpty(orig.getName()));
+
+ mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+ orig)));
+
+ mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend");
+
+ NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel(
+ PKG, 0, PKG, original.getId(), false, "friend");
+
+ assertEquals(original.getName(), friendChannel.getName());
+ assertEquals(original.getId(), friendChannel.getParentChannelId());
+ assertEquals("friend", friendChannel.getConversationId());
+ assertEquals(null, original.getConversationId());
+ assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
+ assertEquals(original.canBubble(), friendChannel.canBubble());
+ assertFalse(original.getId().equals(friendChannel.getId()));
+ assertNotNull(friendChannel.getId());
+ }
+
+ @Test
+ public void deleteConversationNotificationChannels() throws Exception {
+ NotificationChannel messagesParent =
+ new NotificationChannel("messages", "messages", IMPORTANCE_HIGH);
+ Parcel msgParcel = Parcel.obtain();
+ messagesParent.writeToParcel(msgParcel, 0);
+ msgParcel.setDataPosition(0);
+
+ NotificationChannel callsParent =
+ new NotificationChannel("calls", "calls", IMPORTANCE_HIGH);
+ Parcel callParcel = Parcel.obtain();
+ callsParent.writeToParcel(callParcel, 0);
+ callParcel.setDataPosition(0);
+
+ mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+ messagesParent, callsParent)));
+
+ String conversationId = "friend";
+
+ mBinderService.createConversationNotificationChannelForPackage(
+ PKG, mUid, NotificationChannel.CREATOR.createFromParcel(msgParcel), conversationId);
+ mBinderService.createConversationNotificationChannelForPackage(
+ PKG, mUid, NotificationChannel.CREATOR.createFromParcel(callParcel),
+ conversationId);
+
+ NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel(
+ PKG, 0, PKG, messagesParent.getId(), false, conversationId);
+ NotificationChannel callsChild = mBinderService.getConversationNotificationChannel(
+ PKG, 0, PKG, callsParent.getId(), false, conversationId);
+
+ assertEquals(messagesParent.getId(), messagesChild.getParentChannelId());
+ assertEquals(conversationId, messagesChild.getConversationId());
+
+ assertEquals(callsParent.getId(), callsChild.getParentChannelId());
+ assertEquals(conversationId, callsChild.getConversationId());
+
+ mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId);
+
+ assertNull(mBinderService.getConversationNotificationChannel(
+ PKG, 0, PKG, messagesParent.getId(), false, conversationId));
+ assertNull(mBinderService.getConversationNotificationChannel(
+ PKG, 0, PKG, callsParent.getId(), false, conversationId));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8961796..7ac45f0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -224,6 +225,26 @@
assertEquals(expected.getGroup(), actual.getGroup());
assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
assertEquals(expected.getLightColor(), actual.getLightColor());
+ assertEquals(expected.getParentChannelId(), actual.getParentChannelId());
+ assertEquals(expected.getConversationId(), actual.getConversationId());
+ }
+
+ private void compareChannelsParentChild(NotificationChannel parent,
+ NotificationChannel actual, String conversationId) {
+ assertEquals(parent.getName(), actual.getName());
+ assertEquals(parent.getDescription(), actual.getDescription());
+ assertEquals(parent.shouldVibrate(), actual.shouldVibrate());
+ assertEquals(parent.shouldShowLights(), actual.shouldShowLights());
+ assertEquals(parent.getImportance(), actual.getImportance());
+ assertEquals(parent.getLockscreenVisibility(), actual.getLockscreenVisibility());
+ assertEquals(parent.getSound(), actual.getSound());
+ assertEquals(parent.canBypassDnd(), actual.canBypassDnd());
+ assertTrue(Arrays.equals(parent.getVibrationPattern(), actual.getVibrationPattern()));
+ assertEquals(parent.getGroup(), actual.getGroup());
+ assertEquals(parent.getAudioAttributes(), actual.getAudioAttributes());
+ assertEquals(parent.getLightColor(), actual.getLightColor());
+ assertEquals(parent.getId(), actual.getParentChannelId());
+ assertEquals(conversationId, actual.getConversationId());
}
private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
@@ -333,6 +354,7 @@
channel2.setGroup(ncg.getId());
channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
channel2.setLightColor(Color.BLUE);
+ channel2.setConversationId("id1", "conversation");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);
@@ -2786,4 +2808,52 @@
assertEquals(user10Importance, mHelper.getNotificationChannel(
pkg, uidList10[0], channelId, false).getImportance());
}
+
+ @Test
+ public void testGetConversationNotificationChannel() {
+ String conversationId = "friend";
+
+ NotificationChannel parent =
+ new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+ NotificationChannel friend = new NotificationChannel(String.format(
+ CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId),
+ "messages", IMPORTANCE_DEFAULT);
+ friend.setConversationId(parent.getId(), conversationId);
+ mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+
+ compareChannelsParentChild(parent, mHelper.getConversationNotificationChannel(
+ PKG_O, UID_O, parent.getId(), conversationId, false, false), conversationId);
+ }
+
+ @Test
+ public void testGetNotificationChannel_conversationProvidedByNotCustomizedYet() {
+ String conversationId = "friend";
+
+ NotificationChannel parent =
+ new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+ compareChannels(parent, mHelper.getConversationNotificationChannel(
+ PKG_O, UID_O, parent.getId(), conversationId, true, false));
+ }
+
+ @Test
+ public void testConversationNotificationChannelsRequireParents() {
+ String parentId = "does not exist";
+ String conversationId = "friend";
+
+ NotificationChannel friend = new NotificationChannel(String.format(
+ CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId),
+ "messages", IMPORTANCE_DEFAULT);
+ friend.setConversationId(parentId, conversationId);
+
+ try {
+ mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+ fail("allowed creation of conversation channel without a parent");
+ } catch (IllegalArgumentException e) {
+ // good
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 399cf49..135d005 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -44,6 +45,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.app.BlockedAppActivity;
import com.android.internal.app.HarmfulAppWarningActivity;
import com.android.internal.app.SuspendedAppActivity;
import com.android.internal.app.UnlaunchableAppActivity;
@@ -105,6 +107,8 @@
private PackageManagerService mPackageManager;
@Mock
private ActivityManagerInternal mAmInternal;
+ @Mock
+ private LockTaskController mLockTaskController;
private ActivityStartInterceptor mInterceptor;
private ActivityInfo mAInfo = new ActivityInfo();
@@ -145,6 +149,13 @@
when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
.thenReturn(null);
+ // Mock LockTaskController
+ mAInfo.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+ when(mService.getLockTaskController()).thenReturn(mLockTaskController);
+ when(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+ .thenReturn(true);
+
// Initialise activity info
mAInfo.applicationInfo = new ApplicationInfo();
mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
@@ -196,6 +207,18 @@
}
@Test
+ public void testInterceptLockTaskModeViolationPackage() {
+ when(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+ .thenReturn(false);
+
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+
+ assertTrue(BlockedAppActivity.createIntent(TEST_USER_ID, TEST_PACKAGE_NAME)
+ .filterEquals(mInterceptor.mIntent));
+ }
+
+ @Test
public void testInterceptQuietProfile() {
// GIVEN that the user the activity is starting as is currently in quiet mode
when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 079c49f..d22502d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -27,6 +27,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -153,6 +154,19 @@
}
@Test
+ public void testContainerChanges() {
+ removeGlobalMinSizeRestriction();
+ final ActivityStack stack = new StackBuilder(mRootWindowContainer)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ final Task task = stack.getTopMostTask();
+ WindowContainerTransaction t = new WindowContainerTransaction();
+ assertTrue(task.isFocusable());
+ t.setFocusable(stack.mRemoteToken, false);
+ mService.applyContainerTransaction(t);
+ assertFalse(task.isFocusable());
+ }
+
+ @Test
public void testDisplayWindowListener() {
final ArrayList<Integer> added = new ArrayList<>();
final ArrayList<Integer> changed = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 09ac9ce..d819b1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -63,6 +63,26 @@
assertEquals(Insets.of(0, 100, 0, 0),
mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
false /* ignoreVisibility */));
+ assertEquals(Insets.of(0, 100, 0, 0),
+ mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
+ }
+
+ @Test
+ public void testPostLayout_givenInsets() {
+ final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+ ime.getFrameLw().set(0, 0, 500, 100);
+ ime.getGivenContentInsetsLw().set(0, 0, 0, 60);
+ ime.getGivenVisibleInsetsLw().set(0, 0, 0, 75);
+ ime.mHasSurface = true;
+ mProvider.setWindow(ime, null);
+ mProvider.onPostLayout();
+ assertEquals(new Rect(0, 0, 500, 40), mProvider.getSource().getFrame());
+ assertEquals(new Rect(0, 0, 500, 25), mProvider.getSource().getVisibleFrame());
+ assertEquals(Insets.of(0, 40, 0, 0),
+ mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
+ false /* ignoreVisibility */));
+ assertEquals(Insets.of(0, 25, 0, 0),
+ mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index 039ff60..75ec53d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -29,6 +29,9 @@
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
import static android.os.Process.SYSTEM_UID;
import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
@@ -693,6 +696,38 @@
assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
}
+ @Test
+ public void testIsActivityAllowed() {
+ // WHEN lock task mode is not enabled
+ assertTrue(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+ // WHEN lock task mode is enabled
+ Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+ mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+
+ // package with LOCK_TASK_LAUNCH_MODE_ALWAYS should always be allowed
+ assertTrue(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_ALWAYS));
+
+ // unwhitelisted package should not be allowed
+ assertFalse(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+ // update the whitelist
+ String[] whitelist = new String[] { TEST_PACKAGE_NAME };
+ mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+
+ // whitelisted package should be allowed
+ assertTrue(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+ // package with LOCK_TASK_LAUNCH_MODE_NEVER should never be allowed
+ assertFalse(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_NEVER));
+ }
+
private Task getTask(int lockTaskAuth) {
return getTask(TEST_PACKAGE_NAME, lockTaskAuth);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 04d79ca..ea8d082 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -253,12 +253,12 @@
// Under split screen primary we should be focusable when not minimized
mRootWindowContainer.setDockedStackMinimized(false);
- assertTrue(stack.isFocusable());
+ assertTrue(stack.isTopActivityFocusable());
assertTrue(activity.isFocusable());
// Under split screen primary we should not be focusable when minimized
mRootWindowContainer.setDockedStackMinimized(true);
- assertFalse(stack.isFocusable());
+ assertFalse(stack.isTopActivityFocusable());
assertFalse(activity.isFocusable());
final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack(
@@ -267,19 +267,19 @@
.setStack(pinnedStack).build();
// We should not be focusable when in pinned mode
- assertFalse(pinnedStack.isFocusable());
+ assertFalse(pinnedStack.isTopActivityFocusable());
assertFalse(pinnedActivity.isFocusable());
// Add flag forcing focusability.
pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE;
// We should not be focusable when in pinned mode
- assertTrue(pinnedStack.isFocusable());
+ assertTrue(pinnedStack.isTopActivityFocusable());
assertTrue(pinnedActivity.isFocusable());
// Without the overridding activity, stack should not be focusable.
pinnedStack.removeChild(pinnedActivity.getTask(), "testFocusability");
- assertFalse(pinnedStack.isFocusable());
+ assertFalse(pinnedStack.isTopActivityFocusable());
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
new file mode 100644
index 0000000..8d2da1e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.view.ITaskOrganizer;
+import android.view.SurfaceControl;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskOrganizer}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:TaskOrganizerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TaskOrganizerTests extends WindowTestsBase {
+ private ITaskOrganizer makeAndRegisterMockOrganizer() {
+ final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
+ when(organizer.asBinder()).thenReturn(new Binder());
+
+ mWm.mAtmService.registerTaskOrganizer(organizer, WINDOWING_MODE_PINNED);
+
+ return organizer;
+ }
+
+ @Test
+ public void testAppearVanish() throws RemoteException {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+ task.setTaskOrganizer(organizer);
+ verify(organizer).taskAppeared(any(), any());
+ assertTrue(task.isControlledByTaskOrganizer());
+
+ task.removeImmediately();
+ verify(organizer).taskVanished(any());
+ }
+
+ @Test
+ public void testSwapOrganizer() throws RemoteException {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+ final ITaskOrganizer organizer2 = makeAndRegisterMockOrganizer();
+
+ task.setTaskOrganizer(organizer);
+ verify(organizer).taskAppeared(any(), any());
+ task.setTaskOrganizer(organizer2);
+ verify(organizer).taskVanished(any());
+ verify(organizer2).taskAppeared(any(), any());
+ }
+
+ @Test
+ public void testClearOrganizer() throws RemoteException {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+ task.setTaskOrganizer(organizer);
+ verify(organizer).taskAppeared(any(), any());
+ assertTrue(task.isControlledByTaskOrganizer());
+
+ task.setTaskOrganizer(null);
+ verify(organizer).taskVanished(any());
+ assertFalse(task.isControlledByTaskOrganizer());
+ }
+
+ @Test
+ public void testTransferStackToOrganizer() throws RemoteException {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final Task task2 = createTaskInStack(stack, 0 /* userId */);
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+ stack.transferToTaskOrganizer(organizer);
+
+ verify(organizer, times(2)).taskAppeared(any(), any());
+ assertTrue(task.isControlledByTaskOrganizer());
+ assertTrue(task2.isControlledByTaskOrganizer());
+
+ stack.transferToTaskOrganizer(null);
+
+ verify(organizer, times(2)).taskVanished(any());
+ assertFalse(task.isControlledByTaskOrganizer());
+ assertFalse(task2.isControlledByTaskOrganizer());
+ }
+
+ @Test
+ public void testRegisterTaskOrganizerTaskWindowingModeChanges() throws RemoteException {
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ task.setWindowingMode(WINDOWING_MODE_PINNED);
+ verify(organizer).taskAppeared(any(), any());
+ assertTrue(task.isControlledByTaskOrganizer());
+
+ task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ verify(organizer).taskVanished(any());
+ assertFalse(task.isControlledByTaskOrganizer());
+ }
+
+ @Test
+ public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException {
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final Task task2 = createTaskInStack(stack, 0 /* userId */);
+ stack.setWindowingMode(WINDOWING_MODE_PINNED);
+ verify(organizer, times(2)).taskAppeared(any(), any());
+ assertTrue(task.isControlledByTaskOrganizer());
+ assertTrue(task2.isControlledByTaskOrganizer());
+
+ stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ verify(organizer, times(2)).taskVanished(any());
+ assertFalse(task.isControlledByTaskOrganizer());
+ assertFalse(task2.isControlledByTaskOrganizer());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
deleted file mode 100644
index 9cda084..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
+++ /dev/null
@@ -1,153 +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.wm.utils;
-
-import static android.graphics.Bitmap.Config.ARGB_8888;
-
-import static org.junit.Assert.assertEquals;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.ColorSpace;
-import android.graphics.GraphicBuffer;
-import android.graphics.Matrix;
-import android.graphics.PointF;
-import android.view.Surface;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class RotationAnimationUtilsTest {
-
- private static final int BITMAP_HEIGHT = 100;
- private static final int BITMAP_WIDTH = 100;
- private static final int POINT_WIDTH = 1000;
- private static final int POINT_HEIGHT = 2000;
-
- private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
- private Matrix mMatrix;
-
- @Before
- public void setup() {
- mMatrix = new Matrix();
- }
-
- @Test
- public void blackLuma() {
- Bitmap swBitmap = createBitmap(0);
- GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
- float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
- assertEquals(0, borderLuma, 0);
- }
-
- @Test
- public void whiteLuma() {
- Bitmap swBitmap = createBitmap(1);
- GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
- float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
- assertEquals(1, borderLuma, 0);
- }
-
- @Test
- public void whiteImageBlackBorderLuma() {
- Bitmap swBitmap = createBitmap(1);
- setBorderLuma(swBitmap, 0);
- GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
- float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
- assertEquals(0, borderLuma, 0);
- }
-
- @Test
- public void blackImageWhiteBorderLuma() {
- Bitmap swBitmap = createBitmap(0);
- setBorderLuma(swBitmap, 1);
- GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
- float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
- assertEquals(1, borderLuma, 0);
- }
-
- @Test
- public void rotate_0_bottomRight() {
- RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_0,
- POINT_WIDTH, POINT_HEIGHT, mMatrix);
- PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
- assertEquals(POINT_WIDTH, newPoints.x, 0);
- assertEquals(POINT_HEIGHT, newPoints.y, 0);
- }
-
- @Test
- public void rotate_90_bottomRight() {
- RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_90,
- POINT_WIDTH, POINT_HEIGHT, mMatrix);
- PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
- assertEquals(0, newPoints.x, 0);
- assertEquals(POINT_WIDTH, newPoints.y, 0);
- }
-
- @Test
- public void rotate_180_bottomRight() {
- RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_180,
- POINT_WIDTH, POINT_HEIGHT, mMatrix);
- PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
- assertEquals(0, newPoints.x, 0);
- assertEquals(0, newPoints.y, 0);
- }
-
- @Test
- public void rotate_270_bottomRight() {
- RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_270,
- POINT_WIDTH, POINT_HEIGHT, mMatrix);
- PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
- assertEquals(POINT_HEIGHT, newPoints.x, 0);
- assertEquals(0, newPoints.y, 0);
- }
-
- private PointF checkMappedPoints(int x, int y) {
- final float[] fs = new float[] {x, y};
- mMatrix.mapPoints(fs);
- return new PointF(fs[0], fs[1]);
- }
-
- private Bitmap createBitmap(float luma) {
- Bitmap bitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, ARGB_8888);
- for (int i = 0; i < BITMAP_WIDTH; i++) {
- for (int j = 0; j < BITMAP_HEIGHT; j++) {
- bitmap.setPixel(i, j, Color.argb(1, luma, luma, luma));
- }
- }
- return bitmap;
- }
-
- private GraphicBuffer swBitmapToGraphicsBuffer(Bitmap swBitmap) {
- Bitmap hwBitmap = swBitmap.copy(Bitmap.Config.HARDWARE, false);
- return hwBitmap.createGraphicBufferHandle();
- }
-
- private void setBorderLuma(Bitmap swBitmap, float luma) {
- int i;
- int width = swBitmap.getWidth();
- int height = swBitmap.getHeight();
- for (i = 0; i < width; i++) {
- swBitmap.setPixel(i, 0, Color.argb(1, luma, luma, luma));
- swBitmap.setPixel(i, height - 1, Color.argb(1, luma, luma, luma));
- }
- for (i = 0; i < height; i++) {
- swBitmap.setPixel(0, i, Color.argb(1, luma, luma, luma));
- swBitmap.setPixel(width - 1, i, Color.argb(1, luma, luma, luma));
- }
- }
-}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 8397aa4..b8cd378 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2069,6 +2069,13 @@
}
@Override
+ public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime,
+ boolean shouldObfuscateInstantApps) {
+ return UsageStatsService.this.queryEvents(
+ userId, beginTime, endTime, shouldObfuscateInstantApps);
+ }
+
+ @Override
public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime);
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
index f7cd6a3..9906585 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
@@ -21,12 +21,10 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
-import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
-import android.text.TextUtils;
import android.util.Slog;
-import java.util.Locale;
+import java.io.PrintWriter;
import java.util.UUID;
/**
@@ -39,7 +37,7 @@
static final boolean DBG = false;
private static final String NAME = "st_sound_model.db";
- private static final int VERSION = 1;
+ private static final int VERSION = 2;
// Sound trigger-based sound models.
public static interface GenericSoundModelContract {
@@ -47,15 +45,16 @@
public static final String KEY_MODEL_UUID = "model_uuid";
public static final String KEY_VENDOR_UUID = "vendor_uuid";
public static final String KEY_DATA = "data";
+ public static final String KEY_MODEL_VERSION = "model_version";
}
-
// Table Create Statement for the sound trigger table
private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE "
+ GenericSoundModelContract.TABLE + "("
+ GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
+ GenericSoundModelContract.KEY_VENDOR_UUID + " TEXT,"
- + GenericSoundModelContract.KEY_DATA + " BLOB" + " )";
+ + GenericSoundModelContract.KEY_DATA + " BLOB,"
+ + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER" + " )";
public SoundTriggerDbHelper(Context context) {
@@ -70,9 +69,13 @@
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- // TODO: For now, drop older tables and recreate new ones.
- db.execSQL("DROP TABLE IF EXISTS " + GenericSoundModelContract.TABLE);
- onCreate(db);
+ if (oldVersion == 1) {
+ // In version 2, a model version number was added.
+ Slog.d(TAG, "Adding model version column");
+ db.execSQL("ALTER TABLE " + GenericSoundModelContract.TABLE + " ADD COLUMN "
+ + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1");
+ oldVersion++;
+ }
}
/**
@@ -86,6 +89,7 @@
values.put(GenericSoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString());
values.put(GenericSoundModelContract.KEY_VENDOR_UUID, soundModel.vendorUuid.toString());
values.put(GenericSoundModelContract.KEY_DATA, soundModel.data);
+ values.put(GenericSoundModelContract.KEY_MODEL_VERSION, soundModel.version);
try {
return db.insertWithOnConflict(GenericSoundModelContract.TABLE, null, values,
@@ -113,8 +117,10 @@
GenericSoundModelContract.KEY_DATA));
String vendor_uuid = c.getString(
c.getColumnIndex(GenericSoundModelContract.KEY_VENDOR_UUID));
+ int version = c.getInt(
+ c.getColumnIndex(GenericSoundModelContract.KEY_MODEL_VERSION));
return new GenericSoundModel(model_uuid, UUID.fromString(vendor_uuid),
- data);
+ data, version);
} while (c.moveToNext());
}
} finally {
@@ -142,4 +148,48 @@
}
}
}
+
+ public void dump(PrintWriter pw) {
+ synchronized(this) {
+ String selectQuery = "SELECT * FROM " + GenericSoundModelContract.TABLE;
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery(selectQuery, null);
+ try {
+ pw.println(" Enrolled GenericSoundModels:");
+ if (c.moveToFirst()) {
+ String[] columnNames = c.getColumnNames();
+ do {
+ for (String name : columnNames) {
+ int colNameIndex = c.getColumnIndex(name);
+ int type = c.getType(colNameIndex);
+ switch (type) {
+ case Cursor.FIELD_TYPE_STRING:
+ pw.printf(" %s: %s\n", name,
+ c.getString(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ pw.printf(" %s: data blob\n", name);
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ pw.printf(" %s: %d\n", name,
+ c.getInt(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ pw.printf(" %s: %f\n", name,
+ c.getFloat(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_NULL:
+ pw.printf(" %s: null\n", name);
+ break;
+ }
+ }
+ pw.println();
+ } while (c.moveToNext());
+ }
+ } finally {
+ c.close();
+ db.close();
+ }
+ }
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 198b4c3..7099c09 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -611,65 +611,91 @@
int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
synchronized (mLock) {
- MetricsLogger.count(mContext, "sth_set_parameter", 1);
- if (modelId == null || mModule == null) {
- return SoundTrigger.STATUS_ERROR;
- }
- ModelData modelData = mModelDataMap.get(modelId);
- if (modelData == null) {
- Slog.w(TAG, "SetParameter: Invalid model id:" + modelId);
- return SoundTrigger.STATUS_BAD_VALUE;
- }
- if (!modelData.isModelLoaded()) {
- Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelId);
- return SoundTrigger.STATUS_BAD_VALUE;
- }
-
- return mModule.setParameter(modelData.getHandle(), modelParam, value);
+ return setParameterLocked(mModelDataMap.get(modelId), modelParam, value);
}
}
- int getParameter(@NonNull UUID modelId, @ModelParams int modelParam)
- throws UnsupportedOperationException, IllegalArgumentException {
+ int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) {
synchronized (mLock) {
- MetricsLogger.count(mContext, "sth_get_parameter", 1);
- if (mModule == null) {
- throw new UnsupportedOperationException("SoundTriggerModule not initialized");
- }
-
- ModelData modelData = mModelDataMap.get(modelId);
- if (modelData == null) {
- throw new IllegalArgumentException("Invalid model id:" + modelId);
- }
- if (!modelData.isModelLoaded()) {
- throw new UnsupportedOperationException("Given model is not loaded:" + modelId);
- }
-
- return mModule.getParameter(modelData.getHandle(), modelParam);
+ return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value);
}
}
+ private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam,
+ int value) {
+ MetricsLogger.count(mContext, "sth_set_parameter", 1);
+ if (mModule == null) {
+ return SoundTrigger.STATUS_NO_INIT;
+ }
+ if (modelData == null || !modelData.isModelLoaded()) {
+ Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData);
+ return SoundTrigger.STATUS_BAD_VALUE;
+ }
+
+ return mModule.setParameter(modelData.getHandle(), modelParam, value);
+ }
+
+ int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
+ synchronized (mLock) {
+ return getParameterLocked(mModelDataMap.get(modelId), modelParam);
+ }
+ }
+
+ int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+ synchronized (mLock) {
+ return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+ }
+ }
+
+ private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) {
+ MetricsLogger.count(mContext, "sth_get_parameter", 1);
+ if (mModule == null) {
+ throw new UnsupportedOperationException("SoundTriggerModule not initialized");
+ }
+
+ if (modelData == null) {
+ throw new IllegalArgumentException("Invalid model id");
+ }
+ if (!modelData.isModelLoaded()) {
+ throw new UnsupportedOperationException("Given model is not loaded:" + modelData);
+ }
+
+ return mModule.getParameter(modelData.getHandle(), modelParam);
+ }
+
@Nullable
ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
synchronized (mLock) {
- MetricsLogger.count(mContext, "sth_query_parameter", 1);
- if (mModule == null) {
- return null;
- }
- ModelData modelData = mModelDataMap.get(modelId);
- if (modelData == null) {
- Slog.w(TAG, "queryParameter: Invalid model id:" + modelId);
- return null;
- }
- if (!modelData.isModelLoaded()) {
- Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelId);
- return null;
- }
-
- return mModule.queryParameter(modelData.getHandle(), modelParam);
+ return queryParameterLocked(mModelDataMap.get(modelId), modelParam);
}
}
+ @Nullable
+ ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+ synchronized (mLock) {
+ return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+ }
+ }
+
+ @Nullable
+ private ModelParamRange queryParameterLocked(@Nullable ModelData modelData,
+ @ModelParams int modelParam) {
+ MetricsLogger.count(mContext, "sth_query_parameter", 1);
+ if (mModule == null) {
+ return null;
+ }
+ if (modelData == null) {
+ Slog.w(TAG, "queryParameter: Invalid model id");
+ return null;
+ }
+ if (!modelData.isModelLoaded()) {
+ Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData);
+ return null;
+ }
+
+ return mModule.queryParameter(modelData.getHandle(), modelParam);
+ }
+
//---- SoundTrigger.StatusListener methods
@Override
public void onRecognition(RecognitionEvent event) {
@@ -730,7 +756,9 @@
return;
}
- model.setStopped();
+ if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ model.setStopped();
+ }
try {
callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
@@ -874,7 +902,9 @@
return;
}
- modelData.setStopped();
+ if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ modelData.setStopped();
+ }
try {
modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
index d05e044..54dffdc 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -16,17 +16,17 @@
package com.android.server.soundtrigger;
+import android.annotation.Nullable;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
-import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
-import android.hardware.soundtrigger.SoundTriggerModule;
+
+import com.android.server.voiceinteraction.VoiceInteractionManagerService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -71,6 +71,58 @@
public abstract ModuleProperties getModuleProperties();
/**
+ * Set a model specific {@link ModelParams} with the given value. This
+ * parameter will keep its value for the duration the model is loaded regardless of starting and
+ * stopping recognition. Once the model is unloaded, the value will be lost.
+ * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+ * method.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * to modify model parameters
+ * @param modelParam {@link ModelParams}
+ * @param value Value to set
+ * @return - {@link SoundTrigger#STATUS_OK} in case of success
+ * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+ * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+ * if API is not supported by HAL
+ */
+ public abstract int setParameter(int keyphraseId, @ModelParams int modelParam, int value);
+
+ /**
+ * Get a model specific {@link ModelParams}. This parameter will keep its value
+ * for the duration the model is loaded regardless of starting and stopping recognition.
+ * Once the model is unloaded, the value will be lost. If the value is not set, a default
+ * value is returned. See ModelParams for parameter default values.
+ * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+ * method.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * to modify model parameters
+ * @param modelParam {@link ModelParams}
+ * @return value of parameter
+ * @throws UnsupportedOperationException if hal or model do not support this API.
+ * queryParameter should be checked first.
+ * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+ * queryParameter should be checked first.
+ */
+ public abstract int getParameter(int keyphraseId, @ModelParams int modelParam);
+
+ /**
+ * Determine if parameter control is supported for the given model handle.
+ * This method should be checked prior to calling {@link SoundTriggerInternal#setParameter}
+ * or {@link SoundTriggerInternal#getParameter}.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * to modify model parameters
+ * @param modelParam {@link ModelParams}
+ * @return supported range of parameter, null if not supported
+ */
+ @Nullable
+ public abstract ModelParamRange queryParameter(int keyphraseId,
+ @ModelParams int modelParam);
+
+ /**
* Unloads (and stops if running) the given keyphraseId
*/
public abstract int unloadKeyphraseModel(int keyphaseId);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 68b16f3..767010a 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1446,26 +1446,45 @@
@Override
public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
- if (!isInitialized()) return STATUS_ERROR;
+ if (!isInitialized()) throw new UnsupportedOperationException();
return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
recognitionConfig);
}
@Override
public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
- if (!isInitialized()) return STATUS_ERROR;
+ if (!isInitialized()) throw new UnsupportedOperationException();
return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
}
@Override
public ModuleProperties getModuleProperties() {
- if (!isInitialized()) return null;
+ if (!isInitialized()) throw new UnsupportedOperationException();
return mSoundTriggerHelper.getModuleProperties();
}
@Override
+ public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
+ if (!isInitialized()) throw new UnsupportedOperationException();
+ return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
+ }
+
+ @Override
+ public int getParameter(int keyphraseId, @ModelParams int modelParam) {
+ if (!isInitialized()) throw new UnsupportedOperationException();
+ return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam);
+ }
+
+ @Override
+ @Nullable
+ public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
+ if (!isInitialized()) throw new UnsupportedOperationException();
+ return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam);
+ }
+
+ @Override
public int unloadKeyphraseModel(int keyphraseId) {
- if (!isInitialized()) return STATUS_ERROR;
+ if (!isInitialized()) throw new UnsupportedOperationException();
return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
}
@@ -1476,6 +1495,9 @@
// log
sEventLogger.dump(pw);
+ // enrolled models
+ mDbHelper.dump(pw);
+
// stats
mSoundModelStatTracker.dump(pw);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index dd7b5a8..c58b6da 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -27,6 +27,7 @@
import android.text.TextUtils;
import android.util.Slog;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -43,7 +44,7 @@
static final boolean DBG = false;
private static final String NAME = "sound_model.db";
- private static final int VERSION = 6;
+ private static final int VERSION = 7;
public static interface SoundModelContract {
public static final String TABLE = "sound_model";
@@ -56,6 +57,7 @@
public static final String KEY_LOCALE = "locale";
public static final String KEY_HINT_TEXT = "hint_text";
public static final String KEY_USERS = "users";
+ public static final String KEY_MODEL_VERSION = "model_version";
}
// Table Create Statement
@@ -70,6 +72,7 @@
+ SoundModelContract.KEY_LOCALE + " TEXT,"
+ SoundModelContract.KEY_HINT_TEXT + " TEXT,"
+ SoundModelContract.KEY_USERS + " TEXT,"
+ + SoundModelContract.KEY_MODEL_VERSION + " INTEGER,"
+ "PRIMARY KEY (" + SoundModelContract.KEY_KEYPHRASE_ID + ","
+ SoundModelContract.KEY_LOCALE + ","
+ SoundModelContract.KEY_USERS + ")"
@@ -138,6 +141,13 @@
}
oldVersion++;
}
+ if (oldVersion == 6) {
+ // In version 7, a model version number was added.
+ Slog.d(TAG, "Adding model version column");
+ db.execSQL("ALTER TABLE " + SoundModelContract.TABLE + " ADD COLUMN "
+ + SoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1");
+ oldVersion++;
+ }
}
/**
@@ -155,6 +165,7 @@
}
values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE);
values.put(SoundModelContract.KEY_DATA, soundModel.data);
+ values.put(SoundModelContract.KEY_MODEL_VERSION, soundModel.version);
if (soundModel.keyphrases != null && soundModel.keyphrases.length == 1) {
values.put(SoundModelContract.KEY_KEYPHRASE_ID, soundModel.keyphrases[0].id);
@@ -250,6 +261,8 @@
c.getColumnIndex(SoundModelContract.KEY_LOCALE));
String text = c.getString(
c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
+ int version = c.getInt(
+ c.getColumnIndex(SoundModelContract.KEY_MODEL_VERSION));
// Only add keyphrases meant for the current user.
if (users == null) {
@@ -282,7 +295,7 @@
vendorUuid = UUID.fromString(vendorUuidString);
}
KeyphraseSoundModel model = new KeyphraseSoundModel(
- UUID.fromString(modelUuid), vendorUuid, data, keyphrases);
+ UUID.fromString(modelUuid), vendorUuid, data, keyphrases, version);
if (DBG) {
Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: "
+ model);
@@ -325,6 +338,10 @@
return users;
}
+ /**
+ * SoundModelRecord is no longer used, and it should only be used on database migration.
+ * This class does not need to be modified when modifying the database scheme.
+ */
private static class SoundModelRecord {
public final String modelUuid;
public final String vendorUuid;
@@ -413,4 +430,48 @@
return a == b;
}
}
+
+ public void dump(PrintWriter pw) {
+ synchronized(this) {
+ String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE;
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery(selectQuery, null);
+ try {
+ pw.println(" Enrolled KeyphraseSoundModels:");
+ if (c.moveToFirst()) {
+ String[] columnNames = c.getColumnNames();
+ do {
+ for (String name : columnNames) {
+ int colNameIndex = c.getColumnIndex(name);
+ int type = c.getType(colNameIndex);
+ switch (type) {
+ case Cursor.FIELD_TYPE_STRING:
+ pw.printf(" %s: %s\n", name,
+ c.getString(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ pw.printf(" %s: data blob\n", name);
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ pw.printf(" %s: %d\n", name,
+ c.getInt(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ pw.printf(" %s: %f\n", name,
+ c.getFloat(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_NULL:
+ pw.printf(" %s: null\n", name);
+ break;
+ }
+ }
+ pw.println();
+ } while (c.moveToNext());
+ }
+ } finally {
+ c.close();
+ db.close();
+ }
+ }
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e9db31b..506c67e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -41,7 +41,9 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.os.Binder;
@@ -157,9 +159,13 @@
}
}
+ private boolean isSupported(UserInfo user) {
+ return user.isFull();
+ }
+
@Override
- public boolean isSupported(UserInfo userInfo) {
- return userInfo.isFull();
+ public boolean isSupportedUser(TargetUser user) {
+ return isSupported(user.getUserInfo());
}
@Override
@@ -1084,6 +1090,55 @@
}
}
+ @Override
+ public int setParameter(IVoiceInteractionService service, int keyphraseId,
+ @ModelParams int modelParam, int value) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (this) {
+ enforceIsCurrentVoiceInteractionService(service);
+ }
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSoundTriggerInternal.setParameter(keyphraseId, modelParam, value);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+
+ @Override
+ public int getParameter(IVoiceInteractionService service, int keyphraseId,
+ @ModelParams int modelParam) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (this) {
+ enforceIsCurrentVoiceInteractionService(service);
+ }
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSoundTriggerInternal.getParameter(keyphraseId, modelParam);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParamRange queryParameter(IVoiceInteractionService service,
+ int keyphraseId, @ModelParams int modelParam) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (this) {
+ enforceIsCurrentVoiceInteractionService(service);
+ }
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSoundTriggerInternal.queryParameter(keyphraseId, modelParam);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+
private synchronized void unloadAllKeyphraseModels() {
for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) {
final long caller = Binder.clearCallingIdentity();
@@ -1292,6 +1347,7 @@
pw.println(" mCurUserUnlocked: " + mCurUserUnlocked);
pw.println(" mCurUserSupported: " + mCurUserSupported);
dumpSupportedUsers(pw, " ");
+ mDbHelper.dump(pw);
if (mImpl == null) {
pw.println(" (No active implementation)");
return;
diff --git a/telecomm/TEST_MAPPING b/telecomm/TEST_MAPPING
new file mode 100644
index 0000000..d585666
--- /dev/null
+++ b/telecomm/TEST_MAPPING
@@ -0,0 +1,29 @@
+{
+ "presubmit": [
+ {
+ "name": "TeleServiceTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "TelecomUnitTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "TelephonyProviderTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index a8852a8..826a89e 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -568,6 +568,7 @@
private final Bundle mExtras;
private final Bundle mIntentExtras;
private final long mCreationTimeMillis;
+ private final String mContactDisplayName;
private final @CallDirection int mCallDirection;
private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
@@ -872,6 +873,17 @@
}
/**
+ * Returns the name of the caller on the remote end, as derived from a
+ * {@link android.provider.ContactsContract} lookup of the call's handle.
+ * @return The name of the caller, or {@code null} if the lookup is not yet complete, if
+ * there's no contacts entry for the caller, or if the {@link InCallService} does
+ * not hold the {@link android.Manifest.permission#READ_CONTACTS} permission.
+ */
+ public @Nullable String getContactDisplayName() {
+ return mContactDisplayName;
+ }
+
+ /**
* Indicates whether the call is an incoming or outgoing call.
* @return The call's direction.
*/
@@ -909,6 +921,7 @@
areBundlesEqual(mExtras, d.mExtras) &&
areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
+ Objects.equals(mContactDisplayName, d.mContactDisplayName) &&
Objects.equals(mCallDirection, d.mCallDirection) &&
Objects.equals(mCallerNumberVerificationStatus,
d.mCallerNumberVerificationStatus);
@@ -933,6 +946,7 @@
mExtras,
mIntentExtras,
mCreationTimeMillis,
+ mContactDisplayName,
mCallDirection,
mCallerNumberVerificationStatus);
}
@@ -955,6 +969,7 @@
Bundle extras,
Bundle intentExtras,
long creationTimeMillis,
+ String contactDisplayName,
int callDirection,
int callerNumberVerificationStatus) {
mTelecomCallId = telecomCallId;
@@ -973,6 +988,7 @@
mExtras = extras;
mIntentExtras = intentExtras;
mCreationTimeMillis = creationTimeMillis;
+ mContactDisplayName = contactDisplayName;
mCallDirection = callDirection;
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
}
@@ -996,6 +1012,7 @@
parcelableCall.getExtras(),
parcelableCall.getIntentExtras(),
parcelableCall.getCreationTimeMillis(),
+ parcelableCall.getContactDisplayName(),
parcelableCall.getCallDirection(),
parcelableCall.getCallerNumberVerificationStatus());
}
@@ -1445,6 +1462,7 @@
private boolean mChildrenCached;
private String mParentId = null;
+ private String mActiveGenericConferenceChild = null;
private int mState;
private List<String> mCannedTextResponses = null;
private String mCallingPackage;
@@ -1943,6 +1961,20 @@
}
/**
+ * Returns the child {@link Call} in a generic conference that is currently active.
+ * For calls that are not generic conferences, or when the generic conference has more than
+ * 2 children, returns {@code null}.
+ * @see Details#PROPERTY_GENERIC_CONFERENCE
+ * @return The active child call.
+ */
+ public @Nullable Call getGenericConferenceActiveChildCall() {
+ if (mActiveGenericConferenceChild != null) {
+ return mPhone.internalGetCallByTelecomId(mActiveGenericConferenceChild);
+ }
+ return null;
+ }
+
+ /**
* Obtains a list of canned, pre-configured message responses to present to the user as
* ways of rejecting this {@code Call} using via a text message.
*
@@ -2190,6 +2222,13 @@
mChildrenCached = false;
}
+ String activeChildCallId = parcelableCall.getActiveChildCallId();
+ boolean activeChildChanged = !Objects.equals(activeChildCallId,
+ mActiveGenericConferenceChild);
+ if (activeChildChanged) {
+ mActiveGenericConferenceChild = activeChildCallId;
+ }
+
List<String> conferenceableCallIds = parcelableCall.getConferenceableCallIds();
List<Call> conferenceableCalls = new ArrayList<Call>(conferenceableCallIds.size());
for (String otherId : conferenceableCallIds) {
@@ -2249,7 +2288,7 @@
if (parentChanged) {
fireParentChanged(getParent());
}
- if (childrenChanged) {
+ if (childrenChanged || activeChildChanged) {
fireChildrenChanged(getChildren());
}
if (isRttChanged) {
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index be4e2f4..415a817 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
@@ -36,6 +37,265 @@
* @hide
*/
public final class ParcelableCall implements Parcelable {
+
+ public static class ParcelableCallBuilder {
+ private String mId;
+ private int mState;
+ private DisconnectCause mDisconnectCause;
+ private List<String> mCannedSmsResponses;
+ private int mCapabilities;
+ private int mProperties;
+ private int mSupportedAudioRoutes;
+ private long mConnectTimeMillis;
+ private Uri mHandle;
+ private int mHandlePresentation;
+ private String mCallerDisplayName;
+ private int mCallerDisplayNamePresentation;
+ private GatewayInfo mGatewayInfo;
+ private PhoneAccountHandle mAccountHandle;
+ private boolean mIsVideoCallProviderChanged;
+ private IVideoProvider mVideoCallProvider;
+ private boolean mIsRttCallChanged;
+ private ParcelableRttCall mRttCall;
+ private String mParentCallId;
+ private List<String> mChildCallIds;
+ private StatusHints mStatusHints;
+ private int mVideoState;
+ private List<String> mConferenceableCallIds;
+ private Bundle mIntentExtras;
+ private Bundle mExtras;
+ private long mCreationTimeMillis;
+ private int mCallDirection;
+ private int mCallerNumberVerificationStatus;
+ private String mContactDisplayName;
+ private String mActiveChildCallId;
+
+ public ParcelableCallBuilder setId(String id) {
+ mId = id;
+ return this;
+ }
+
+ public ParcelableCallBuilder setState(int state) {
+ mState = state;
+ return this;
+ }
+
+ public ParcelableCallBuilder setDisconnectCause(DisconnectCause disconnectCause) {
+ mDisconnectCause = disconnectCause;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCannedSmsResponses(List<String> cannedSmsResponses) {
+ mCannedSmsResponses = cannedSmsResponses;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCapabilities(int capabilities) {
+ mCapabilities = capabilities;
+ return this;
+ }
+
+ public ParcelableCallBuilder setProperties(int properties) {
+ mProperties = properties;
+ return this;
+ }
+
+ public ParcelableCallBuilder setSupportedAudioRoutes(int supportedAudioRoutes) {
+ mSupportedAudioRoutes = supportedAudioRoutes;
+ return this;
+ }
+
+ public ParcelableCallBuilder setConnectTimeMillis(long connectTimeMillis) {
+ mConnectTimeMillis = connectTimeMillis;
+ return this;
+ }
+
+ public ParcelableCallBuilder setHandle(Uri handle) {
+ mHandle = handle;
+ return this;
+ }
+
+ public ParcelableCallBuilder setHandlePresentation(int handlePresentation) {
+ mHandlePresentation = handlePresentation;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallerDisplayName(String callerDisplayName) {
+ mCallerDisplayName = callerDisplayName;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallerDisplayNamePresentation(
+ int callerDisplayNamePresentation) {
+ mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+ return this;
+ }
+
+ public ParcelableCallBuilder setGatewayInfo(GatewayInfo gatewayInfo) {
+ mGatewayInfo = gatewayInfo;
+ return this;
+ }
+
+ public ParcelableCallBuilder setAccountHandle(PhoneAccountHandle accountHandle) {
+ mAccountHandle = accountHandle;
+ return this;
+ }
+
+ public ParcelableCallBuilder setIsVideoCallProviderChanged(
+ boolean isVideoCallProviderChanged) {
+ mIsVideoCallProviderChanged = isVideoCallProviderChanged;
+ return this;
+ }
+
+ public ParcelableCallBuilder setVideoCallProvider(IVideoProvider videoCallProvider) {
+ mVideoCallProvider = videoCallProvider;
+ return this;
+ }
+
+ public ParcelableCallBuilder setIsRttCallChanged(boolean isRttCallChanged) {
+ mIsRttCallChanged = isRttCallChanged;
+ return this;
+ }
+
+ public ParcelableCallBuilder setRttCall(ParcelableRttCall rttCall) {
+ mRttCall = rttCall;
+ return this;
+ }
+
+ public ParcelableCallBuilder setParentCallId(String parentCallId) {
+ mParentCallId = parentCallId;
+ return this;
+ }
+
+ public ParcelableCallBuilder setChildCallIds(List<String> childCallIds) {
+ mChildCallIds = childCallIds;
+ return this;
+ }
+
+ public ParcelableCallBuilder setStatusHints(StatusHints statusHints) {
+ mStatusHints = statusHints;
+ return this;
+ }
+
+ public ParcelableCallBuilder setVideoState(int videoState) {
+ mVideoState = videoState;
+ return this;
+ }
+
+ public ParcelableCallBuilder setConferenceableCallIds(
+ List<String> conferenceableCallIds) {
+ mConferenceableCallIds = conferenceableCallIds;
+ return this;
+ }
+
+ public ParcelableCallBuilder setIntentExtras(Bundle intentExtras) {
+ mIntentExtras = intentExtras;
+ return this;
+ }
+
+ public ParcelableCallBuilder setExtras(Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCreationTimeMillis(long creationTimeMillis) {
+ mCreationTimeMillis = creationTimeMillis;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallDirection(int callDirection) {
+ mCallDirection = callDirection;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallerNumberVerificationStatus(
+ int callerNumberVerificationStatus) {
+ mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+ return this;
+ }
+
+ public ParcelableCallBuilder setContactDisplayName(String contactDisplayName) {
+ mContactDisplayName = contactDisplayName;
+ return this;
+ }
+
+ public ParcelableCallBuilder setActiveChildCallId(String activeChildCallId) {
+ mActiveChildCallId = activeChildCallId;
+ return this;
+ }
+
+ public ParcelableCall createParcelableCall() {
+ return new ParcelableCall(
+ mId,
+ mState,
+ mDisconnectCause,
+ mCannedSmsResponses,
+ mCapabilities,
+ mProperties,
+ mSupportedAudioRoutes,
+ mConnectTimeMillis,
+ mHandle,
+ mHandlePresentation,
+ mCallerDisplayName,
+ mCallerDisplayNamePresentation,
+ mGatewayInfo,
+ mAccountHandle,
+ mIsVideoCallProviderChanged,
+ mVideoCallProvider,
+ mIsRttCallChanged,
+ mRttCall,
+ mParentCallId,
+ mChildCallIds,
+ mStatusHints,
+ mVideoState,
+ mConferenceableCallIds,
+ mIntentExtras,
+ mExtras,
+ mCreationTimeMillis,
+ mCallDirection,
+ mCallerNumberVerificationStatus,
+ mContactDisplayName,
+ mActiveChildCallId);
+ }
+
+ public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
+ ParcelableCallBuilder newBuilder = new ParcelableCallBuilder();
+ newBuilder.mId = parcelableCall.mId;
+ newBuilder.mState = parcelableCall.mState;
+ newBuilder.mDisconnectCause = parcelableCall.mDisconnectCause;
+ newBuilder.mCannedSmsResponses = parcelableCall.mCannedSmsResponses;
+ newBuilder.mCapabilities = parcelableCall.mCapabilities;
+ newBuilder.mProperties = parcelableCall.mProperties;
+ newBuilder.mSupportedAudioRoutes = parcelableCall.mSupportedAudioRoutes;
+ newBuilder.mConnectTimeMillis = parcelableCall.mConnectTimeMillis;
+ newBuilder.mHandle = parcelableCall.mHandle;
+ newBuilder.mHandlePresentation = parcelableCall.mHandlePresentation;
+ newBuilder.mCallerDisplayName = parcelableCall.mCallerDisplayName;
+ newBuilder.mCallerDisplayNamePresentation =
+ parcelableCall.mCallerDisplayNamePresentation;
+ newBuilder.mGatewayInfo = parcelableCall.mGatewayInfo;
+ newBuilder.mAccountHandle = parcelableCall.mAccountHandle;
+ newBuilder.mIsVideoCallProviderChanged = parcelableCall.mIsVideoCallProviderChanged;
+ newBuilder.mVideoCallProvider = parcelableCall.mVideoCallProvider;
+ newBuilder.mIsRttCallChanged = parcelableCall.mIsRttCallChanged;
+ newBuilder.mRttCall = parcelableCall.mRttCall;
+ newBuilder.mParentCallId = parcelableCall.mParentCallId;
+ newBuilder.mChildCallIds = parcelableCall.mChildCallIds;
+ newBuilder.mStatusHints = parcelableCall.mStatusHints;
+ newBuilder.mVideoState = parcelableCall.mVideoState;
+ newBuilder.mConferenceableCallIds = parcelableCall.mConferenceableCallIds;
+ newBuilder.mIntentExtras = parcelableCall.mIntentExtras;
+ newBuilder.mExtras = parcelableCall.mExtras;
+ newBuilder.mCreationTimeMillis = parcelableCall.mCreationTimeMillis;
+ newBuilder.mCallDirection = parcelableCall.mCallDirection;
+ newBuilder.mCallerNumberVerificationStatus =
+ parcelableCall.mCallerNumberVerificationStatus;
+ newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
+ newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
+ return newBuilder;
+ }
+ }
+
private final String mId;
private final int mState;
private final DisconnectCause mDisconnectCause;
@@ -65,6 +325,8 @@
private final long mCreationTimeMillis;
private final int mCallDirection;
private final int mCallerNumberVerificationStatus;
+ private final String mContactDisplayName;
+ private final String mActiveChildCallId; // Only valid for CDMA conferences
public ParcelableCall(
String id,
@@ -94,7 +356,10 @@
Bundle extras,
long creationTimeMillis,
int callDirection,
- int callerNumberVerificationStatus) {
+ int callerNumberVerificationStatus,
+ String contactDisplayName,
+ String activeChildCallId
+ ) {
mId = id;
mState = state;
mDisconnectCause = disconnectCause;
@@ -123,6 +388,8 @@
mCreationTimeMillis = creationTimeMillis;
mCallDirection = callDirection;
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+ mContactDisplayName = contactDisplayName;
+ mActiveChildCallId = activeChildCallId;
}
/** The unique ID of the call. */
@@ -332,6 +599,21 @@
return mCallerNumberVerificationStatus;
}
+ /**
+ * @return the name of the remote party as derived from a contacts DB lookup.
+ */
+ public @Nullable String getContactDisplayName() {
+ return mContactDisplayName;
+ }
+
+ /**
+ * @return On a CDMA conference with two participants, returns the ID of the child call that's
+ * currently active.
+ */
+ public @Nullable String getActiveChildCallId() {
+ return mActiveChildCallId;
+ }
+
/** Responsible for creating ParcelableCall objects for deserialized Parcels. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCall> CREATOR =
@@ -371,35 +653,40 @@
long creationTimeMillis = source.readLong();
int callDirection = source.readInt();
int callerNumberVerificationStatus = source.readInt();
- return new ParcelableCall(
- id,
- state,
- disconnectCause,
- cannedSmsResponses,
- capabilities,
- properties,
- supportedAudioRoutes,
- connectTimeMillis,
- handle,
- handlePresentation,
- callerDisplayName,
- callerDisplayNamePresentation,
- gatewayInfo,
- accountHandle,
- isVideoCallProviderChanged,
- videoCallProvider,
- isRttCallChanged,
- rttCall,
- parentCallId,
- childCallIds,
- statusHints,
- videoState,
- conferenceableCallIds,
- intentExtras,
- extras,
- creationTimeMillis,
- callDirection,
- callerNumberVerificationStatus);
+ String contactDisplayName = source.readString();
+ String activeChildCallId = source.readString();
+ return new ParcelableCallBuilder()
+ .setId(id)
+ .setState(state)
+ .setDisconnectCause(disconnectCause)
+ .setCannedSmsResponses(cannedSmsResponses)
+ .setCapabilities(capabilities)
+ .setProperties(properties)
+ .setSupportedAudioRoutes(supportedAudioRoutes)
+ .setConnectTimeMillis(connectTimeMillis)
+ .setHandle(handle)
+ .setHandlePresentation(handlePresentation)
+ .setCallerDisplayName(callerDisplayName)
+ .setCallerDisplayNamePresentation(callerDisplayNamePresentation)
+ .setGatewayInfo(gatewayInfo)
+ .setAccountHandle(accountHandle)
+ .setIsVideoCallProviderChanged(isVideoCallProviderChanged)
+ .setVideoCallProvider(videoCallProvider)
+ .setIsRttCallChanged(isRttCallChanged)
+ .setRttCall(rttCall)
+ .setParentCallId(parentCallId)
+ .setChildCallIds(childCallIds)
+ .setStatusHints(statusHints)
+ .setVideoState(videoState)
+ .setConferenceableCallIds(conferenceableCallIds)
+ .setIntentExtras(intentExtras)
+ .setExtras(extras)
+ .setCreationTimeMillis(creationTimeMillis)
+ .setCallDirection(callDirection)
+ .setCallerNumberVerificationStatus(callerNumberVerificationStatus)
+ .setContactDisplayName(contactDisplayName)
+ .setActiveChildCallId(activeChildCallId)
+ .createParcelableCall();
}
@Override
@@ -446,6 +733,8 @@
destination.writeLong(mCreationTimeMillis);
destination.writeInt(mCallDirection);
destination.writeInt(mCallerNumberVerificationStatus);
+ destination.writeString(mContactDisplayName);
+ destination.writeString(mActiveChildCallId);
}
@Override
diff --git a/telephony/TEST_MAPPING b/telephony/TEST_MAPPING
new file mode 100644
index 0000000..d585666
--- /dev/null
+++ b/telephony/TEST_MAPPING
@@ -0,0 +1,29 @@
+{
+ "presubmit": [
+ {
+ "name": "TeleServiceTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "TelecomUnitTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "TelephonyProviderTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index aaafee2..f39981f 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -16,8 +16,6 @@
package android.telephony;
-import com.android.telephony.Rlog;
-
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index 368f8f1..9bc534c 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -18,17 +18,19 @@
import android.annotation.Nullable;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.provider.Settings;
-import com.android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -76,15 +78,16 @@
*/
public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
IPackageManager packageManager, IPermissionManager permissionManager,
- TelephonyManager telephonyManager, ContentResolver contentResolver, int userId) {
+ TelephonyManager telephonyManager, int userId, Context context) {
if (DEBUG) {
- Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
+ Log.d(TAG, "disableCarrierAppsUntilPrivileged");
}
SystemConfig config = SystemConfig.getInstance();
ArraySet<String> systemCarrierAppsDisabledUntilUsed =
config.getDisabledUntilUsedPreinstalledCarrierApps();
ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+ ContentResolver contentResolver = getContentResolverForUser(context, userId);
disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed,
systemCarrierAssociatedAppsDisabledUntilUsed);
@@ -102,10 +105,10 @@
* Manager can kill it, and this can lead to crashes as the app is in an unexpected state.
*/
public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
- IPackageManager packageManager, IPermissionManager permissionManager,
- ContentResolver contentResolver, int userId) {
+ IPackageManager packageManager, IPermissionManager permissionManager, int userId,
+ Context context) {
if (DEBUG) {
- Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
+ Log.d(TAG, "disableCarrierAppsUntilPrivileged");
}
SystemConfig config = SystemConfig.getInstance();
ArraySet<String> systemCarrierAppsDisabledUntilUsed =
@@ -114,15 +117,23 @@
ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+ ContentResolver contentResolver = getContentResolverForUser(context, userId);
disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
null /* telephonyManager */, contentResolver, userId,
systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed);
}
+ private static ContentResolver getContentResolverForUser(Context context, int userId) {
+ Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId),
+ 0);
+ return userContext.getContentResolver();
+ }
+
/**
* Disable carrier apps until they are privileged
* Must be public b/c framework unit tests can't access package-private methods.
*/
+ // Must be public b/c framework unit tests can't access package-private methods.
@VisibleForTesting
public static void disableCarrierAppsUntilPrivileged(String callingPackage,
IPackageManager packageManager, IPermissionManager permissionManager,
@@ -130,8 +141,8 @@
ContentResolver contentResolver, int userId,
ArraySet<String> systemCarrierAppsDisabledUntilUsed,
ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) {
- List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(packageManager,
- userId, systemCarrierAppsDisabledUntilUsed);
+ List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper(
+ packageManager, userId, systemCarrierAppsDisabledUntilUsed);
if (candidates == null || candidates.isEmpty()) {
return;
}
@@ -142,9 +153,8 @@
systemCarrierAssociatedAppsDisabledUntilUsed);
List<String> enabledCarrierPackages = new ArrayList<>();
-
- boolean hasRunOnce = Settings.Secure.getIntForUser(
- contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1;
+ boolean hasRunOnce = Settings.Secure.getInt(contentResolver,
+ Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1;
try {
for (ApplicationInfo ai : candidates) {
@@ -168,16 +178,17 @@
}
}
+ int enabledSetting = packageManager.getApplicationEnabledSetting(packageName,
+ userId);
if (hasPrivileges) {
// Only update enabled state for the app on /system. Once it has been
// updated we shouldn't touch it.
- if (!ai.isUpdatedSystemApp()
- && (ai.enabledSetting
+ if (enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
- || ai.enabledSetting
+ || enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
- || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
- Rlog.i(TAG, "Update state(" + packageName + "): ENABLED for user "
+ || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+ Log.i(TAG, "Update state(" + packageName + "): ENABLED for user "
+ userId);
packageManager.setSystemAppInstallState(
packageName,
@@ -194,13 +205,16 @@
// Also enable any associated apps for this carrier app.
if (associatedAppList != null) {
for (ApplicationInfo associatedApp : associatedAppList) {
- if (associatedApp.enabledSetting
+ int associatedAppEnabledSetting =
+ packageManager.getApplicationEnabledSetting(
+ associatedApp.packageName, userId);
+ if (associatedAppEnabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
- || associatedApp.enabledSetting
+ || associatedAppEnabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
|| (associatedApp.flags
& ApplicationInfo.FLAG_INSTALLED) == 0) {
- Rlog.i(TAG, "Update associated state(" + associatedApp.packageName
+ Log.i(TAG, "Update associated state(" + associatedApp.packageName
+ "): ENABLED for user " + userId);
packageManager.setSystemAppInstallState(
associatedApp.packageName,
@@ -221,11 +235,10 @@
} else { // No carrier privileges
// Only update enabled state for the app on /system. Once it has been
// updated we shouldn't touch it.
- if (!ai.isUpdatedSystemApp()
- && ai.enabledSetting
+ if (enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
&& (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
- Rlog.i(TAG, "Update state(" + packageName
+ Log.i(TAG, "Update state(" + packageName
+ "): DISABLED_UNTIL_USED for user " + userId);
packageManager.setSystemAppInstallState(
packageName,
@@ -239,11 +252,14 @@
if (!hasRunOnce) {
if (associatedAppList != null) {
for (ApplicationInfo associatedApp : associatedAppList) {
- if (associatedApp.enabledSetting
+ int associatedAppEnabledSetting =
+ packageManager.getApplicationEnabledSetting(
+ associatedApp.packageName, userId);
+ if (associatedAppEnabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
&& (associatedApp.flags
& ApplicationInfo.FLAG_INSTALLED) != 0) {
- Rlog.i(TAG,
+ Log.i(TAG,
"Update associated state(" + associatedApp.packageName
+ "): DISABLED_UNTIL_USED for user " + userId);
packageManager.setSystemAppInstallState(
@@ -259,8 +275,7 @@
// Mark the execution so we do not disable apps again.
if (!hasRunOnce) {
- Settings.Secure.putIntForUser(
- contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId);
+ Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1);
}
if (!enabledCarrierPackages.isEmpty()) {
@@ -271,7 +286,7 @@
permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId);
}
} catch (RemoteException e) {
- Rlog.w(TAG, "Could not reach PackageManager", e);
+ Log.w(TAG, "Could not reach PackageManager", e);
}
}
@@ -351,6 +366,31 @@
return apps;
}
+ private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper(
+ IPackageManager packageManager,
+ int userId,
+ ArraySet<String> systemCarrierAppsDisabledUntilUsed) {
+ if (systemCarrierAppsDisabledUntilUsed == null) {
+ return null;
+ }
+
+ int size = systemCarrierAppsDisabledUntilUsed.size();
+ if (size == 0) {
+ return null;
+ }
+
+ List<ApplicationInfo> apps = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i);
+ ApplicationInfo ai =
+ getApplicationInfoIfNotUpdatedSystemApp(packageManager, userId, packageName);
+ if (ai != null) {
+ apps.add(ai);
+ }
+ }
+ return apps;
+ }
+
private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper(
IPackageManager packageManager,
int userId,
@@ -363,11 +403,11 @@
systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i);
for (int j = 0; j < associatedAppPackages.size(); j++) {
ApplicationInfo ai =
- getApplicationInfoIfSystemApp(
+ getApplicationInfoIfNotUpdatedSystemApp(
packageManager, userId, associatedAppPackages.get(j));
// Only update enabled state for the app on /system. Once it has been updated we
// shouldn't touch it.
- if (ai != null && !ai.isUpdatedSystemApp()) {
+ if (ai != null) {
List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage);
if (appList == null) {
appList = new ArrayList<>();
@@ -381,6 +421,26 @@
}
@Nullable
+ private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp(
+ IPackageManager packageManager,
+ int userId,
+ String packageName) {
+ try {
+ ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+ | PackageManager.MATCH_SYSTEM_ONLY
+ | PackageManager.MATCH_FACTORY_ONLY, userId);
+ if (ai != null) {
+ return ai;
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Could not reach PackageManager", e);
+ }
+ return null;
+ }
+
+ @Nullable
private static ApplicationInfo getApplicationInfoIfSystemApp(
IPackageManager packageManager,
int userId,
@@ -388,12 +448,13 @@
try {
ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId);
- if (ai != null && ai.isSystemApp()) {
+ | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+ | PackageManager.MATCH_SYSTEM_ONLY, userId);
+ if (ai != null) {
return ai;
}
} catch (RemoteException e) {
- Rlog.w(TAG, "Could not reach PackageManager", e);
+ Log.w(TAG, "Could not reach PackageManager", e);
}
return null;
}
diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
index a36ff93..5c53f7e 100644
--- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
@@ -19,7 +19,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.os.Build;
-import com.android.telephony.Rlog;
+import android.util.Log;
import android.text.TextUtils;
import android.util.SparseIntArray;
@@ -498,11 +498,11 @@
StringBuilder ret = new StringBuilder(lengthSeptets);
if (languageTable < 0 || languageTable > sLanguageTables.length) {
- Rlog.w(TAG, "unknown language table " + languageTable + ", using default");
+ Log.w(TAG, "unknown language table " + languageTable + ", using default");
languageTable = 0;
}
if (shiftTable < 0 || shiftTable > sLanguageShiftTables.length) {
- Rlog.w(TAG, "unknown single shift table " + shiftTable + ", using default");
+ Log.w(TAG, "unknown single shift table " + shiftTable + ", using default");
shiftTable = 0;
}
@@ -512,11 +512,11 @@
String shiftTableToChar = sLanguageShiftTables[shiftTable];
if (languageTableToChar.isEmpty()) {
- Rlog.w(TAG, "no language table for code " + languageTable + ", using default");
+ Log.w(TAG, "no language table for code " + languageTable + ", using default");
languageTableToChar = sLanguageTables[0];
}
if (shiftTableToChar.isEmpty()) {
- Rlog.w(TAG, "no single shift table for code " + shiftTable + ", using default");
+ Log.w(TAG, "no single shift table for code " + shiftTable + ", using default");
shiftTableToChar = sLanguageShiftTables[0];
}
@@ -556,7 +556,7 @@
}
}
} catch (RuntimeException ex) {
- Rlog.e(TAG, "Error GSM 7 bit packed: ", ex);
+ Log.e(TAG, "Error GSM 7 bit packed: ", ex);
return null;
}
@@ -813,7 +813,7 @@
for (int i = 0; i < sz; i++) {
char c = s.charAt(i);
if (c == GSM_EXTENDED_ESCAPE) {
- Rlog.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
+ Log.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
continue;
}
if (charToLanguageTable.get(c, -1) != -1) {
@@ -892,7 +892,7 @@
for (int i = 0; i < sz && !lpcList.isEmpty(); i++) {
char c = s.charAt(i);
if (c == GSM_EXTENDED_ESCAPE) {
- Rlog.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!");
+ Log.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!");
continue;
}
// iterate through enabled locking shift tables
@@ -1496,7 +1496,7 @@
int numTables = sLanguageTables.length;
int numShiftTables = sLanguageShiftTables.length;
if (numTables != numShiftTables) {
- Rlog.e(TAG, "Error: language tables array length " + numTables +
+ Log.e(TAG, "Error: language tables array length " + numTables +
" != shift tables array length " + numShiftTables);
}
@@ -1506,7 +1506,7 @@
int tableLen = table.length();
if (tableLen != 0 && tableLen != 128) {
- Rlog.e(TAG, "Error: language tables index " + i +
+ Log.e(TAG, "Error: language tables index " + i +
" length " + tableLen + " (expected 128 or 0)");
}
@@ -1524,7 +1524,7 @@
int shiftTableLen = shiftTable.length();
if (shiftTableLen != 0 && shiftTableLen != 128) {
- Rlog.e(TAG, "Error: language shift tables index " + i +
+ Log.e(TAG, "Error: language shift tables index " + i +
" length " + shiftTableLen + " (expected 128 or 0)");
}
diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
index 45a563c..714f5a6 100644
--- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java
+++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
@@ -19,7 +19,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
-import com.android.telephony.Rlog;
+import android.util.Log;
import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch;
import com.android.internal.telephony.HbpcdLookup.MccIdd;
@@ -54,16 +54,16 @@
if (c2 != null) {
int c2Counter = c2.getCount();
if (DBG) {
- Rlog.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
+ Log.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
}
if (c2Counter == 1) {
if (DBG) {
- Rlog.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2);
+ Log.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2);
}
c2.moveToFirst();
tmpMcc = c2.getInt(0);
if (DBG) {
- Rlog.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
+ Log.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
}
c2.close();
return tmpMcc;
@@ -85,18 +85,18 @@
int c3Counter = c3.getCount();
if (c3Counter > 0) {
if (c3Counter > 1) {
- Rlog.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
+ Log.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
}
- if (DBG) Rlog.d(LOG_TAG, "Query conflict sid returned the cursor " + c3);
+ if (DBG) Log.d(LOG_TAG, "Query conflict sid returned the cursor " + c3);
c3.moveToFirst();
tmpMcc = c3.getInt(0);
if (DBG) {
- Rlog.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
+ Log.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
}
if (!isNitzTimeZone) {
// time zone is not accurate, it may get wrong mcc, ignore it.
if (DBG) {
- Rlog.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc);
+ Log.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc);
}
tmpMcc = 0;
}
@@ -115,18 +115,18 @@
null, null);
if (c5 != null) {
if (c5.getCount() > 0) {
- if (DBG) Rlog.d(LOG_TAG, "Query Range returned the cursor " + c5);
+ if (DBG) Log.d(LOG_TAG, "Query Range returned the cursor " + c5);
c5.moveToFirst();
tmpMcc = c5.getInt(0);
- if (DBG) Rlog.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
+ if (DBG) Log.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
c5.close();
return tmpMcc;
}
c5.close();
}
- if (DBG) Rlog.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
+ if (DBG) Log.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
- if (DBG) Rlog.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc = " + tmpMcc);
+ if (DBG) Log.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc = " + tmpMcc);
// If unknown MCC still could not be resolved,
return tmpMcc;
}
@@ -135,7 +135,7 @@
* Gets country information with given MCC.
*/
public String getIddByMcc(int mcc) {
- if (DBG) Rlog.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
+ if (DBG) Log.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
String idd = "";
Cursor c = null;
@@ -145,19 +145,19 @@
MccIdd.MCC + "=" + mcc, null, null);
if (cur != null) {
if (cur.getCount() > 0) {
- if (DBG) Rlog.d(LOG_TAG, "Query Idd returned the cursor " + cur);
+ if (DBG) Log.d(LOG_TAG, "Query Idd returned the cursor " + cur);
// TODO: for those country having more than 1 IDDs, need more information
// to decide which IDD would be used. currently just use the first 1.
cur.moveToFirst();
idd = cur.getString(0);
- if (DBG) Rlog.d(LOG_TAG, "IDD = " + idd);
+ if (DBG) Log.d(LOG_TAG, "IDD = " + idd);
}
cur.close();
}
if (c != null) c.close();
- if (DBG) Rlog.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
+ if (DBG) Log.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
return idd;
}
}
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index afb9b6f..9b82828 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -40,7 +40,7 @@
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
import android.telephony.PackageChangeReceiver;
-import com.android.telephony.Rlog;
+import android.util.Log;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -565,7 +565,7 @@
int mode = appOps.unsafeCheckOp(opStr, applicationData.mUid,
applicationData.mPackageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
- Rlog.e(LOG_TAG, applicationData.mPackageName + " lost "
+ Log.e(LOG_TAG, applicationData.mPackageName + " lost "
+ opStr + ": "
+ (updateIfNeeded ? " (fixing)" : " (no permission to fix)"));
if (updateIfNeeded) {
@@ -643,7 +643,7 @@
int uid = packageManager.getPackageInfo(oldPackageName, 0).applicationInfo.uid;
setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT);
} catch (NameNotFoundException e) {
- Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
+ Log.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
}
}
@@ -750,7 +750,7 @@
// the package signature matches system signature.
final int result = packageManager.checkSignatures(context.getPackageName(), packageName);
if (result != PackageManager.SIGNATURE_MATCH) {
- Rlog.e(LOG_TAG, packageName + " does not have system signature");
+ Log.e(LOG_TAG, packageName + " does not have system signature");
return;
}
try {
@@ -758,13 +758,13 @@
int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid,
packageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
- Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)");
+ Log.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)");
setExclusiveAppops(packageName, appOps, info.applicationInfo.uid,
AppOpsManager.MODE_ALLOWED);
}
} catch (NameNotFoundException e) {
// No whitelisted system app on this device
- Rlog.e(LOG_TAG, "Package not found: " + packageName);
+ Log.e(LOG_TAG, "Package not found: " + packageName);
}
}
diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
index 06c3728..cd365a1 100644
--- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
+++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
@@ -24,13 +24,16 @@
import android.os.SystemProperties;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
-import com.android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
import com.android.internal.telephony.HbpcdLookup.MccIdd;
import com.android.internal.telephony.HbpcdLookup.MccLookup;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -143,7 +146,7 @@
// First check whether the number is a NANP number.
int nanpState = checkNANP(numberEntry, allIDDs);
- if (DBG) Rlog.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
+ if (DBG) Log.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
if ((nanpState == NP_NANP_LOCAL)
|| (nanpState == NP_NANP_AREA_LOCAL)
@@ -173,7 +176,7 @@
int internationalState = checkInternationalNumberPlan(context, numberEntry, allIDDs,
NANP_IDD);
- if (DBG) Rlog.d(TAG, "International type: " + getNumberPlanType(internationalState));
+ if (DBG) Log.d(TAG, "International type: " + getNumberPlanType(internationalState));
String returnNumber = null;
switch (internationalState) {
@@ -272,7 +275,7 @@
}
}
} catch (SQLException e) {
- Rlog.e(TAG, "Can't access HbpcdLookup database", e);
+ Log.e(TAG, "Can't access HbpcdLookup database", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -281,7 +284,7 @@
IDDS_MAPS.put(mcc, allIDDs);
- if (DBG) Rlog.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
+ if (DBG) Log.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
return allIDDs;
}
@@ -472,7 +475,7 @@
int tempCC = allCCs[i];
for (int j = 0; j < MAX_COUNTRY_CODES_LENGTH; j ++) {
if (tempCC == ccArray[j]) {
- if (DBG) Rlog.d(TAG, "Country code = " + tempCC);
+ if (DBG) Log.d(TAG, "Country code = " + tempCC);
return tempCC;
}
}
@@ -509,7 +512,7 @@
}
}
} catch (SQLException e) {
- Rlog.e(TAG, "Can't access HbpcdLookup database", e);
+ Log.e(TAG, "Can't access HbpcdLookup database", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -561,10 +564,10 @@
* Filter the destination number if using VZW sim card.
*/
public static String filterDestAddr(Context context, int subId, String destAddr) {
- if (DBG) Rlog.d(TAG, "enter filterDestAddr. destAddr=\"" + Rlog.pii(TAG, destAddr) + "\"" );
+ if (DBG) Log.d(TAG, "enter filterDestAddr. destAddr=\"" + pii(TAG, destAddr) + "\"" );
if (destAddr == null || !PhoneNumberUtils.isGlobalPhoneNumber(destAddr)) {
- Rlog.w(TAG, "destAddr" + Rlog.pii(TAG, destAddr) +
+ Log.w(TAG, "destAddr" + pii(TAG, destAddr) +
" is not a global phone number! Nothing changed.");
return destAddr;
}
@@ -585,9 +588,9 @@
}
if (DBG) {
- Rlog.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
- Rlog.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? Rlog.pii(TAG,
- result) : Rlog.pii(TAG, destAddr)) + "\"");
+ Log.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
+ Log.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? pii(TAG,
+ result) : pii(TAG, destAddr)) + "\"");
}
return result != null ? result : destAddr;
}
@@ -608,7 +611,7 @@
networkType = CDMA_HOME_NETWORK;
}
} else {
- if (DBG) Rlog.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
+ if (DBG) Log.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
}
return networkType;
@@ -650,4 +653,44 @@
// by default this value is false
return false;
}
+
+ /**
+ * Redact personally identifiable information for production users.
+ * @param tag used to identify the source of a log message
+ * @param pii the personally identifiable information we want to apply secure hash on.
+ * @return If tag is loggable in verbose mode or pii is null, return the original input.
+ * otherwise return a secure Hash of input pii
+ */
+ private static String pii(String tag, Object pii) {
+ String val = String.valueOf(pii);
+ if (pii == null || TextUtils.isEmpty(val) || Log.isLoggable(tag, Log.VERBOSE)) {
+ return val;
+ }
+ return "[" + secureHash(val.getBytes()) + "]";
+ }
+
+ /**
+ * Returns a secure hash (using the SHA1 algorithm) of the provided input.
+ *
+ * @return "****" if the build type is user, otherwise the hash
+ * @param input the bytes for which the secure hash should be computed.
+ */
+ private static String secureHash(byte[] input) {
+ // Refrain from logging user personal information in user build.
+ if (android.os.Build.IS_USER) {
+ return "****";
+ }
+
+ MessageDigest messageDigest;
+
+ try {
+ messageDigest = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ return "####";
+ }
+
+ byte[] result = messageDigest.digest(input);
+ return Base64.encodeToString(
+ result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+ }
}
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 4109ca6..05d4bf3 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -28,7 +28,6 @@
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
-import com.android.telephony.Rlog;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -521,7 +520,7 @@
return;
}
- if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next.");
+ if (DBG) Log.d(LOG_TAG, "No modify permission, check carrier privilege next.");
enforceCallingOrSelfCarrierPrivilege(context, subId, message);
}
@@ -539,7 +538,7 @@
}
if (DBG) {
- Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
+ Log.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
}
enforceCallingOrSelfCarrierPrivilege(context, subId, message);
@@ -559,7 +558,7 @@
}
if (DBG) {
- Rlog.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, "
+ Log.d(LOG_TAG, "No READ_PRIVILEGED_PHONE_STATE permission, "
+ "check carrier privilege next.");
}
@@ -567,6 +566,33 @@
}
/**
+ * Ensure the caller (or self, if not processing an IPC) has
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} or
+ * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or carrier privileges.
+ *
+ * @throws SecurityException if the caller does not have the required permission/privileges
+ */
+ public static void enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ Context context, int subId, String message) {
+ if (context.checkCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ == PERMISSION_GRANTED) {
+ return;
+ }
+
+ if (context.checkCallingOrSelfPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+ == PERMISSION_GRANTED) {
+ return;
+ }
+
+ if (DBG) {
+ Log.d(LOG_TAG, "No READ_PRIVILEGED_PHONE_STATE nor READ_PRECISE_PHONE_STATE permission"
+ + ", check carrier privilege next.");
+ }
+
+ enforceCallingOrSelfCarrierPrivilege(context, subId, message);
+ }
+
+ /**
* Make sure the caller (or self, if not processing an IPC) has carrier privileges.
*
* @throws SecurityException if the caller does not have the required privileges
@@ -584,7 +610,7 @@
Context context, int subId, int uid, String message) {
if (getCarrierPrivilegeStatus(context, subId, uid)
!= TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
- if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege.");
+ if (DBG) Log.e(LOG_TAG, "No Carrier Privilege.");
throw new SecurityException(message);
}
}
diff --git a/media/java/android/media/RouteDiscoveryRequest.aidl b/telephony/java/android/telephony/BarringInfo.aidl
similarity index 89%
copy from media/java/android/media/RouteDiscoveryRequest.aidl
copy to telephony/java/android/telephony/BarringInfo.aidl
index 744f656..50ddf6b 100644
--- a/media/java/android/media/RouteDiscoveryRequest.aidl
+++ b/telephony/java/android/telephony/BarringInfo.aidl
@@ -14,6 +14,7 @@
* limitations under the License.
*/
-package android.media;
+/** @hide */
+package android.telephony;
-parcelable RouteDiscoveryRequest;
+parcelable BarringInfo;
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
new file mode 100644
index 0000000..5419c3c
--- /dev/null
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides the barring configuration for a particular service type.
+ *
+ * Provides indication about the barring of a particular service for use. Certain barring types
+ * are only valid for certain technology families. Any service that does not have a barring
+ * configuration is unbarred by default.
+ */
+public final class BarringInfo implements Parcelable {
+
+ /**
+ * Barring Service Type
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "BARRING_SERVICE_TYPE_", value = {
+ BARRING_SERVICE_TYPE_CS_SERVICE,
+ BARRING_SERVICE_TYPE_PS_SERVICE,
+ BARRING_SERVICE_TYPE_CS_VOICE,
+ BARRING_SERVICE_TYPE_MO_SIGNALLING,
+ BARRING_SERVICE_TYPE_MO_DATA,
+ BARRING_SERVICE_TYPE_CS_FALLBACK,
+ BARRING_SERVICE_TYPE_MMTEL_VOICE,
+ BARRING_SERVICE_TYPE_MMTEL_VIDEO,
+ BARRING_SERVICE_TYPE_EMERGENCY,
+ BARRING_SERVICE_TYPE_SMS})
+ public @interface BarringServiceType {}
+
+ /* Applicabe to UTRAN */
+ /** Barring indicator for circuit-switched service; applicable to UTRAN */
+ public static final int BARRING_SERVICE_TYPE_CS_SERVICE =
+ android.hardware.radio.V1_5.BarringServiceType.CS_SERVICE;
+ /** Barring indicator for packet-switched service; applicable to UTRAN */
+ public static final int BARRING_SERVICE_TYPE_PS_SERVICE =
+ android.hardware.radio.V1_5.BarringServiceType.PS_SERVICE;
+ /** Barring indicator for circuit-switched voice service; applicable to UTRAN */
+ public static final int BARRING_SERVICE_TYPE_CS_VOICE =
+ android.hardware.radio.V1_5.BarringServiceType.CS_VOICE;
+
+ /* Applicable to EUTRAN, NGRAN */
+ /** Barring indicator for mobile-originated signalling; applicable to EUTRAN and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING =
+ android.hardware.radio.V1_5.BarringServiceType.MO_SIGNALLING;
+ /** Barring indicator for mobile-originated data traffic; applicable to EUTRAN and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_MO_DATA =
+ android.hardware.radio.V1_5.BarringServiceType.MO_DATA;
+ /** Barring indicator for circuit-switched fallback for voice; applicable to EUTRAN and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_CS_FALLBACK =
+ android.hardware.radio.V1_5.BarringServiceType.CS_FALLBACK;
+ /** Barring indicator for MMTEL (IMS) voice; applicable to EUTRAN and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE =
+ android.hardware.radio.V1_5.BarringServiceType.MMTEL_VOICE;
+ /** Barring indicator for MMTEL (IMS) video; applicable to EUTRAN and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO =
+ android.hardware.radio.V1_5.BarringServiceType.MMTEL_VIDEO;
+
+ /* Applicable to UTRAN, EUTRAN, NGRAN */
+ /** Barring indicator for emergency services; applicable to UTRAN, EUTRAN, and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_EMERGENCY =
+ android.hardware.radio.V1_5.BarringServiceType.EMERGENCY;
+ /** Barring indicator for SMS sending; applicable to UTRAN, EUTRAN, and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_SMS =
+ android.hardware.radio.V1_5.BarringServiceType.SMS;
+
+ //TODO: add barring constants for Operator-Specific barring codes
+
+ /** Describe the current barring configuration of a cell */
+ public static final class BarringServiceInfo implements Parcelable {
+ /**
+ * Barring Type
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "BARRING_TYPE_", value =
+ {BARRING_TYPE_NONE,
+ BARRING_TYPE_UNCONDITIONAL,
+ BARRING_TYPE_CONDITIONAL,
+ BARRING_TYPE_UNKNOWN})
+ public @interface BarringType {}
+
+ /** Barring is inactive */
+ public static final int BARRING_TYPE_NONE = android.hardware.radio.V1_5.BarringType.NONE;
+ /** The service is barred */
+ public static final int BARRING_TYPE_UNCONDITIONAL =
+ android.hardware.radio.V1_5.BarringType.UNCONDITIONAL;
+ /** The service may be barred based on additional factors */
+ public static final int BARRING_TYPE_CONDITIONAL =
+ android.hardware.radio.V1_5.BarringType.CONDITIONAL;
+
+ /** If a modem does not report barring info, then the barring type will be UNKNOWN */
+ public static final int BARRING_TYPE_UNKNOWN = -1;
+
+ private final @BarringType int mBarringType;
+
+ private final boolean mIsConditionallyBarred;
+ private final int mConditionalBarringFactor;
+ private final int mConditionalBarringTimeSeconds;
+
+ /** @hide */
+ public BarringServiceInfo(@BarringType int type) {
+ this(type, false, 0, 0);
+ }
+
+ /** @hide */
+ @TestApi
+ public BarringServiceInfo(@BarringType int barringType, boolean isConditionallyBarred,
+ int conditionalBarringFactor, int conditionalBarringTimeSeconds) {
+ mBarringType = barringType;
+ mIsConditionallyBarred = isConditionallyBarred;
+ mConditionalBarringFactor = conditionalBarringFactor;
+ mConditionalBarringTimeSeconds = conditionalBarringTimeSeconds;
+ }
+
+ public @BarringType int getBarringType() {
+ return mBarringType;
+ }
+
+ /**
+ * @return true if the conditional barring parameters have resulted in the service being
+ * barred; false if the service has either not been evaluated for conditional
+ * barring or has been evaluated and isn't barred.
+ */
+ public boolean isConditionallyBarred() {
+ return mIsConditionallyBarred;
+ }
+
+ /**
+ * @return the conditional barring factor as a percentage 0-100, which is the probability of
+ * a random device being barred for the service type.
+ */
+ public int getConditionalBarringFactor() {
+ return mConditionalBarringFactor;
+ }
+
+ /**
+ * @return the conditional barring time seconds, which is the interval between successive
+ * evaluations for conditional barring based on the barring factor.
+ */
+ @SuppressLint("MethodNameUnits")
+ public int getConditionalBarringTimeSeconds() {
+ return mConditionalBarringTimeSeconds;
+ }
+
+ /**
+ * Return whether a service is currently barred based on the BarringInfo
+ *
+ * @return true if the service is currently being barred, otherwise false
+ */
+ public boolean isBarred() {
+ return mBarringType == BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL
+ || (mBarringType == BarringServiceInfo.BARRING_TYPE_CONDITIONAL
+ && mIsConditionallyBarred);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBarringType, mIsConditionallyBarred,
+ mConditionalBarringFactor, mConditionalBarringTimeSeconds);
+ }
+
+ @Override
+ public boolean equals(Object rhs) {
+ if (!(rhs instanceof BarringServiceInfo)) return false;
+
+ BarringServiceInfo other = (BarringServiceInfo) rhs;
+ return mBarringType == other.mBarringType
+ && mIsConditionallyBarred == other.mIsConditionallyBarred
+ && mConditionalBarringFactor == other.mConditionalBarringFactor
+ && mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds;
+ }
+
+ /** @hide */
+ public BarringServiceInfo(Parcel p) {
+ mBarringType = p.readInt();
+ mIsConditionallyBarred = p.readBoolean();
+ mConditionalBarringFactor = p.readInt();
+ mConditionalBarringTimeSeconds = p.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mBarringType);
+ dest.writeBoolean(mIsConditionallyBarred);
+ dest.writeInt(mConditionalBarringFactor);
+ dest.writeInt(mConditionalBarringTimeSeconds);
+ }
+
+ /* @inheritDoc */
+ public static final @NonNull Parcelable.Creator<BarringServiceInfo> CREATOR =
+ new Parcelable.Creator<BarringServiceInfo>() {
+ @Override
+ public BarringServiceInfo createFromParcel(Parcel source) {
+ return new BarringServiceInfo(source);
+ }
+
+ @Override
+ public BarringServiceInfo[] newArray(int size) {
+ return new BarringServiceInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ }
+
+ private CellIdentity mCellIdentity;
+
+ // A SparseArray potentially mapping each BarringService type to a BarringServiceInfo config
+ // that describes the current barring status of that particular service.
+ private SparseArray<BarringServiceInfo> mBarringServiceInfos;
+
+ /** @hide */
+ @TestApi
+ @SystemApi
+ public BarringInfo() {
+ mBarringServiceInfos = new SparseArray<>();
+ }
+
+ /**
+ * Constructor for new BarringInfo instances.
+ *
+ * @hide
+ */
+ @TestApi
+ public BarringInfo(@Nullable CellIdentity barringCellId,
+ @NonNull SparseArray<BarringServiceInfo> barringServiceInfos) {
+ mCellIdentity = barringCellId;
+ mBarringServiceInfos = barringServiceInfos;
+ }
+
+ /** @hide */
+ public static BarringInfo create(
+ @NonNull android.hardware.radio.V1_5.CellIdentity halBarringCellId,
+ @NonNull List<android.hardware.radio.V1_5.BarringInfo> halBarringInfos) {
+ CellIdentity ci = CellIdentity.create(halBarringCellId);
+ SparseArray<BarringServiceInfo> serviceInfos = new SparseArray<>();
+
+ for (android.hardware.radio.V1_5.BarringInfo halBarringInfo : halBarringInfos) {
+ if (halBarringInfo.type == android.hardware.radio.V1_5.BarringType.CONDITIONAL) {
+ if (halBarringInfo.typeSpecificInfo.getDiscriminator()
+ != android.hardware.radio.V1_5.BarringTypeSpecificInfo
+ .hidl_discriminator.conditionalBarringInfo) {
+ // this is an error case where the barring info is conditional but the
+ // conditional barring fields weren't included
+ continue;
+ }
+ android.hardware.radio.V1_5.ConditionalBarringInfo conditionalInfo =
+ halBarringInfo.typeSpecificInfo.conditionalBarringInfo();
+ serviceInfos.put(
+ halBarringInfo.service, new BarringServiceInfo(
+ halBarringInfo.type, // will always be CONDITIONAL here
+ conditionalInfo.isBarred,
+ conditionalInfo.barringFactor,
+ conditionalInfo.barringTimeSeconds));
+ } else {
+ // Barring type is either NONE or UNCONDITIONAL
+ serviceInfos.put(
+ halBarringInfo.service, new BarringServiceInfo(halBarringInfo.type,
+ false, 0, 0));
+ }
+ }
+ return new BarringInfo(ci, serviceInfos);
+ }
+
+ /**
+ * Return whether a service is currently barred based on the BarringInfo
+ *
+ * @param service the service to be checked.
+ * @return true if the service is currently being barred, otherwise false
+ */
+ public boolean isServiceBarred(@BarringServiceType int service) {
+ BarringServiceInfo bsi = mBarringServiceInfos.get(service);
+ return bsi != null && (bsi.isBarred());
+ }
+
+ /**
+ * Get the BarringServiceInfo for a specified service.
+ *
+ * @return a BarringServiceInfo struct describing the current barring status for a service
+ */
+ public @NonNull BarringServiceInfo getBarringServiceInfo(@BarringServiceType int service) {
+ BarringServiceInfo bsi = mBarringServiceInfos.get(service);
+ // If barring is reported but not for a particular service, then we report the barring
+ // type as UNKNOWN; if the modem reports barring info but doesn't report for a particular
+ // service then we can safely assume that the service isn't barred (for instance because
+ // that particular service isn't applicable to the current RAN).
+ return (bsi != null) ? bsi : new BarringServiceInfo(
+ mBarringServiceInfos.size() > 0 ? BarringServiceInfo.BARRING_TYPE_NONE :
+ BarringServiceInfo.BARRING_TYPE_UNKNOWN);
+ }
+
+ /** @hide */
+ @SystemApi
+ public @NonNull BarringInfo createLocationInfoSanitizedCopy() {
+ return new BarringInfo(mCellIdentity.sanitizeLocationInfo(), mBarringServiceInfos);
+ }
+
+ /** @hide */
+ public BarringInfo(Parcel p) {
+ mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader());
+ mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader());
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mCellIdentity, flags);
+ dest.writeSparseArray(mBarringServiceInfos);
+ }
+
+ public static final @NonNull Parcelable.Creator<BarringInfo> CREATOR =
+ new Parcelable.Creator<BarringInfo>() {
+ @Override
+ public BarringInfo createFromParcel(Parcel source) {
+ return new BarringInfo(source);
+ }
+
+ @Override
+ public BarringInfo[] newArray(int size) {
+ return new BarringInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = mCellIdentity != null ? mCellIdentity.hashCode() : 7;
+ for (int i = 0; i < mBarringServiceInfos.size(); i++) {
+ hash = hash + 15 * mBarringServiceInfos.keyAt(i);
+ hash = hash + 31 * mBarringServiceInfos.valueAt(i).hashCode();
+ }
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object rhs) {
+ if (!(rhs instanceof BarringInfo)) return false;
+
+ BarringInfo bi = (BarringInfo) rhs;
+
+ if (hashCode() != bi.hashCode()) return false;
+
+ if (mBarringServiceInfos.size() != bi.mBarringServiceInfos.size()) return false;
+
+ for (int i = 0; i < mBarringServiceInfos.size(); i++) {
+ if (mBarringServiceInfos.keyAt(i) != bi.mBarringServiceInfos.keyAt(i)) return false;
+ if (!Objects.equals(mBarringServiceInfos.valueAt(i),
+ bi.mBarringServiceInfos.valueAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "BarringInfo {mCellIdentity=" + mCellIdentity
+ + ", mBarringServiceInfos=" + mBarringServiceInfos + "}";
+ }
+}
diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java
index e01deb2..1e1cdba 100644
--- a/telephony/java/android/telephony/CallQuality.java
+++ b/telephony/java/android/telephony/CallQuality.java
@@ -80,6 +80,9 @@
private int mMaxRelativeJitter;
private int mAverageRoundTripTime;
private int mCodecType;
+ private boolean mRtpInactivityDetected;
+ private boolean mRxSilenceDetected;
+ private boolean mTxSilenceDetected;
/** @hide **/
public CallQuality(Parcel in) {
@@ -94,6 +97,9 @@
mMaxRelativeJitter = in.readInt();
mAverageRoundTripTime = in.readInt();
mCodecType = in.readInt();
+ mRtpInactivityDetected = in.readBoolean();
+ mRxSilenceDetected = in.readBoolean();
+ mTxSilenceDetected = in.readBoolean();
}
/** @hide **/
@@ -109,7 +115,7 @@
* @param numRtpPacketsReceived RTP packets received from network
* @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never
* transmitted
- * @param numRtpPacketsNotReceived RTP packets which were lost in network and never recieved
+ * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received
* @param averageRelativeJitter average relative jitter in milliseconds
* @param maxRelativeJitter maximum relative jitter in milliseconds
* @param averageRoundTripTime average round trip delay in milliseconds
@@ -127,6 +133,48 @@
int maxRelativeJitter,
int averageRoundTripTime,
int codecType) {
+ this(downlinkCallQualityLevel, uplinkCallQualityLevel, callDuration,
+ numRtpPacketsTransmitted, numRtpPacketsReceived, numRtpPacketsTransmittedLost,
+ numRtpPacketsNotReceived, averageRelativeJitter, maxRelativeJitter,
+ averageRoundTripTime, codecType, false, false, false);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param callQualityLevel the call quality level (see #CallQualityLevel)
+ * @param callDuration the call duration in milliseconds
+ * @param numRtpPacketsTransmitted RTP packets sent to network
+ * @param numRtpPacketsReceived RTP packets received from network
+ * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never
+ * transmitted
+ * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received
+ * @param averageRelativeJitter average relative jitter in milliseconds
+ * @param maxRelativeJitter maximum relative jitter in milliseconds
+ * @param averageRoundTripTime average round trip delay in milliseconds
+ * @param codecType the codec type
+ * @param rtpInactivityDetected True if no incoming RTP is received for a continuous duration of
+ * 4 seconds
+ * @param rxSilenceDetected True if only silence RTP packets are received for 20 seconds
+ * immediately after call is connected
+ * @param txSilenceDetected True if only silence RTP packets are sent for 20 seconds immediately
+ * after call is connected
+ */
+ public CallQuality(
+ @CallQualityLevel int downlinkCallQualityLevel,
+ @CallQualityLevel int uplinkCallQualityLevel,
+ int callDuration,
+ int numRtpPacketsTransmitted,
+ int numRtpPacketsReceived,
+ int numRtpPacketsTransmittedLost,
+ int numRtpPacketsNotReceived,
+ int averageRelativeJitter,
+ int maxRelativeJitter,
+ int averageRoundTripTime,
+ int codecType,
+ boolean rtpInactivityDetected,
+ boolean rxSilenceDetected,
+ boolean txSilenceDetected) {
this.mDownlinkCallQualityLevel = downlinkCallQualityLevel;
this.mUplinkCallQualityLevel = uplinkCallQualityLevel;
this.mCallDuration = callDuration;
@@ -138,6 +186,9 @@
this.mMaxRelativeJitter = maxRelativeJitter;
this.mAverageRoundTripTime = averageRoundTripTime;
this.mCodecType = codecType;
+ this.mRtpInactivityDetected = rtpInactivityDetected;
+ this.mRxSilenceDetected = rxSilenceDetected;
+ this.mTxSilenceDetected = txSilenceDetected;
}
// getters
@@ -226,6 +277,29 @@
}
/**
+ * Returns true if no rtp packets are received continuously for the last 4 seconds
+ */
+ public boolean isRtpInactivityDetected() {
+ return mRtpInactivityDetected;
+ }
+
+ /**
+ * Returns true if only silence rtp packets are received for a duration of 20 seconds starting
+ * at call setup
+ */
+ public boolean isIncomingSilenceDetected() {
+ return mRxSilenceDetected;
+ }
+
+ /**
+ * Returns true if only silence rtp packets are sent for a duration of 20 seconds starting at
+ * call setup
+ */
+ public boolean isOutgoingSilenceDetected() {
+ return mTxSilenceDetected;
+ }
+
+ /**
* Returns the codec type. This value corresponds to the AUDIO_QUALITY_* constants in
* {@link ImsStreamMediaProfile}.
*
@@ -270,6 +344,9 @@
+ " maxRelativeJitter=" + mMaxRelativeJitter
+ " averageRoundTripTime=" + mAverageRoundTripTime
+ " codecType=" + mCodecType
+ + " rtpInactivityDetected=" + mRtpInactivityDetected
+ + " txSilenceDetected=" + mRxSilenceDetected
+ + " rxSilenceDetected=" + mTxSilenceDetected
+ "}";
}
@@ -286,7 +363,10 @@
mAverageRelativeJitter,
mMaxRelativeJitter,
mAverageRoundTripTime,
- mCodecType);
+ mCodecType,
+ mRtpInactivityDetected,
+ mRxSilenceDetected,
+ mTxSilenceDetected);
}
@Override
@@ -311,7 +391,10 @@
&& mAverageRelativeJitter == s.mAverageRelativeJitter
&& mMaxRelativeJitter == s.mMaxRelativeJitter
&& mAverageRoundTripTime == s.mAverageRoundTripTime
- && mCodecType == s.mCodecType);
+ && mCodecType == s.mCodecType
+ && mRtpInactivityDetected == s.mRtpInactivityDetected
+ && mRxSilenceDetected == s.mRxSilenceDetected
+ && mTxSilenceDetected == s.mTxSilenceDetected);
}
/**
@@ -336,6 +419,9 @@
dest.writeInt(mMaxRelativeJitter);
dest.writeInt(mAverageRoundTripTime);
dest.writeInt(mCodecType);
+ dest.writeBoolean(mRtpInactivityDetected);
+ dest.writeBoolean(mRxSilenceDetected);
+ dest.writeBoolean(mTxSilenceDetected);
}
public static final @android.annotation.NonNull Parcelable.Creator<CallQuality> CREATOR = new Parcelable.Creator() {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6a62237..5a7c3b3 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -16,8 +16,6 @@
package android.telephony;
-import com.android.telephony.Rlog;
-
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,6 +34,7 @@
import android.telephony.ims.ImsReasonInfo;
import com.android.internal.telephony.ICarrierConfigLoader;
+import com.android.telephony.Rlog;
/**
* Provides access to telephony configuration values that are carrier-specific.
@@ -300,7 +299,6 @@
/**
* A string array containing numbers that shouldn't be included in the call log.
- * @hide
*/
public static final String KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY =
"unloggable_numbers_string_array";
@@ -313,12 +311,11 @@
KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
/**
- * Do only allow auto selection in Advanced Network Settings when in home network.
+ * Only allow auto selection in Advanced Network Settings when in home network.
* Manual selection is allowed when in roaming network.
- * @hide
*/
- public static final String
- KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network";
+ public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL =
+ "only_auto_select_in_home_network";
/**
* Control whether users receive a simplified network settings UI and improved network
@@ -582,9 +579,6 @@
* registration state to change. That is, turning on or off mobile data will not cause VT to be
* enabled or disabled.
* When {@code false}, disabling mobile data will cause VT to be de-registered.
- * <p>
- * See also {@link #KEY_VILTE_DATA_IS_METERED_BOOL}.
- * @hide
*/
public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS =
"ignore_data_enabled_changed_for_video_calls";
@@ -648,7 +642,6 @@
/**
* Default WFC_IMS_enabled: true VoWiFi by default is on
* false VoWiFi by default is off
- * @hide
*/
public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL =
"carrier_default_wfc_ims_enabled_bool";
@@ -672,6 +665,12 @@
"carrier_promote_wfc_on_call_fail_bool";
/**
+ * Flag specifying whether provisioning is required for RCS.
+ */
+ public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL =
+ "carrier_rcs_provisioning_required_bool";
+
+ /**
* Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi
* Calling.
*/
@@ -714,9 +713,7 @@
*
* As of now, Verizon is the only carrier enforcing this dependency in their
* WFC awareness and activation requirements.
- *
- * @hide
- * */
+ */
public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL
= "carrier_volte_override_wfc_provisioning_bool";
@@ -848,9 +845,12 @@
"carrier_force_disable_etws_cmas_test_bool";
/**
- * The default flag specifying whether "Turn on Notifications" option will be always shown in
- * Settings->More->Emergency broadcasts menu regardless developer options is turned on or not.
+ * The default flag specifying whether "Allow alerts" option will be always shown in
+ * emergency alerts settings regardless developer options is turned on or not.
+ *
+ * @deprecated The allow alerts option is always shown now. No longer need a config for that.
*/
+ @Deprecated
public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL =
"always_show_emergency_alert_onoff_bool";
@@ -1074,7 +1074,6 @@
*
* When {@code false}, the old behavior is used, where the toggle in accessibility settings is
* used to set the IMS stack's RTT enabled state.
- * @hide
*/
public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL =
"ignore_rtt_mode_setting_bool";
@@ -1115,7 +1114,6 @@
* Determines whether the IMS conference merge process supports and returns its participants
* data. When {@code true}, on merge complete, conference call would have a list of its
* participants returned in XML format, {@code false otherwise}.
- * @hide
*/
public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL =
"support_ims_conference_event_package_bool";
@@ -1188,20 +1186,18 @@
public static final String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
/**
- * Determine whether user can switch Wi-Fi preferred or Cellular preferred in calling preference.
+ * Determine whether user can switch Wi-Fi preferred or Cellular preferred
+ * in calling preference.
* Some operators support Wi-Fi Calling only, not VoLTE.
* They don't need "Cellular preferred" option.
- * In this case, set uneditalbe attribute for preferred preference.
- * @hide
+ * In this case, set uneditable attribute for preferred preference.
*/
public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool";
- /**
- * Flag to indicate if Wi-Fi needs to be disabled in ECBM
- * @hide
- **/
- public static final String
- KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm";
+ /**
+ * Flag to indicate if Wi-Fi needs to be disabled in ECBM.
+ */
+ public static final String KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm";
/**
* List operator-specific error codes and indices of corresponding error strings in
@@ -1265,9 +1261,8 @@
public static final String KEY_WFC_SPN_USE_ROOT_LOCALE = "wfc_spn_use_root_locale";
/**
- * The Component Name of the activity that can setup the emergency addrees for WiFi Calling
+ * The Component Name of the activity that can setup the emergency address for WiFi Calling
* as per carrier requirement.
- * @hide
*/
public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING =
"wfc_emergency_address_carrier_app_string";
@@ -1431,22 +1426,19 @@
public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
/**
- * APN types that user is not allowed to modify
- * @hide
+ * APN types that user is not allowed to modify.
*/
public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY =
"read_only_apn_types_string_array";
/**
- * APN fields that user is not allowed to modify
- * @hide
+ * APN fields that user is not allowed to modify.
*/
public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY =
"read_only_apn_fields_string_array";
/**
* Default value of APN types field if not specified by user when adding/modifying an APN.
- * @hide
*/
public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY =
"apn_settings_default_apn_types_string_array";
@@ -1477,29 +1469,25 @@
"hide_digits_helper_text_on_stk_input_screen_bool";
/**
- * Boolean indicating if show data RAT icon on status bar even when data is disabled
- * @hide
+ * Boolean indicating if show data RAT icon on status bar even when data is disabled.
*/
public static final String KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL =
"always_show_data_rat_icon_bool";
/**
- * Boolean indicating if default data account should show LTE or 4G icon
- * @hide
+ * Boolean indicating if default data account should show LTE or 4G icon.
*/
public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL =
"show_4g_for_lte_data_icon_bool";
/**
* Boolean indicating if default data account should show 4G icon when in 3G.
- * @hide
*/
public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL =
"show_4g_for_3g_data_icon_bool";
/**
- * Boolean indicating if lte+ icon should be shown if available
- * @hide
+ * Boolean indicating if LTE+ icon should be shown if available.
*/
public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL =
"hide_lte_plus_data_icon_bool";
@@ -1514,10 +1502,8 @@
"operator_name_filter_pattern_string";
/**
- * The string is used to compare with operator name. If it matches the pattern then show
- * specific data icon.
- *
- * @hide
+ * The string is used to compare with operator name.
+ * If it matches the pattern then show specific data icon.
*/
public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING =
"show_carrier_data_icon_pattern_string";
@@ -1530,33 +1516,28 @@
"show_precise_failed_cause_bool";
/**
- * Boolean to decide whether lte is enabled.
- * @hide
+ * Boolean to decide whether LTE is enabled.
*/
public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
/**
* Boolean to decide whether TD-SCDMA is supported.
- * @hide
*/
public static final String KEY_SUPPORT_TDSCDMA_BOOL = "support_tdscdma_bool";
/**
* A list of mcc/mnc that support TD-SCDMA for device when connect to the roaming network.
- * @hide
*/
public static final String KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY =
"support_tdscdma_roaming_networks_string_array";
/**
* Boolean to decide whether world mode is enabled.
- * @hide
*/
public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
/**
* Flatten {@link android.content.ComponentName} of the carrier's settings activity.
- * @hide
*/
public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING =
"carrier_settings_activity_component_name_string";
@@ -1610,25 +1591,23 @@
/**
* Defines carrier-specific actions which act upon
* com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED, used for customization of the
- * default carrier app
+ * default carrier app.
* Format: "CARRIER_ACTION_IDX, ..."
* Where {@code CARRIER_ACTION_IDX} is an integer defined in
- * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+ * com.android.carrierdefaultapp.CarrierActionUtils
* Example:
- * {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS
- * disable_metered_apns}
- * @hide
+ * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS
+ * disables metered APNs
*/
- @UnsupportedAppUsage
+ @SuppressLint("IntentName")
public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY =
"carrier_default_actions_on_redirection_string_array";
/**
- * Defines carrier-specific actions which act upon
- * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
+ * Defines carrier-specific actions which act upon CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
* and configured signal args:
- * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_APN_TYPE_KEY apnType},
- * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_ERROR_CODE_KEY errorCode}
+ * android.telephony.TelephonyManager#EXTRA_APN_TYPE,
+ * android.telephony.TelephonyManager#EXTRA_ERROR_CODE
* used for customization of the default carrier app
* Format:
* {
@@ -1636,42 +1615,41 @@
* "APN_1, ERROR_CODE_2 : CARRIER_ACTION_IDX_1 "
* }
* Where {@code APN_1} is a string defined in
- * {@link com.android.internal.telephony.PhoneConstants PhoneConstants}
+ * com.android.internal.telephony.PhoneConstants
* Example: "default"
*
- * {@code ERROR_CODE_1} is an integer defined in
- * {@link DataFailCause DcFailure}
+ * {@code ERROR_CODE_1} is an integer defined in android.telephony.DataFailCause
* Example:
- * {@link DataFailCause#MISSING_UNKNOWN_APN}
+ * android.telephony.DataFailCause#MISSING_UNKNOWN_APN
*
* {@code CARRIER_ACTION_IDX_1} is an integer defined in
- * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+ * com.android.carrierdefaultapp.CarrierActionUtils
* Example:
- * {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS}
- * @hide
+ * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS
+ * disables metered APNs
*/
+ @SuppressLint("IntentName")
public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY =
"carrier_default_actions_on_dcfailure_string_array";
/**
- * Defines carrier-specific actions which act upon
- * com.android.internal.telephony.CARRIER_SIGNAL_RESET, used for customization of the
- * default carrier app
+ * Defines carrier-specific actions which act upon CARRIER_SIGNAL_RESET,
+ * used for customization of the default carrier app.
* Format: "CARRIER_ACTION_IDX, ..."
* Where {@code CARRIER_ACTION_IDX} is an integer defined in
- * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+ * com.android.carrierdefaultapp.CarrierActionUtils
* Example:
- * {@link com.android.carrierdefaultapp.CarrierActionUtils
- * #CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS clear all notifications on reset}
- * @hide
+ * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS
+ * clears all notifications on reset
*/
+ @SuppressLint("IntentName")
public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET =
"carrier_default_actions_on_reset_string_array";
/**
* Defines carrier-specific actions which act upon
* com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE,
- * used for customization of the default carrier app
+ * used for customization of the default carrier app.
* Format:
* {
* "true : CARRIER_ACTION_IDX_1",
@@ -1679,17 +1657,17 @@
* }
* Where {@code true} is a boolean indicates default network available/unavailable
* Where {@code CARRIER_ACTION_IDX} is an integer defined in
- * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+ * com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils
* Example:
- * {@link com.android.carrierdefaultapp.CarrierActionUtils
- * #CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER enable the app as the default URL handler}
- * @hide
+ * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER
+ * enables the app as the default URL handler
*/
+ @SuppressLint("IntentName")
public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE =
"carrier_default_actions_on_default_network_available_string_array";
+
/**
- * Defines a list of acceptable redirection url for default carrier app
- * @hides
+ * Defines a list of acceptable redirection url for default carrier app.
*/
public static final String KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY =
"carrier_default_redirection_url_string_array";
@@ -1817,10 +1795,10 @@
/**
* Determines whether to enable enhanced call blocking feature on the device.
- * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED
- * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE
- * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE
- * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN
+ * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED
+ * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE
+ * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE
+ * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN
*
* <p>
* 1. For Single SIM(SS) device, it can be customized in both carrier_config_mccmnc.xml
@@ -1830,7 +1808,6 @@
* function is used regardless of SIM.
* <p>
* If {@code true} enable enhanced call blocking feature on the device, {@code false} otherwise.
- * @hide
*/
public static final String KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL =
"support_enhanced_call_blocking_bool";
@@ -1941,7 +1918,6 @@
/**
* Flag indicating whether the carrier supports call deflection for an incoming IMS call.
- * @hide
*/
public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL =
"carrier_allow_deflect_ims_call_bool";
@@ -2006,8 +1982,6 @@
/**
* Whether system apps are allowed to use fallback if carrier video call is not available.
* Defaults to {@code true}.
- *
- * @hide
*/
public static final String KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL =
"allow_video_calling_fallback_bool";
@@ -2045,9 +2019,8 @@
"enhanced_4g_lte_title_variant_bool";
/**
- * The index indicates the carrier specified title string of Enahnce 4G LTE Mode settings.
+ * The index indicates the carrier specified title string of Enhanced 4G LTE Mode settings.
* Default value is 0, which indicates the default title string.
- * @hide
*/
public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT =
"enhanced_4g_lte_title_variant_int";
@@ -2091,15 +2064,13 @@
* {@link #KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL} is false. If
* {@link #KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL} is true, this
* configuration is ignored and roaming preference cannot be changed.
- * @hide
*/
public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL =
"editable_wfc_roaming_mode_bool";
/**
- * Flag specifying wether to show blocking pay phone option in blocked numbers screen. Only show
- * the option if payphone call presentation represents in the carrier's region.
- * @hide
+ * Flag specifying whether to show blocking pay phone option in blocked numbers screen.
+ * Only show the option if payphone call presentation is present in the carrier's region.
*/
public static final java.lang.String KEY_SHOW_BLOCKING_PAY_PHONE_OPTION_BOOL =
"show_blocking_pay_phone_option_bool";
@@ -2109,7 +2080,6 @@
* {@code false} - roaming preference can be selected separately from the home preference.
* {@code true} - roaming preference is the same as home preference and
* {@link #KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT} is used as the default value.
- * @hide
*/
public static final String KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL =
"use_wfc_home_network_mode_in_roaming_network_bool";
@@ -2137,7 +2107,6 @@
* while the device is registered over WFC. Default value is -1, which indicates
* that this notification is not pertinent for a particular carrier. We've added a delay
* to prevent false positives.
- * @hide
*/
public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT =
"emergency_notification_delay_int";
@@ -2188,7 +2157,6 @@
/**
* Flag specifying whether to show an alert dialog for video call charges.
* By default this value is {@code false}.
- * @hide
*/
public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL =
"show_video_call_charges_alert_dialog_bool";
@@ -2475,10 +2443,9 @@
/**
* Identifies if the key is available for WLAN or EPDG or both. The value is a bitmask.
* 0 indicates that neither EPDG or WLAN is enabled.
- * 1 indicates that key type {@link TelephonyManager#KEY_TYPE_EPDG} is enabled.
- * 2 indicates that key type {@link TelephonyManager#KEY_TYPE_WLAN} is enabled.
+ * 1 indicates that key type TelephonyManager#KEY_TYPE_EPDG is enabled.
+ * 2 indicates that key type TelephonyManager#KEY_TYPE_WLAN is enabled.
* 3 indicates that both are enabled.
- * @hide
*/
public static final String IMSI_KEY_AVAILABILITY_INT = "imsi_key_availability_int";
@@ -2496,7 +2463,6 @@
/**
* Flag specifying whether IMS registration state menu is shown in Status Info setting,
* default to false.
- * @hide
*/
public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL =
"show_ims_registration_status_bool";
@@ -2552,7 +2518,6 @@
/**
* The flag to disable the popup dialog which warns the user of data charges.
- * @hide
*/
public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL =
"disable_charge_indication_bool";
@@ -2617,15 +2582,13 @@
/**
* Determines whether any carrier has been identified and its specific config has been applied,
* default to false.
- * @hide
*/
public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
/**
* Determines whether we should show a warning asking the user to check with their carrier
- * on pricing when the user enabled data roaming.
+ * on pricing when the user enabled data roaming,
* default to false.
- * @hide
*/
public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL =
"check_pricing_with_carrier_data_roaming_bool";
@@ -2777,10 +2740,10 @@
* Specifies a carrier-defined {@link android.telecom.CallRedirectionService} which Telecom
* will bind to for outgoing calls. An empty string indicates that no carrier-defined
* {@link android.telecom.CallRedirectionService} is specified.
- * @hide
*/
public static final String KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING =
"call_redirection_service_component_name_string";
+
/**
* Support for the original string display of CDMA MO call.
* By default, it is disabled.
@@ -2893,8 +2856,8 @@
"call_waiting_service_class_int";
/**
- * This configuration allow the system UI to display different 5G icon for different 5G
- * scenario.
+ * This configuration allows the system UI to display different 5G icons for different 5G
+ * scenarios.
*
* There are five 5G scenarios:
* 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
@@ -2911,24 +2874,22 @@
* 5G cell as a secondary cell) but the use of 5G is restricted.
*
* The configured string contains multiple key-value pairs separated by comma. For each pair,
- * the key and value is separated by a colon. The key is corresponded to a 5G status above and
+ * the key and value are separated by a colon. The key corresponds to a 5G status above and
* the value is the icon name. Use "None" as the icon name if no icon should be shown in a
* specific 5G scenario. If the scenario is "None", config can skip this key and value.
*
* Icon name options: "5G_Plus", "5G".
*
* Here is an example:
- * UE want to display 5G_Plus icon for scenario#1, and 5G icon for scenario#2; otherwise no
+ * UE wants to display 5G_Plus icon for scenario#1, and 5G icon for scenario#2; otherwise not
* define.
* The configuration is: "connected_mmwave:5G_Plus,connected:5G"
- *
- * @hide
*/
public static final String KEY_5G_ICON_CONFIGURATION_STRING =
"5g_icon_configuration_string";
/**
- * Timeout in second for displaying 5G icon, default value is 0 which means the timer is
+ * Timeout in seconds for displaying 5G icon, default value is 0 which means the timer is
* disabled.
*
* System UI will show the 5G icon and start a timer with the timeout from this config when the
@@ -2937,8 +2898,6 @@
*
* If 5G is reacquired during this timer, the timer is canceled and restarted when 5G is next
* lost. Allows us to momentarily lose 5G without blinking the icon.
- *
- * @hide
*/
public static final String KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT =
"5g_icon_display_grace_period_sec_int";
@@ -3103,8 +3062,6 @@
* signal bar of primary network. By default it will be false, meaning whenever data
* is going over opportunistic network, signal bar will reflect signal strength and rat
* icon of that network.
- *
- * @hide
*/
public static final String KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN =
"always_show_primary_signal_bar_in_opportunistic_network_boolean";
@@ -3326,8 +3283,6 @@
/**
* Determines whether wifi calling location privacy policy is shown.
- *
- * @hide
*/
public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL =
"show_wfc_location_privacy_policy_bool";
@@ -3438,8 +3393,8 @@
"support_wps_over_ims_bool";
/**
- * Holds the list of carrier certificate hashes. Note that each carrier has its own certificates
- * @hide
+ * Holds the list of carrier certificate hashes.
+ * Note that each carrier has its own certificates.
*/
public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY =
"carrier_certificate_string_array";
@@ -3497,6 +3452,7 @@
sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2);
sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2);
sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false);
+ sDefaults.putBoolean(KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, false);
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 8e703fe..3f0aeb5 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -16,16 +16,17 @@
package android.telephony;
-import com.android.telephony.Rlog;
-
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.hardware.radio.V1_0.CellInfoType;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.telephony.Rlog;
+
import java.util.Objects;
import java.util.UUID;
@@ -324,6 +325,86 @@
}
/** @hide */
+ public static CellIdentity create(android.hardware.radio.V1_0.CellIdentity cellIdentity) {
+ if (cellIdentity == null) return null;
+ switch(cellIdentity.cellInfoType) {
+ case CellInfoType.GSM: {
+ if (cellIdentity.cellIdentityGsm.size() == 1) {
+ return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
+ }
+ break;
+ }
+ case CellInfoType.WCDMA: {
+ if (cellIdentity.cellIdentityWcdma.size() == 1) {
+ return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.TD_SCDMA: {
+ if (cellIdentity.cellIdentityTdscdma.size() == 1) {
+ return new CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.LTE: {
+ if (cellIdentity.cellIdentityLte.size() == 1) {
+ return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
+ }
+ break;
+ }
+ case CellInfoType.CDMA: {
+ if (cellIdentity.cellIdentityCdma.size() == 1) {
+ return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.NONE: break;
+ default: break;
+ }
+ return null;
+ }
+
+ /** @hide */
+ public static CellIdentity create(android.hardware.radio.V1_2.CellIdentity cellIdentity) {
+ if (cellIdentity == null) return null;
+ switch(cellIdentity.cellInfoType) {
+ case CellInfoType.GSM: {
+ if (cellIdentity.cellIdentityGsm.size() == 1) {
+ return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
+ }
+ break;
+ }
+ case CellInfoType.WCDMA: {
+ if (cellIdentity.cellIdentityWcdma.size() == 1) {
+ return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.TD_SCDMA: {
+ if (cellIdentity.cellIdentityTdscdma.size() == 1) {
+ return new CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.LTE: {
+ if (cellIdentity.cellIdentityLte.size() == 1) {
+ return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
+ }
+ break;
+ }
+ case CellInfoType.CDMA: {
+ if (cellIdentity.cellIdentityCdma.size() == 1) {
+ return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.NONE: break;
+ default: break;
+ }
+ return null;
+ }
+
+ /** @hide */
public static CellIdentity create(android.hardware.radio.V1_5.CellIdentity ci) {
if (ci == null) return null;
switch (ci.getDiscriminator()) {
diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java
index 1193199..2d0bd52 100644
--- a/telephony/java/android/telephony/CellLocation.java
+++ b/telephony/java/android/telephony/CellLocation.java
@@ -53,8 +53,7 @@
/**
* Create a new CellLocation from a intent notifier Bundle
*
- * This method is used by PhoneStateIntentReceiver and maybe by
- * external applications.
+ * This method maybe used by external applications.
*
* @param bundle Bundle from intent notifier
* @return newly created CellLocation
diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java
index 39af34c..c706d28 100644
--- a/telephony/java/android/telephony/ImsManager.java
+++ b/telephony/java/android/telephony/ImsManager.java
@@ -17,6 +17,8 @@
package android.telephony.ims;
import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -35,7 +37,26 @@
private Context mContext;
- /** @hide */
+ /**
+ * <p>Broadcast Action: Indicates that an IMS operation was rejected by the network due to it
+ * not being authorized on the network.
+ * May include the {@link SubscriptionManager#EXTRA_SUBSCRIPTION_INDEX} extra to also specify
+ * which subscription the operation was rejected for.
+ * <p class="note">
+ * Carrier applications may listen to this broadcast to be notified of possible IMS provisioning
+ * issues.
+ */
+ // Moved from TelephonyIntents, need to keep backwards compatibility with OEM apps that have
+ // this value hard-coded in BroadcastReceiver.
+ @SuppressLint("ActionValue")
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION =
+ "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+
+ /**
+ * Use {@link Context#getSystemService(String)} to get an instance of this class.
+ * @hide
+ */
public ImsManager(@NonNull Context context) {
mContext = context;
}
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 31434c1..0cfb8c5 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -20,6 +20,9 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.LinkProperties;
import android.os.Build;
@@ -31,8 +34,6 @@
import android.telephony.Annotation.NetworkType;
import android.telephony.data.ApnSetting;
-import dalvik.system.VMRuntime;
-
import java.util.Objects;
@@ -134,6 +135,13 @@
}
/**
+ * To check the SDK version for {@link PreciseDataConnectionState#getDataConnectionState}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long GET_DATA_CONNECTION_STATE_CODE_CHANGE = 147600208L;
+
+ /**
* Returns the state of data connection that supported the apn types returned by
* {@link #getDataConnectionApnTypeBitMask()}
*
@@ -144,7 +152,7 @@
@SystemApi
public @DataState int getDataConnectionState() {
if (mState == TelephonyManager.DATA_DISCONNECTING
- && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ && !Compatibility.isChangeEnabled(GET_DATA_CONNECTION_STATE_CODE_CHANGE)) {
return TelephonyManager.DATA_CONNECTED;
}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 2c62d06..2c8014e 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -16,8 +16,6 @@
package android.telephony;
-import com.android.telephony.Rlog;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,6 +34,8 @@
import android.telephony.NetworkRegistrationInfo.NRState;
import android.text.TextUtils;
+import com.android.telephony.Rlog;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -353,6 +353,7 @@
private String mOperatorAlphaLongRaw;
private String mOperatorAlphaShortRaw;
+ private boolean mIsDataRoamingFromRegistration;
private boolean mIsIwlanPreferred;
/**
@@ -438,6 +439,7 @@
mNrFrequencyRange = s.mNrFrequencyRange;
mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw;
mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw;
+ mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration;
mIsIwlanPreferred = s.mIsIwlanPreferred;
}
@@ -472,6 +474,7 @@
mNrFrequencyRange = in.readInt();
mOperatorAlphaLongRaw = in.readString();
mOperatorAlphaShortRaw = in.readString();
+ mIsDataRoamingFromRegistration = in.readBoolean();
mIsIwlanPreferred = in.readBoolean();
}
@@ -499,6 +502,7 @@
out.writeInt(mNrFrequencyRange);
out.writeString(mOperatorAlphaLongRaw);
out.writeString(mOperatorAlphaShortRaw);
+ out.writeBoolean(mIsDataRoamingFromRegistration);
out.writeBoolean(mIsIwlanPreferred);
}
@@ -584,8 +588,8 @@
*/
@DuplexMode
public int getDuplexMode() {
- // only support LTE duplex mode
- if (!isLte(getRilDataRadioTechnology())) {
+ // support LTE/NR duplex mode
+ if (!isPsOnlyTech(getRilDataRadioTechnology())) {
return DUPLEX_MODE_UNKNOWN;
}
@@ -649,7 +653,9 @@
}
/**
- * Get current data network roaming type
+ * Get whether the current data network is roaming.
+ * This value may be overwritten by resource overlay or carrier configuration.
+ * @see #getDataRoamingFromRegistration() to get the value from the network registration.
* @return roaming type
* @hide
*/
@@ -659,18 +665,25 @@
}
/**
- * Get whether data network registration state is roaming
+ * Set whether the data network registration state is roaming.
+ * This should only be set to the roaming value received
+ * once the data registration phase has completed.
+ * @hide
+ */
+ public void setDataRoamingFromRegistration(boolean dataRoaming) {
+ mIsDataRoamingFromRegistration = dataRoaming;
+ }
+
+ /**
+ * Get whether data network registration state is roaming.
+ * This value is set directly from the modem and will not be overwritten
+ * by resource overlay or carrier configuration.
* @return true if registration indicates roaming, false otherwise
* @hide
*/
+ @SystemApi
public boolean getDataRoamingFromRegistration() {
- final NetworkRegistrationInfo regState = getNetworkRegistrationInfo(
- NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
- if (regState != null) {
- return regState.getRegistrationState()
- == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
- }
- return false;
+ return mIsDataRoamingFromRegistration;
}
/**
@@ -873,6 +886,7 @@
mNrFrequencyRange,
mOperatorAlphaLongRaw,
mOperatorAlphaShortRaw,
+ mIsDataRoamingFromRegistration,
mIsIwlanPreferred);
}
}
@@ -903,6 +917,7 @@
&& mNetworkRegistrationInfos.size() == s.mNetworkRegistrationInfos.size()
&& mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos)
&& mNrFrequencyRange == s.mNrFrequencyRange
+ && mIsDataRoamingFromRegistration == s.mIsDataRoamingFromRegistration
&& mIsIwlanPreferred == s.mIsIwlanPreferred;
}
}
@@ -1062,6 +1077,8 @@
.append(", mNrFrequencyRange=").append(mNrFrequencyRange)
.append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
.append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
+ .append(", mIsDataRoamingFromRegistration=")
+ .append(mIsDataRoamingFromRegistration)
.append(", mIsIwlanPreferred=").append(mIsIwlanPreferred)
.append("}").toString();
}
@@ -1102,6 +1119,7 @@
}
mOperatorAlphaLongRaw = null;
mOperatorAlphaShortRaw = null;
+ mIsDataRoamingFromRegistration = false;
mIsIwlanPreferred = false;
}
@@ -1624,7 +1642,8 @@
* @return Current data network type
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ @SystemApi
+ @TestApi
public @NetworkType int getDataNetworkType() {
final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
@@ -1717,9 +1736,10 @@
}
/** @hide */
- public static boolean isLte(int radioTechnology) {
- return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE ||
- radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA;
+ public static boolean isPsOnlyTech(int radioTechnology) {
+ return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_NR;
}
/** @hide */
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 1f7d55f..1c58f8f 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -87,8 +87,7 @@
/**
* Create a new SignalStrength from a intent notifier Bundle
*
- * This method is used by PhoneStateIntentReceiver and maybe by
- * external applications.
+ * This method may be used by external applications.
*
* @param m Bundle from intent notifier
* @return newly created SignalStrength
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index db33be3..8ed4ee5 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -281,6 +281,42 @@
*/
public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1;
+ /** @hide */
+ @IntDef(prefix = { "PREMIUM_SMS_CONSENT" }, value = {
+ SmsManager.PREMIUM_SMS_CONSENT_UNKNOWN,
+ SmsManager.PREMIUM_SMS_CONSENT_ASK_USER,
+ SmsManager.PREMIUM_SMS_CONSENT_NEVER_ALLOW,
+ SmsManager.PREMIUM_SMS_CONSENT_ALWAYS_ALLOW
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PremiumSmsConsent {}
+
+ /** Premium SMS Consent for the package is unknown. This indicates that the user
+ * has not set a permission for this package, because this package has never tried
+ * to send a premium SMS.
+ * @hide
+ */
+ @SystemApi
+ public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0;
+
+ /** Default premium SMS Consent (ask user for each premium SMS sent).
+ * @hide
+ */
+ @SystemApi
+ public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1;
+
+ /** Premium SMS Consent when the owner has denied the app from sending premium SMS.
+ * @hide
+ */
+ @SystemApi
+ public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2;
+
+ /** Premium SMS Consent when the owner has allowed the app to send premium SMS.
+ * @hide
+ */
+ @SystemApi
+ public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3;
+
// result of asking the user for a subscription to perform an operation.
private interface SubscriptionResolverResult {
void onSuccess(int subId);
@@ -2539,7 +2575,7 @@
* </p>
*
* @return the bundle key/values pairs that contains MMS configuration values
- * or an empty bundle if they cannot be found.
+ * or an empty Bundle if they cannot be found.
*/
@NonNull public Bundle getCarrierConfigValues() {
try {
@@ -2869,4 +2905,53 @@
}
return false;
}
+
+ /**
+ * Gets the premium SMS permission for the specified package. If the package has never
+ * been seen before, the default {@link SmsManager#PREMIUM_SMS_PERMISSION_ASK_USER}
+ * will be returned.
+ * @param packageName the name of the package to query permission
+ * @return one of {@link SmsManager#PREMIUM_SMS_CONSENT_UNKNOWN},
+ * {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER},
+ * {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or
+ * {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW}
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @PremiumSmsConsent int getPremiumSmsConsent(@NonNull String packageName) {
+ int permission = 0;
+ try {
+ ISms iSms = getISmsService();
+ if (iSms != null) {
+ permission = iSms.getPremiumSmsPermission(packageName);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getPremiumSmsPermission() RemoteException", e);
+ }
+ return permission;
+ }
+
+ /**
+ * Sets the premium SMS permission for the specified package and save the value asynchronously
+ * to persistent storage.
+ * @param packageName the name of the package to set permission
+ * @param permission one of {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER},
+ * {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or
+ * {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW}
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setPremiumSmsConsent(
+ @NonNull String packageName, @PremiumSmsConsent int permission) {
+ try {
+ ISms iSms = getISmsService();
+ if (iSms != null) {
+ iSms.setPremiumSmsPermission(packageName, permission);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "setPremiumSmsPermission() RemoteException", e);
+ }
+ }
}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index c217b8b..eefbd44 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -20,8 +20,13 @@
import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.StringDef;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.os.Binder;
@@ -31,8 +36,10 @@
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
import com.android.internal.telephony.Sms7BitEncodingTranslator;
import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
+import com.android.internal.telephony.cdma.sms.UserData;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -56,6 +63,16 @@
UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
}
+ /** @hide */
+ @IntDef(prefix = { "ENCODING_" }, value = {
+ ENCODING_UNKNOWN,
+ ENCODING_7BIT,
+ ENCODING_8BIT,
+ ENCODING_16BIT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EncodingSize {}
+
/** User data text encoding code unit size */
public static final int ENCODING_UNKNOWN = 0;
public static final int ENCODING_7BIT = 1;
@@ -315,6 +332,34 @@
}
/**
+ * Create an SmsMessage from a native SMS-Submit PDU, specified by Bluetooth Message Access
+ * Profile Specification v1.4.2 5.8.
+ * This is used by Bluetooth MAP profile to decode message when sending non UTF-8 SMS messages.
+ *
+ * @param data Message data.
+ * @param isCdma Indicates weather the type of the SMS is CDMA.
+ * @return An SmsMessage representing the message.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public static SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) {
+ SmsMessageBase wrappedMessage;
+
+ if (isCdma) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+ 0, data);
+ } else {
+ // Bluetooth uses its own method to decode GSM PDU so this part is not called.
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+ 0, data);
+ }
+
+ return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null;
+ }
+
+ /**
* Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
* length in bytes (not hex chars) less the SMSC header
*
@@ -633,6 +678,83 @@
}
/**
+ * Get an SMS-SUBMIT PDU's encoded message.
+ * This is used by Bluetooth MAP profile to handle long non UTF-8 SMS messages.
+ *
+ * @param isTypeGsm true when message's type is GSM, false when type is CDMA
+ * @param destinationAddress the address of the destination for the message
+ * @param message message content
+ * @param encoding User data text encoding code unit size
+ * @param languageTable GSM national language table to use, specified by 3GPP
+ * 23.040 9.2.3.24.16
+ * @param languageShiftTable GSM national language shift table to use, specified by 3GPP
+ * 23.040 9.2.3.24.15
+ * @param refNumber parameter to create SmsHeader
+ * @param seqNumber parameter to create SmsHeader
+ * @param msgCount parameter to create SmsHeader
+ * @return a byte[] containing the encoded message
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @SystemApi
+ @NonNull
+ public static byte[] getSubmitPduEncodedMessage(boolean isTypeGsm,
+ @NonNull String destinationAddress,
+ @NonNull String message,
+ @EncodingSize int encoding, int languageTable,
+ int languageShiftTable, int refNumber,
+ int seqNumber, int msgCount) {
+ byte[] data;
+ SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+ concatRef.refNumber = refNumber;
+ concatRef.seqNumber = seqNumber; // 1-based sequence
+ concatRef.msgCount = msgCount;
+ // We currently set this to true since our messaging app will never
+ // send more than 255 parts (it converts the message to MMS well before that).
+ // However, we should support 3rd party messaging apps that might need 16-bit
+ // references
+ // Note: It's not sufficient to just flip this bit to true; it will have
+ // ripple effects (several calculations assume 8-bit ref).
+ concatRef.isEightBits = true;
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.concatRef = concatRef;
+
+ /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding
+ * will be determined(again) by getSubmitPdu().
+ * All packets need to be encoded using the same encoding, as the bMessage
+ * only have one filed to describe the encoding for all messages in a concatenated
+ * SMS... */
+ if (encoding == ENCODING_7BIT) {
+ smsHeader.languageTable = languageTable;
+ smsHeader.languageShiftTable = languageShiftTable;
+ }
+
+ if (isTypeGsm) {
+ data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null,
+ destinationAddress, message, false,
+ SmsHeader.toByteArray(smsHeader), encoding, languageTable,
+ languageShiftTable).encodedMessage;
+ } else { // SMS_TYPE_CDMA
+ UserData uData = new UserData();
+ uData.payloadStr = message;
+ uData.userDataHeader = smsHeader;
+ if (encoding == ENCODING_7BIT) {
+ uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+ } else { // assume UTF-16
+ uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+ }
+ uData.msgEncodingSet = true;
+ data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
+ destinationAddress, uData, false).encodedMessage;
+ }
+ if (data == null) {
+ return new byte[0];
+ }
+ return data;
+ }
+
+ /**
* Returns the address of the SMS service center that relayed this message
* or null if there is none.
*/
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 4510fed..36f541f 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -16,8 +16,6 @@
package android.telephony;
-import com.android.telephony.Rlog;
-
import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
@@ -67,6 +65,7 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.util.HandlerExecutor;
import com.android.internal.util.Preconditions;
+import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -2240,10 +2239,9 @@
@UnsupportedAppUsage
public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId, int subId) {
if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId);
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
- intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId);
intent.putExtra(EXTRA_SLOT_INDEX, phoneId);
intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
+ putSubscriptionIdExtra(intent, subId);
}
/**
@@ -3489,4 +3487,19 @@
}
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
+
+ /**
+ * Helper method that puts a subscription id on an intent with the constants:
+ * PhoneConstant.SUBSCRIPTION_KEY and SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX.
+ * Both constants are used to support backwards compatibility. Once we know we got all places,
+ * we can remove PhoneConstants.SUBSCRIPTION_KEY.
+ * @param intent Intent to put sub id on.
+ * @param subId SubscriptionId to put on intent.
+ *
+ * @hide
+ */
+ public static void putSubscriptionIdExtra(Intent intent, int subId) {
+ intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+ intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 843c065..13aad7e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -46,6 +46,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
@@ -108,8 +109,6 @@
import com.android.internal.telephony.SmsApplication;
import com.android.telephony.Rlog;
-import dalvik.system.VMRuntime;
-
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -449,12 +448,8 @@
case UNKNOWN:
modemCount = MODEM_COUNT_SINGLE_MODEM;
// check for voice and data support, 0 if not supported
- if (!isVoiceCapable() && !isSmsCapable() && mContext != null) {
- ConnectivityManager cm = (ConnectivityManager) mContext
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- if (cm != null && !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
- modemCount = MODEM_COUNT_NO_MODEM;
- }
+ if (!isVoiceCapable() && !isSmsCapable() && !isDataCapable()) {
+ modemCount = MODEM_COUNT_NO_MODEM;
}
break;
case DSDS:
@@ -1109,6 +1104,16 @@
*/
public static final int CDMA_ROAMING_MODE_ANY = 2;
+ /** @hide */
+ @IntDef(prefix = { "CDMA_ROAMING_MODE_" }, value = {
+ CDMA_ROAMING_MODE_RADIO_DEFAULT,
+ CDMA_ROAMING_MODE_HOME,
+ CDMA_ROAMING_MODE_AFFILIATED,
+ CDMA_ROAMING_MODE_ANY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CdmaRoamingMode{}
+
/**
* An unknown carrier id. It could either be subscription unavailable or the subscription
* carrier cannot be recognized. Unrecognized carriers here means
@@ -1447,27 +1452,10 @@
"android.telephony.extra.SIM_COMBINATION_NAMES";
/**
- * Broadcast Action: The time was set by the carrier (typically by the NITZ string).
- * This is a sticky broadcast.
- * The intent will have the following extra values:</p>
- * <ul>
- * <li><em>time</em> - The time as a long in UTC milliseconds.</li>
- * </ul>
- *
- * <p class="note">
- * Requires the READ_PHONE_STATE permission.
- *
- * <p class="note">This is a protected intent that can only be sent by the system.
- *
- * @hide
- */
- @SystemApi
- public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
-
- /**
* <p>Broadcast Action: The emergency callback mode is changed.
* <ul>
- * <li><em>phoneinECMState</em> - A boolean value,true=phone in ECM, false=ECM off</li>
+ * <li><em>EXTRA_PHONE_IN_ECM_STATE</em> - A boolean value,true=phone in ECM,
+ * false=ECM off</li>
* </ul>
* <p class="note">
* You can <em>not</em> receive this through components declared
@@ -1477,18 +1465,218 @@
*
* <p class="note">This is a protected intent that can only be sent by the system.
*
+ * @see #EXTRA_PHONE_IN_ECM_STATE
+ *
* @hide
*/
@SystemApi
@SuppressLint("ActionValue")
- public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
- = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
+ public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED =
+ "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
+
+
+ /**
+ * Extra included in {@link #ACTION_EMERGENCY_CALLBACK_MODE_CHANGED}.
+ * Indicates whether the phone is in an emergency phone state.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_PHONE_IN_ECM_STATE =
+ "android.telephony.extra.PHONE_IN_ECM_STATE";
+
+ /**
+ * <p>Broadcast Action: when data connections get redirected with validation failure.
+ * intended for sim/account status checks and only sent to the specified carrier app
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+ * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+ * <li>{@link #EXTRA_REDIRECTION_URL}</li><dd>redirection url string</dd>
+ * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system.</p>
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_CARRIER_SIGNAL_REDIRECTED =
+ "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
+
+ /**
+ * <p>Broadcast Action: when data connections setup fails.
+ * intended for sim/account status checks and only sent to the specified carrier app
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+ * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+ * <li>{@link #EXTRA_ERROR_CODE}</li><dd>A integer with dataFailCause.</dd>
+ * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system. </p>
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED =
+ "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
+
+ /**
+ * <p>Broadcast Action: when pco value is available.
+ * intended for sim/account status checks and only sent to the specified carrier app
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+ * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+ * <li>{@link #EXTRA_APN_PROTOCOL}</li><dd>A string with the protocol of the apn connection
+ * (IP,IPV6, IPV4V6)</dd>
+ * <li>{@link #EXTRA_APN_PROTOCOL_INT}</li><dd>A integer with the protocol of the apn
+ * connection (IP,IPV6, IPV4V6)</dd>
+ * <li>{@link #EXTRA_PCO_ID}</li><dd>An integer indicating the pco id for the data.</dd>
+ * <li>{@link #EXTRA_PCO_VALUE}</li><dd>A byte array of pco data read from modem.</dd>
+ * <li>subId</li><dd>Sub Id which associated the data connection.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system. </p>
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE =
+ "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
+
+ /**
+ * <p>Broadcast Action: when system default network available/unavailable with
+ * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when
+ * other network becomes system default network, Wi-Fi for example.
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>{@link #EXTRA_DEFAULT_NETWORK_AVAILABLE}</li>
+ * <dd>A boolean indicates default network available.</dd>
+ * <li>subId</li><dd>Sub Id which associated the default data.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system. </p>
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE =
+ "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
+
+ /**
+ * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
+ * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system.</p>
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_CARRIER_SIGNAL_RESET =
+ "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
+
+ // CARRIER_SIGNAL_ACTION extra keys
+ /**
+ * An string extra of redirected url upon {@link #ACTION_CARRIER_SIGNAL_REDIRECTED}.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_REDIRECTION_URL = "redirectionUrl";
+
+ /**
+ * An integer extra of error code upon {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED}.
+ * Check {@link DataFailCause} for all possible values.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_ERROR_CODE = "errorCode";
+
+ /**
+ * An string extra of corresponding apn type upon
+ * {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED},
+ * {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * @deprecated This is kept for backward compatibility reason. Use {@link #EXTRA_APN_TYPE_INT}
+ * instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Deprecated
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_APN_TYPE = "apnType";
+
+ /**
+ * An string integer of corresponding apn type upon
+ * {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED},
+ * {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * Check {@link ApnSetting} TYPE_* for its values.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_APN_TYPE_INT = "apnTypeInt";
+
+ /**
+ * An string extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * @deprecated This is kept for backward compatibility reason.
+ * Use {@link #EXTRA_APN_PROTOCOL_INT} instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Deprecated
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_APN_PROTOCOL = "apnProto";
+
+ /**
+ * An integer extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * Check {@link ApnSetting} PROTOCOL_* for its values.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt";
+
+ /**
+ * An integer extra indicating the pco id for the data upon
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_PCO_ID = "pcoId";
+
+ /**
+ * An extra of byte array of pco data read from modem upon
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_PCO_VALUE = "pcoValue";
+
+ /**
+ * An boolean extra indicating default network available upon
+ * {@link #ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE} broadcasts.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable";
/**
* <p>Broadcast Action: The emergency call state is changed.
* <ul>
- * <li><em>phoneInEmergencyCall</em> - A boolean value, true if phone in emergency call,
- * false otherwise</li>
+ * <li><em>EXTRA_PHONE_IN_EMERGENCY_CALL</em> - A boolean value, true if phone in emergency
+ * call, false otherwise</li>
* </ul>
* <p class="note">
* You can <em>not</em> receive this through components declared
@@ -1498,12 +1686,25 @@
*
* <p class="note">This is a protected intent that can only be sent by the system.
*
+ * @see #EXTRA_PHONE_IN_EMERGENCY_CALL
+ *
* @hide
*/
@SystemApi
@SuppressLint("ActionValue")
- public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
- = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
+ public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED =
+ "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
+
+
+ /**
+ * Extra included in {@link #ACTION_EMERGENCY_CALL_STATE_CHANGED}.
+ * It indicates whether the phone is making an emergency call.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_PHONE_IN_EMERGENCY_CALL =
+ "android.telephony.extra.PHONE_IN_EMERGENCY_CALL";
/**
* <p>Broadcast Action: It indicates the Emergency callback mode blocks datacall/sms
@@ -1516,8 +1717,8 @@
* @hide
*/
@SystemApi
- public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS
- = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
+ public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS =
+ "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
/**
* Broadcast Action: The default data subscription has changed in a multi-SIM device.
@@ -1530,8 +1731,8 @@
*/
@SystemApi
@SuppressLint("ActionValue")
- public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED
- = "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED";
+ public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED =
+ "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED";
/**
* Broadcast Action: The default voice subscription has changed in a mult-SIm device.
@@ -1544,8 +1745,8 @@
*/
@SystemApi
@SuppressLint("ActionValue")
- public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED
- = "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED";
+ public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED =
+ "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED";
/**
* Broadcast Action: This triggers a client initiated OMA-DM session to the OMA server.
@@ -1558,8 +1759,8 @@
*/
@SystemApi
@SuppressLint("ActionValue")
- public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE
- = "com.android.omadm.service.CONFIGURATION_UPDATE";
+ public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
+ "com.android.omadm.service.CONFIGURATION_UPDATE";
//
//
@@ -2075,6 +2276,16 @@
public static final int PHONE_TYPE_CDMA = PhoneConstants.PHONE_TYPE_CDMA;
/** Phone is via SIP. */
public static final int PHONE_TYPE_SIP = PhoneConstants.PHONE_TYPE_SIP;
+ /** Phone is via IMS. */
+ public static final int PHONE_TYPE_IMS = PhoneConstants.PHONE_TYPE_IMS;
+
+ /**
+ * Phone is via Third Party.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PHONE_TYPE_THIRD_PARTY = PhoneConstants.PHONE_TYPE_THIRD_PARTY;
/**
* Returns the current phone type.
@@ -5061,7 +5272,9 @@
* not present or not loaded
* @hide
*/
- @UnsupportedAppUsage
+ @Nullable
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String[] getIsimImpu() {
try {
IPhoneSubInfo info = getSubscriberInfo();
@@ -5243,6 +5456,13 @@
public static final int DATA_DISCONNECTING = 4;
/**
+ * To check the SDK version for {@link TelephonyManager#getDataState}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long GET_DATA_STATE_CODE_CHANGE = 147600208L;
+
+ /**
* Returns a constant indicating the current data connection state
* (cellular).
*
@@ -5260,7 +5480,7 @@
int state = telephony.getDataStateForSubId(
getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
if (state == TelephonyManager.DATA_DISCONNECTING
- && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ && !Compatibility.isChangeEnabled(GET_DATA_STATE_CODE_CHANGE)) {
return TelephonyManager.DATA_CONNECTED;
}
@@ -5322,6 +5542,13 @@
//
/**
+ * To check the SDK version for {@link TelephonyManager#listen}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
+ private static final long LISTEN_CODE_CHANGE = 147600208L;
+
+ /**
* Registers a listener object to receive notification of changes
* in specified telephony states.
* <p>
@@ -5360,7 +5587,7 @@
// subId from PhoneStateListener is deprecated Q on forward, use the subId from
// TelephonyManager instance. keep using subId from PhoneStateListener for pre-Q.
int subId = mSubId;
- if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q) {
+ if (Compatibility.isChangeEnabled(LISTEN_CODE_CHANGE)) {
// since mSubId in PhoneStateListener is deprecated from Q on forward, this is
// the only place to set mSubId and its for "informational" only.
// TODO: remove this once we completely get rid of mSubId in PhoneStateListener
@@ -7432,6 +7659,18 @@
RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA;
/**
+ * The default preferred network mode constant.
+ *
+ * <p> This constant is used in case of nothing is set in
+ * TelephonyProperties#default_network().
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int DEFAULT_PREFERRED_NETWORK_MODE =
+ RILConstants.DEFAULT_PREFERRED_NETWORK_MODE;
+
+ /**
* Get the preferred network type.
* Used for device configuration by some CDMA operators.
*
@@ -8767,8 +9006,9 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public int getCdmaRoamingMode() {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @CdmaRoamingMode int getCdmaRoamingMode() {
int mode = CDMA_ROAMING_MODE_RADIO_DEFAULT;
try {
ITelephony telephony = getITelephony();
@@ -8795,8 +9035,9 @@
*
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public boolean setCdmaRoamingMode(int mode) {
+ public boolean setCdmaRoamingMode(@CdmaRoamingMode int mode) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -8808,6 +9049,36 @@
return false;
}
+ /** @hide */
+ @IntDef(flag = true, prefix = { "CDMA_SUBSCRIPTION_" }, value = {
+ CDMA_SUBSCRIPTION_UNKNOWN,
+ CDMA_SUBSCRIPTION_RUIM_SIM,
+ CDMA_SUBSCRIPTION_NV
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CdmaSubscription{}
+
+ /** Used for CDMA subscription mode, it'll be UNKNOWN if there is no Subscription source.
+ * @hide
+ */
+ @SystemApi
+ public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1;
+
+ /** Used for CDMA subscription mode: RUIM/SIM (default)
+ * @hide
+ */
+ @SystemApi
+ public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0;
+
+ /** Used for CDMA subscription mode: NV -> non-volatile memory
+ * @hide
+ */
+ @SystemApi
+ public static final int CDMA_SUBSCRIPTION_NV = 1;
+
+ /** @hide */
+ public static final int PREFERRED_CDMA_SUBSCRIPTION = CDMA_SUBSCRIPTION_RUIM_SIM;
+
/**
* Sets the subscription mode for CDMA phone to the given mode {@code mode}.
*
@@ -8815,14 +9086,15 @@
*
* @return {@code true} if successed.
*
- * @see Phone#CDMA_SUBSCRIPTION_UNKNOWN
- * @see Phone#CDMA_SUBSCRIPTION_RUIM_SIM
- * @see Phone#CDMA_SUBSCRIPTION_NV
+ * @see #CDMA_SUBSCRIPTION_UNKNOWN
+ * @see #CDMA_SUBSCRIPTION_RUIM_SIM
+ * @see #CDMA_SUBSCRIPTION_NV
*
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public boolean setCdmaSubscriptionMode(int mode) {
+ public boolean setCdmaSubscriptionMode(@CdmaSubscription int mode) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -10435,6 +10707,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param enabled control enable or disable carrier data.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10461,6 +10734,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param enabled control enable or disable radio.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10487,6 +10761,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param report control start/stop reporting network status.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10669,12 +10944,21 @@
}
/**
+ * Checks whether cellular data connection is enabled in the device.
+ *
+ * Whether cellular data connection is enabled, meaning upon request whether will try to setup
+ * metered data connection considering all factors below:
+ * 1) User turned on data setting {@link #isDataEnabled}.
+ * 2) Carrier allows data to be on.
+ * 3) Network policy.
+ * And possibly others.
+ *
+ * @return {@code true} if the overall data connection is capable; {@code false} if not.
* @hide
- * It's similar to isDataEnabled, but unlike isDataEnabled, this API also evaluates
- * carrierDataEnabled, policyDataEnabled etc to give a final decision of whether mobile data is
- * capable of using.
*/
- public boolean isDataCapable() {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isDataConnectionEnabled() {
boolean retVal = false;
try {
int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId());
@@ -10682,13 +10966,24 @@
if (telephony != null)
retVal = telephony.isDataEnabled(subId);
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#isDataEnabled", e);
+ Log.e(TAG, "Error isDataConnectionEnabled", e);
} catch (NullPointerException e) {
}
return retVal;
}
/**
+ * Checks if FEATURE_TELEPHONY_DATA is enabled.
+ *
+ * @hide
+ */
+ public boolean isDataCapable() {
+ if (mContext == null) return true;
+ return mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_DATA);
+ }
+
+ /**
* In this mode, modem will not send specified indications when screen is off.
* @hide
*/
diff --git a/telephony/java/android/telephony/VoLteServiceState.java b/telephony/java/android/telephony/VoLteServiceState.java
index 1214012..d4a27d9 100644
--- a/telephony/java/android/telephony/VoLteServiceState.java
+++ b/telephony/java/android/telephony/VoLteServiceState.java
@@ -53,8 +53,7 @@
/**
* Create a new VoLteServiceState from a intent notifier Bundle
*
- * This method is used by PhoneStateIntentReceiver and maybe by
- * external applications.
+ * This method is maybe used by external applications.
*
* @param m Bundle from intent notifier
* @return newly created VoLteServiceState
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index c962714..d483291 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -27,6 +27,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.AccessNetworkConstants;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsRcsController;
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index f052180..9b739d3 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -25,8 +25,6 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.WorkerThread;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.RemoteException;
import android.telephony.CarrierConfigManager;
@@ -34,6 +32,7 @@
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.aidl.IImsConfigCallback;
import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.feature.RcsFeature;
import android.telephony.ims.stub.ImsConfigImplBase;
import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -84,6 +83,11 @@
"STRING_QUERY_RESULT_ERROR_NOT_READY";
/**
+ * There is no existing configuration for the queried provisioning key.
+ */
+ public static final int PROVISIONING_RESULT_UNKNOWN = -1;
+
+ /**
* The integer result of provisioning for the queried key is disabled.
*/
public static final int PROVISIONING_VALUE_DISABLED = 0;
@@ -94,6 +98,151 @@
public static final int PROVISIONING_VALUE_ENABLED = 1;
+ // Inheriting values from ImsConfig for backwards compatibility.
+ /**
+ * An integer key representing the SIP T1 timer value in milliseconds for the associated
+ * subscription.
+ * <p>
+ * The SIP T1 timer is an estimate of the round-trip time and will retransmit
+ * INVITE transactions that are longer than T1 milliseconds over unreliable transports, doubling
+ * the time before retransmission every time there is no response. See RFC3261, section 17.1.1.1
+ * for more details.
+ * <p>
+ * The value is an integer.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_T1_TIMER_VALUE_MS = 7;
+
+ /**
+ * An integer key representing the voice over LTE (VoLTE) provisioning status for the
+ * associated subscription. Determines whether the user can register for voice services over
+ * LTE.
+ * <p>
+ * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VoLTE provisioning and
+ * {@link #PROVISIONING_VALUE_DISABLED} to disable VoLTE provisioning.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_VOLTE_PROVISIONING_STATUS = 10;
+
+ /**
+ * An integer key representing the video telephony (VT) provisioning status for the
+ * associated subscription. Determines whether the user can register for video services over
+ * LTE.
+ * <p>
+ * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VT provisioning and
+ * {@link #PROVISIONING_VALUE_DISABLED} to disable VT provisioning.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_VT_PROVISIONING_STATUS = 11;
+
+ /**
+ * An integer key associated with the carrier configured SIP PUBLISH timer, which dictates the
+ * expiration time in seconds for published online availability in RCS presence.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15;
+
+ /**
+ * An integer key associated with the carrier configured expiration time in seconds for
+ * RCS presence published offline availability in RCS presence.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16;
+
+ /**
+ * An integer key associated with whether or not capability discovery is provisioned for this
+ * subscription. Any capability requests will be ignored by the RCS service.
+ * <p>
+ * The value is an integer, either {@link #PROVISIONING_VALUE_DISABLED} if capability
+ * discovery is disabled or {@link #PROVISIONING_VALUE_ENABLED} if capability discovery is
+ * enabled.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17;
+
+ /**
+ * An integer key associated with the period of time the capability information of each contact
+ * is cached on the device.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18;
+
+ /**
+ * An integer key associated with the period of time in seconds that the availability
+ * information of a contact is cached on the device.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19;
+
+ /**
+ * An integer key associated with the carrier configured interval in seconds expected between
+ * successive capability polling attempts.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20;
+
+ /**
+ * An integer key representing the minimum time allowed between two consecutive presence publish
+ * messages from the device.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21;
+
+ /**
+ * An integer key associated with the maximum number of MDNs contained in one SIP Request
+ * Contained List (RCS) used to retrieve the RCS capabilities of the contacts book.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22;
+
+ /**
+ * An integer associated with the expiration timer used duriing the SIP subscription of a
+ * Request Contained List (RCL), which is used to retrieve the RCS capabilities of the contact
+ * book.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23;
+
+ /**
+ * An integer key representing the RCS enhanced address book (EAB) provisioning status for the
+ * associated subscription. Determines whether or not SIP OPTIONS or presence will be used to
+ * retrieve RCS capabilities for the user's contacts.
+ * <p>
+ * Use {@link #PROVISIONING_VALUE_ENABLED} to enable EAB provisioning and
+ * {@link #PROVISIONING_VALUE_DISABLED} to disable EAB provisioning.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_EAB_PROVISIONING_STATUS = 25;
+
/**
* Override the user-defined WiFi Roaming enabled setting for this subscription, defined in
* {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning
@@ -231,10 +380,6 @@
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerProvisioningChangedCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull Callback callback) throws ImsException {
- if (!isImsAvailableOnDevice()) {
- throw new ImsException("IMS not available on device.",
- ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
- }
callback.setExecutor(executor);
try {
getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder());
@@ -269,7 +414,7 @@
*
* @param key An integer that represents the provisioning key, which is defined by the OEM.
* @return an integer value for the provided key, or
- * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
+ * {@link #PROVISIONING_RESULT_UNKNOWN} if the key doesn't exist.
* @throws IllegalArgumentException if the key provided was invalid.
*/
@WorkerThread
@@ -396,41 +541,75 @@
}
/**
+ * Get the provisioning status for the IMS RCS capability specified.
+ *
+ * If provisioning is not required for the queried
+ * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method will always return
+ * {@code true}.
+ *
+ * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
+ * @return true if the device is provisioned for the capability or does not require
+ * provisioning, false if the capability does require provisioning and has not been
+ * provisioned yet.
+ */
+ @WorkerThread
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean getRcsProvisioningStatusForCapability(
+ @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) {
+ try {
+ return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Set the provisioning status for the IMS RCS capability using the specified subscription.
+ *
+ * Provisioning may or may not be required, depending on the carrier configuration. If
+ * provisioning is not required for the carrier associated with this subscription or the device
+ * does not support the capability/technology combination specified, this operation will be a
+ * no-op.
+ *
+ * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
+ * @param isProvisioned true if the device is provisioned for the RCS capability specified,
+ * false otherwise.
+ */
+ @WorkerThread
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void setRcsProvisioningStatusForCapability(
+ @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+ boolean isProvisioned) {
+ try {
+ getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability,
+ isProvisioned);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Notify the framework that an RCS autoconfiguration XML file has been received for
* provisioning.
+ * <p>
+ * Requires Permission: Manifest.permission.MODIFY_PHONE_STATE or that the calling app has
+ * carrier privileges (see {@link #hasCarrierPrivileges}).
* @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed.
* @param isCompressed The XML file is compressed in gzip format and must be decompressed
* before being read.
- * @hide
+ *
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
if (config == null) {
throw new IllegalArgumentException("Must include a non-null config XML file.");
}
- // TODO: Connect to ImsConfigImplBase.
- throw new UnsupportedOperationException("notifyRcsAutoConfigurationReceived is not"
- + "supported");
- }
-
- private static boolean isImsAvailableOnDevice() {
- IPackageManager pm = IPackageManager.Stub.asInterface(
- TelephonyFrameworkInitializer
- .getTelephonyServiceManager()
- .getPackageManagerServiceRegisterer()
- .get());
- if (pm == null) {
- // For some reason package manger is not available.. This will fail internally anyways,
- // so do not throw error and allow.
- return true;
- }
try {
- return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS, 0);
+ getITelephony().notifyRcsAutoConfigurationReceived(mSubId, config, isCompressed);
} catch (RemoteException e) {
- // For some reason package manger is not available.. This will fail internally anyways,
- // so do not throw error and allow.
+ throw e.rethrowAsRuntimeException();
}
- return true;
+
}
private static ITelephony getITelephony() {
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 492170b..893a311 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,6 +36,7 @@
* Contains the User Capability Exchange capabilities corresponding to a contact's URI.
* @hide
*/
+@SystemApi
public final class RcsContactUceCapability implements Parcelable {
/** Supports 1-to-1 chat */
@@ -135,7 +137,7 @@
* @param type The capability to map to a service URI that is different from the contact's
* URI.
*/
- public Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) {
+ public @NonNull Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) {
mCapabilities.mCapabilities |= type;
// Put each of these capabilities into the map separately.
for (int shift = 0; shift < Integer.SIZE; shift++) {
@@ -157,7 +159,7 @@
* Add a UCE capability flag that this contact supports.
* @param type the capability that the contact supports.
*/
- public Builder add(@CapabilityFlag int type) {
+ public @NonNull Builder add(@CapabilityFlag int type) {
mCapabilities.mCapabilities |= type;
return this;
}
@@ -167,7 +169,7 @@
* @param extension A string containing a carrier specific service tag that is an extension
* of the {@link CapabilityFlag}s that are defined here.
*/
- public Builder add(@NonNull String extension) {
+ public @NonNull Builder add(@NonNull String extension) {
mCapabilities.mExtensionTags.add(extension);
return this;
}
@@ -175,7 +177,7 @@
/**
* @return the constructed instance.
*/
- public RcsContactUceCapability build() {
+ public @NonNull RcsContactUceCapability build() {
return mCapabilities;
}
}
@@ -205,7 +207,7 @@
}
}
- public static final Creator<RcsContactUceCapability> CREATOR =
+ public static final @NonNull Creator<RcsContactUceCapability> CREATOR =
new Creator<RcsContactUceCapability>() {
@Override
public RcsContactUceCapability createFromParcel(Parcel in) {
@@ -219,7 +221,7 @@
};
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeParcelable(mContactUri, 0);
out.writeInt(mCapabilities);
out.writeStringList(mExtensionTags);
diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
index 53e4596..57206c9 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
@@ -40,4 +40,5 @@
// Return result code defined in ImsConfig#OperationStatusConstants
int setConfigString(int item, String value);
void updateImsCarrierConfigs(in PersistableBundle bundle);
+ void notifyRcsAutoConfigurationReceived(in byte[] config, boolean isCompressed);
}
diff --git a/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl b/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl
index 881b477..70cf651 100644
--- a/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl
@@ -32,11 +32,11 @@
oneway void onNetworkResponse(int code, in String reason, int operationToken);
oneway void onCapabilityRequestResponsePresence(in List<RcsContactUceCapability> infos,
int operationToken);
- oneway void onNotifyUpdateCapabilities();
+ oneway void onNotifyUpdateCapabilities(int publishTriggerType);
oneway void onUnpublish();
// RcsSipOptionsImplBase specific
oneway void onCapabilityRequestResponseOptions(int code, in String reason,
in RcsContactUceCapability info, int operationToken);
oneway void onRemoteCapabilityRequest(in Uri contactUri, in RcsContactUceCapability remoteInfo,
int operationToken);
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index d18e93c..6a2638b 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -17,11 +17,13 @@
package android.telephony.ims.stub;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsConfigCallback;
import android.util.Log;
@@ -199,6 +201,12 @@
}
}
+ @Override
+ public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed)
+ throws RemoteException {
+ getImsConfigImpl().notifyRcsAutoConfigurationReceived(config, isCompressed);
+ }
+
private void notifyImsConfigChanged(int item, int value) throws RemoteException {
getImsConfigImpl().notifyConfigChanged(item, value);
}
@@ -228,7 +236,8 @@
* The configuration requested resulted in an unknown result. This may happen if the
* IMS configurations are unavailable.
*/
- public static final int CONFIG_RESULT_UNKNOWN = -1;
+ public static final int CONFIG_RESULT_UNKNOWN = ProvisioningManager.PROVISIONING_RESULT_UNKNOWN;
+
/**
* Setting the configuration value completed.
*/
@@ -356,9 +365,9 @@
* @param config The XML file to be read, if not compressed, it should be in ASCII/UTF8 format.
* @param isCompressed The XML file is compressed in gzip format and must be decompressed
* before being read.
- * @hide
+ *
*/
- public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) {
+ public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
}
/**
diff --git a/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java
index 055fca5..bb03448 100644
--- a/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java
@@ -113,6 +113,51 @@
})
public @interface PresenceResponseCode {}
+
+ /** A capability update has been requested due to the Entity Tag (ETag) expiring. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 0;
+ /** A capability update has been requested due to moving to LTE with VoPS disabled. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED = 1;
+ /** A capability update has been requested due to moving to LTE with VoPS enabled. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED = 2;
+ /** A capability update has been requested due to moving to eHRPD. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 3;
+ /** A capability update has been requested due to moving to HSPA+. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS = 4;
+ /** A capability update has been requested due to moving to 3G. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G = 5;
+ /** A capability update has been requested due to moving to 2G. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G = 6;
+ /** A capability update has been requested due to moving to WLAN */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN = 7;
+ /** A capability update has been requested due to moving to IWLAN */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN = 8;
+ /** A capability update has been requested but the reason is unknown. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_UNKNOWN = 9;
+ /** A capability update has been requested due to moving to 5G NR with VoPS disabled. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10;
+ /** A capability update has been requested due to moving to 5G NR with VoPS enabled. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11;
+
+ /** @hide*/
+ @IntDef(value = {
+ CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN,
+ CAPABILITY_UPDATE_TRIGGER_UNKNOWN,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED
+ }, prefix = "CAPABILITY_UPDATE_TRIGGER_")
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StackPublishTriggerType {
+ }
+
/**
* Provide the framework with a subsequent network response update to
* {@link #updateCapabilities(RcsContactUceCapability, int)} and
@@ -164,15 +209,18 @@
* This is typically used when trying to generate an initial PUBLISH for a new subscription to
* the network. The device will cache all presence publications after boot until this method is
* called once.
+ * @param publishTriggerType {@link StackPublishTriggerType} The reason for the capability
+ * update request.
* @throws ImsException If this {@link RcsPresenceExchangeImplBase} instance is not currently
* connected to the framework. This can happen if the {@link RcsFeature} is not
* {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
* {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the
* Telephony stack has crashed.
*/
- public final void onNotifyUpdateCapabilites() throws ImsException {
+ public final void onNotifyUpdateCapabilites(@StackPublishTriggerType int publishTriggerType)
+ throws ImsException {
try {
- getListener().onNotifyUpdateCapabilities();
+ getListener().onNotifyUpdateCapabilities(publishTriggerType);
} catch (RemoteException e) {
throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index 9116a3b..0d86e2b 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -178,7 +178,7 @@
* SIP T1 timer value in milliseconds. See RFC 3261 for define.
* Value is in Integer format.
*/
- public static final int SIP_T1_TIMER = 7;
+ public static final int SIP_T1_TIMER = ProvisioningManager.KEY_T1_TIMER_VALUE_MS;
/**
* SIP T2 timer value in milliseconds. See RFC 3261 for define.
@@ -196,13 +196,15 @@
* VoLTE status for VLT/s status of Enabled (1), or Disabled (0).
* Value is in Integer format.
*/
- public static final int VLT_SETTING_ENABLED = 10;
+ public static final int VLT_SETTING_ENABLED =
+ ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS;
/**
* VoLTE status for LVC/s status of Enabled (1), or Disabled (0).
* Value is in Integer format.
*/
- public static final int LVC_SETTING_ENABLED = 11;
+ public static final int LVC_SETTING_ENABLED =
+ ProvisioningManager.KEY_VT_PROVISIONING_STATUS;
/**
* Domain Name for the device to populate the request URI for REGISTRATION.
* Value is in String format.
@@ -222,48 +224,56 @@
* Requested expiration for Published Online availability.
* Value is in Integer format.
*/
- public static final int PUBLISH_TIMER = 15;
+ public static final int PUBLISH_TIMER = ProvisioningManager.KEY_RCS_PUBLISH_TIMER_SEC;
/**
* Requested expiration for Published Offline availability.
* Value is in Integer format.
*/
- public static final int PUBLISH_TIMER_EXTENDED = 16;
+ public static final int PUBLISH_TIMER_EXTENDED =
+ ProvisioningManager.KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC;
/**
*
* Value is in Integer format.
*/
- public static final int CAPABILITY_DISCOVERY_ENABLED = 17;
+ public static final int CAPABILITY_DISCOVERY_ENABLED =
+ ProvisioningManager.KEY_RCS_CAPABILITY_DISCOVERY_ENABLED;
/**
* Period of time the capability information of the contact is cached on handset.
* Value is in Integer format.
*/
- public static final int CAPABILITIES_CACHE_EXPIRATION = 18;
+ public static final int CAPABILITIES_CACHE_EXPIRATION =
+ ProvisioningManager.KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC;
/**
* Peiod of time the availability information of a contact is cached on device.
* Value is in Integer format.
*/
- public static final int AVAILABILITY_CACHE_EXPIRATION = 19;
+ public static final int AVAILABILITY_CACHE_EXPIRATION =
+ ProvisioningManager.KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC;
/**
* Interval between successive capabilities polling.
* Value is in Integer format.
*/
- public static final int CAPABILITIES_POLL_INTERVAL = 20;
+ public static final int CAPABILITIES_POLL_INTERVAL =
+ ProvisioningManager.KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC;
/**
* Minimum time between two published messages from the device.
* Value is in Integer format.
*/
- public static final int SOURCE_THROTTLE_PUBLISH = 21;
+ public static final int SOURCE_THROTTLE_PUBLISH =
+ ProvisioningManager.KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS;
/**
* The Maximum number of MDNs contained in one Request Contained List.
* Value is in Integer format.
*/
- public static final int MAX_NUMENTRIES_IN_RCL = 22;
+ public static final int MAX_NUMENTRIES_IN_RCL =
+ ProvisioningManager.KEY_RCS_MAX_NUM_ENTRIES_IN_RCL;
/**
* Expiration timer for subscription of a Request Contained List, used in capability
* polling.
* Value is in Integer format.
*/
- public static final int CAPAB_POLL_LIST_SUB_EXP = 23;
+ public static final int CAPAB_POLL_LIST_SUB_EXP =
+ ProvisioningManager.KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC;
/**
* Applies compression to LIST Subscription.
* Value is in Integer format. Enable (1), Disable(0).
@@ -273,7 +283,8 @@
* VOLTE Status for EAB/s status of Enabled (1), or Disabled (0).
* Value is in Integer format.
*/
- public static final int EAB_SETTING_ENABLED = 25;
+ public static final int EAB_SETTING_ENABLED =
+ ProvisioningManager.KEY_EAB_PROVISIONING_STATUS;
/**
* Wi-Fi calling roaming status.
* Value is in Integer format. ON (1), OFF(0).
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index b846a10..cdb95a8 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1977,6 +1977,17 @@
*/
boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech);
+ /**
+ * Get the provisioning status for the IMS Rcs capability specified.
+ */
+ boolean getRcsProvisioningStatusForCapability(int subId, int capability);
+
+ /**
+ * Set the provisioning status for the IMS Rcs capability using the specified subscription.
+ */
+ void setRcsProvisioningStatusForCapability(int subId, int capability,
+ boolean isProvisioned);
+
/** Is the capability and tech flagged as provisioned in the cache */
boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech);
@@ -2126,4 +2137,9 @@
* Command line command to enable or disable handling of CEP data for test purposes.
*/
oneway void setCepEnabled(boolean isCepEnabled);
+
+ /**
+ * Notify Rcs auto config received.
+ */
+ void notifyRcsAutoConfigurationReceived(int subId, in byte[] config, boolean isCompressed);
}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index 51701eb..db8c845 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -100,9 +100,6 @@
public static final String DATA_APN_TYPE_KEY = "apnType";
public static final String DATA_APN_KEY = "apn";
- public static final String PHONE_IN_ECM_STATE = "phoneinECMState";
- public static final String PHONE_IN_EMERGENCY_CALL = "phoneInEmergencyCall";
-
/**
* Return codes for supplyPinReturnResult and
* supplyPukReturnResult APIs
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 284544b..9ee26c2 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -233,11 +233,14 @@
/** NR 5G, LTE, TD-SCDMA, CDMA, EVDO, GSM and WCDMA */
int NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 33;
+ /** Default preferred network mode */
+ int DEFAULT_PREFERRED_NETWORK_MODE = NETWORK_MODE_WCDMA_PREF;
+
@UnsupportedAppUsage
int PREFERRED_NETWORK_MODE = Optional.of(TelephonyProperties.default_network())
.filter(list -> !list.isEmpty())
.map(list -> list.get(0))
- .orElse(NETWORK_MODE_WCDMA_PREF);
+ .orElse(DEFAULT_PREFERRED_NETWORK_MODE);
int BAND_MODE_UNSPECIFIED = 0; //"unspecified" (selected by baseband automatically)
int BAND_MODE_EURO = 1; //"EURO band" (GSM-900 / DCS-1800 / WCDMA-IMT-2000)
@@ -555,4 +558,5 @@
int RIL_UNSOL_EMERGENCY_NUMBER_LIST = 1102;
int RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED = 1103;
int RIL_UNSOL_REGISTRATION_FAILED = 1104;
+ int RIL_UNSOL_BARRING_INFO_CHANGED = 1105;
}
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index f78c65f..a15f73c 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -19,6 +19,7 @@
import android.content.Intent;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsManager;
/**
* The intents that the telephony services broadcast.
@@ -123,32 +124,6 @@
public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
= TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED;
- /**
- * Broadcast Action: The phone's signal strength has changed. The intent will have the
- * following extra values:</p>
- * <ul>
- * <li><em>phoneName</em> - A string version of the phone name.</li>
- * <li><em>asu</em> - A numeric value for the signal strength.
- * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
- * The following special values are defined:
- * <ul><li>0 means "-113 dBm or less".</li><li>31 means "-51 dBm or greater".</li></ul>
- * </li>
- * </ul>
- *
- * <p class="note">
- * You can <em>not</em> receive this through components declared
- * in manifests, only by exlicitly registering for it with
- * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
- * android.content.IntentFilter) Context.registerReceiver()}.
- *
- * <p class="note">
- * Requires the READ_PHONE_STATE permission.
- *
- * <p class="note">This is a protected intent that can only be sent
- * by the system.
- */
- public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
-
/**
* Broadcast Action: The data connection state has changed for any one of the
@@ -224,9 +199,11 @@
* <p class="note">
* This is for the OEM applications to understand about possible provisioning issues.
* Used in OMA-DM applications.
+ * @deprecated Use {@link ImsManager#ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION} instead.
*/
- public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION
- = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+ @Deprecated
+ public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION =
+ ImsManager.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION;
/**
* Broadcast Action: A "secret code" has been entered in the dialer. Secret codes are
@@ -351,85 +328,6 @@
"android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED";
/**
- * <p>Broadcast Action: when data connections get redirected with validation failure.
- * intended for sim/account status checks and only sent to the specified carrier app
- * The intent will have the following extra values:</p>
- * <ul>
- * <li>apnType</li><dd>A string with the apn type.</dd>
- * <li>redirectionUrl</li><dd>redirection url string</dd>
- * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
- * </ul>
- * <p class="note">This is a protected intent that can only be sent by the system.</p>
- */
- public static final String ACTION_CARRIER_SIGNAL_REDIRECTED =
- "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
- /**
- * <p>Broadcast Action: when data connections setup fails.
- * intended for sim/account status checks and only sent to the specified carrier app
- * The intent will have the following extra values:</p>
- * <ul>
- * <li>apnType</li><dd>A string with the apn type.</dd>
- * <li>errorCode</li><dd>A integer with dataFailCause.</dd>
- * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
- * </ul>
- * <p class="note">This is a protected intent that can only be sent by the system. </p>
- */
- public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED =
- "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
-
- /**
- * <p>Broadcast Action: when pco value is available.
- * intended for sim/account status checks and only sent to the specified carrier app
- * The intent will have the following extra values:</p>
- * <ul>
- * <li>apnType</li><dd>A string with the apn type.</dd>
- * <li>apnProto</li><dd>A string with the protocol of the apn connection (IP,IPV6,
- * IPV4V6)</dd>
- * <li>pcoId</li><dd>An integer indicating the pco id for the data.</dd>
- * <li>pcoValue</li><dd>A byte array of pco data read from modem.</dd>
- * <li>subId</li><dd>Sub Id which associated the data connection.</dd>
- * </ul>
- * <p class="note">This is a protected intent that can only be sent by the system. </p>
- */
- public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE =
- "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
-
- /**
- * <p>Broadcast Action: when system default network available/unavailable with
- * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when
- * other network becomes system default network, Wi-Fi for example.
- * The intent will have the following extra values:</p>
- * <ul>
- * <li>defaultNetworkAvailable</li><dd>A boolean indicates default network available.</dd>
- * <li>subId</li><dd>Sub Id which associated the default data.</dd>
- * </ul>
- * <p class="note">This is a protected intent that can only be sent by the system. </p>
- */
- public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE =
- "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
-
- /**
- * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
- * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
- * The intent will have the following extra values:</p>
- * <ul>
- * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
- * </ul>
- * <p class="note">This is a protected intent that can only be sent by the system.</p>
- */
- public static final String ACTION_CARRIER_SIGNAL_RESET =
- "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
-
- // CARRIER_SIGNAL_ACTION extra keys
- public static final String EXTRA_REDIRECTION_URL_KEY = "redirectionUrl";
- public static final String EXTRA_ERROR_CODE_KEY = "errorCode";
- public static final String EXTRA_APN_TYPE_KEY = "apnType";
- public static final String EXTRA_APN_PROTO_KEY = "apnProto";
- public static final String EXTRA_PCO_ID_KEY = "pcoId";
- public static final String EXTRA_PCO_VALUE_KEY = "pcoValue";
- public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY = "defaultNetworkAvailable";
-
- /**
* Broadcast action to trigger CI OMA-DM Session.
*/
public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 2bc129a..091edd4 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -19,15 +19,17 @@
static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
test_suites: ["general-tests"],
test_config: "RollbackTest.xml",
+ java_resources: [":com.android.apex.apkrollback.test_v2"],
}
java_test_host {
name: "StagedRollbackTest",
srcs: ["StagedRollbackTest/src/**/*.java"],
libs: ["tradefed"],
- static_libs: ["testng"],
+ static_libs: ["testng", "compatibility-tradefed"],
test_suites: ["general-tests"],
test_config: "StagedRollbackTest.xml",
+ data: [":com.android.apex.apkrollback.test_v1"],
}
java_test_host {
@@ -37,3 +39,44 @@
test_suites: ["general-tests"],
test_config: "MultiUserRollbackTest.xml",
}
+
+genrule {
+ name: "com.android.apex.apkrollback.test.pem",
+ out: ["com.android.apex.apkrollback.test.pem"],
+ cmd: "openssl genrsa -out $(out) 4096",
+}
+
+genrule {
+ name: "com.android.apex.apkrollback.test.pubkey",
+ srcs: [":com.android.apex.apkrollback.test.pem"],
+ out: ["com.android.apex.apkrollback.test.pubkey"],
+ tools: ["avbtool"],
+ cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)",
+}
+
+apex_key {
+ name: "com.android.apex.apkrollback.test.key",
+ private_key: ":com.android.apex.apkrollback.test.pem",
+ public_key: ":com.android.apex.apkrollback.test.pubkey",
+ installable: false,
+}
+
+apex {
+ name: "com.android.apex.apkrollback.test_v1",
+ manifest: "testdata/manifest_v1.json",
+ androidManifest: "testdata/AndroidManifest.xml",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.apkrollback.test.key",
+ apps: ["TestAppAv1"],
+ installable: false,
+}
+
+apex {
+ name: "com.android.apex.apkrollback.test_v2",
+ manifest: "testdata/manifest_v2.json",
+ androidManifest: "testdata/AndroidManifest.xml",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.apkrollback.test.key",
+ apps: ["TestAppAv2"],
+ installable: false,
+}
\ No newline at end of file
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 9e490f7..ce20311 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -23,12 +23,14 @@
import android.Manifest;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
import android.provider.DeviceConfig;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -185,12 +187,6 @@
*/
@Test
public void testNativeWatchdogTriggersRollback_Phase1() throws Exception {
- // When multiple staged sessions are installed on a device which doesn't support checkpoint,
- // only the 1st one will prevail. We have to check no other rollbacks available to ensure
- // TestApp.A is always the 1st and the only one to commit so rollback can work as intended.
- // If there are leftover rollbacks from previous tests, this assertion will fail.
- assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
-
Uninstall.packages(TestApp.A);
Install.single(TestApp.A1).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
@@ -220,6 +216,64 @@
TestApp.A)).isNotNull();
}
+ /**
+ * Stage install an apk with rollback that will be later triggered by unattributable crash.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase1() throws Exception {
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+ Install.single(TestApp.A2).setEnableRollback().setStaged().commit();
+ }
+
+ /**
+ * Verify the rollback is available and then install another package with rollback.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase2() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ TestApp.A)).isNotNull();
+
+ // Install another package with rollback
+ Uninstall.packages(TestApp.B);
+ Install.single(TestApp.B1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+
+ Install.single(TestApp.B2).setEnableRollback().setStaged().commit();
+ }
+
+ /**
+ * Verify the rollbacks are available.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase3() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ TestApp.A)).isNotNull();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ TestApp.B)).isNotNull();
+ }
+
+ /**
+ * Verify the rollbacks are committed after crashing.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase4() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ TestApp.A)).isNotNull();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ TestApp.B)).isNotNull();
+ }
+
@Test
public void testNetworkFailedRollback_Phase1() throws Exception {
// Remove available rollbacks and uninstall NetworkStack on /data/
@@ -435,9 +489,63 @@
// testNativeWatchdogTriggersRollback will fail if multiple staged sessions are
// committed on a device which doesn't support checkpoint. Let's clean up all rollbacks
// so there is only one rollback to commit when testing native crashes.
- RollbackManager rm = RollbackUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
.map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+ assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
+ }
+
+ private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
+ private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1",
+ APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
+ private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2",
+ APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex");
+ private static final TestApp TEST_APP_A_V2_UNKNOWN = new TestApp("Av2Unknown", TestApp.A, 0,
+ /*isApex*/false, "TestAppAv2.apk");
+
+ @Test
+ public void testRollbackApexWithApk_Phase1() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+
+ int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback()
+ .commit();
+ InstallUtils.waitForSessionReady(sessionId);
+ }
+
+ @Test
+ public void testRollbackApexWithApk_Phase2() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ InstallUtils.processUserData(TestApp.A);
+
+ RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME);
+ assertThat(available).isStaged();
+ assertThat(available).packagesContainsExactly(
+ Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
+ Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1));
+
+ RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2);
+ RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
+ assertThat(committed).isNotNull();
+ assertThat(committed).isStaged();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
+ Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1));
+ assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2);
+ assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+ // Note: The app is not rolled back until after the rollback is staged
+ // and the device has been rebooted.
+ InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+ assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
+ }
+
+ @Test
+ public void testRollbackApexWithApk_Phase3() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
}
private static void runShellCommand(String cmd) {
@@ -445,4 +553,11 @@
.executeShellCommand(cmd);
IoUtils.closeQuietly(pfd);
}
+
+ @Test
+ public void isCheckpointSupported() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+ assertThat(sm.isCheckpointSupported()).isTrue();
+ }
}
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 91577c2..181e29a 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -17,8 +17,10 @@
package com.android.tests.rollback.host;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -27,6 +29,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.util.concurrent.TimeUnit;
/**
@@ -48,14 +51,33 @@
phase));
}
+ private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
+
@Before
public void setUp() throws Exception {
+ if (!getDevice().isAdbRoot()) {
+ getDevice().enableAdbRoot();
+ }
+ getDevice().remountSystemWritable();
+ getDevice().executeShellCommand(
+ "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
+ + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
getDevice().reboot();
+ runPhase("testCleanUp");
}
@After
public void tearDown() throws Exception {
runPhase("testCleanUp");
+
+ if (!getDevice().isAdbRoot()) {
+ getDevice().enableAdbRoot();
+ }
+ getDevice().remountSystemWritable();
+ getDevice().executeShellCommand(
+ "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
+ + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
+ getDevice().reboot();
}
/**
@@ -75,7 +97,6 @@
@Test
public void testNativeWatchdogTriggersRollback() throws Exception {
- //Stage install ModuleMetadata package - this simulates a Mainline module update
runPhase("testNativeWatchdogTriggersRollback_Phase1");
// Reboot device to activate staged package
@@ -101,6 +122,40 @@
runPhase("testNativeWatchdogTriggersRollback_Phase3");
}
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll() throws Exception {
+ // This test requires committing multiple staged rollbacks
+ assumeTrue(isCheckpointSupported());
+
+ // Install a package with rollback enabled.
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1");
+ getDevice().reboot();
+
+ // Once previous staged install is applied, install another package
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2");
+ getDevice().reboot();
+
+ // Verify the new staged install has also been applied successfully.
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3");
+
+ // crash system_server enough times to trigger a rollback
+ crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
+
+ // Rollback should be committed automatically now.
+ // Give time for rollback to be committed. This could take a while,
+ // because we need all of the following to happen:
+ // 1. system_server comes back up and boot completes.
+ // 2. Rollback health observer detects updatable crashing signal.
+ // 3. Staged rollback session becomes ready.
+ // 4. Device actually reboots.
+ // So we give a generous timeout here.
+ assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+ getDevice().waitForDeviceAvailable();
+
+ // verify all available rollbacks have been committed
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4");
+ }
+
/**
* Tests failed network health check triggers watchdog staged rollbacks.
*/
@@ -184,6 +239,28 @@
runPhase("testRollbackDataPolicy_Phase3");
}
+ /**
+ * Tests that userdata of apk-in-apex is restored when apex is rolled back.
+ */
+ @Test
+ public void testRollbackApexWithApk() throws Exception {
+ getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A");
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+ final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
+ final File apex = buildHelper.getTestFile(fileName);
+ if (!getDevice().isAdbRoot()) {
+ getDevice().enableAdbRoot();
+ }
+ getDevice().remountSystemWritable();
+ assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+ getDevice().reboot();
+ runPhase("testRollbackApexWithApk_Phase1");
+ getDevice().reboot();
+ runPhase("testRollbackApexWithApk_Phase2");
+ getDevice().reboot();
+ runPhase("testRollbackApexWithApk_Phase3");
+ }
+
private void crashProcess(String processName, int numberOfCrashes) throws Exception {
String pid = "";
String lastPid = "invalid";
@@ -202,4 +279,13 @@
// Find the NetworkStack path (can be NetworkStack.apk or NetworkStackNext.apk)
return getDevice().executeShellCommand("ls /system/priv-app/NetworkStack*/*.apk");
}
+
+ private boolean isCheckpointSupported() throws Exception {
+ try {
+ runPhase("isCheckpointSupported");
+ return true;
+ } catch (AssertionError ignore) {
+ return false;
+ }
+ }
}
diff --git a/tests/RollbackTest/testdata/AndroidManifest.xml b/tests/RollbackTest/testdata/AndroidManifest.xml
new file mode 100644
index 0000000..f21ec89
--- /dev/null
+++ b/tests/RollbackTest/testdata/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apex.apkrollback.test">
+ <!-- APEX does not have classes.dex -->
+ <application android:hasCode="false" />
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29"/>
+</manifest>
+
diff --git a/tests/RollbackTest/testdata/manifest_v1.json b/tests/RollbackTest/testdata/manifest_v1.json
new file mode 100644
index 0000000..1762fc6
--- /dev/null
+++ b/tests/RollbackTest/testdata/manifest_v1.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.apkrollback.test",
+ "version": 1
+}
diff --git a/tests/RollbackTest/testdata/manifest_v2.json b/tests/RollbackTest/testdata/manifest_v2.json
new file mode 100644
index 0000000..c5127b9c
--- /dev/null
+++ b/tests/RollbackTest/testdata/manifest_v2.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.apkrollback.test",
+ "version": 2
+}
diff --git a/tests/TaskOrganizerTest/Android.bp b/tests/TaskOrganizerTest/Android.bp
new file mode 100644
index 0000000..8a13dbc
--- /dev/null
+++ b/tests/TaskOrganizerTest/Android.bp
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test {
+ name: "TaskOrganizerTest",
+ srcs: ["**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/tests/TaskOrganizerTest/AndroidManifest.xml b/tests/TaskOrganizerTest/AndroidManifest.xml
new file mode 100644
index 0000000..0cb6c10
--- /dev/null
+++ b/tests/TaskOrganizerTest/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.taskembed">
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <application>
+ <service android:name=".TaskOrganizerPipTest"
+ android:exported="true">
+ </service>
+ </application>
+</manifest>
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
new file mode 100644
index 0000000..6ffa19d
--- /dev/null
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.taskembed;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.Service;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.ITaskOrganizer;
+import android.view.IWindowContainer;
+import android.view.WindowContainerTransaction;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+public class TaskOrganizerPipTest extends Service {
+ static final int PIP_WIDTH = 640;
+ static final int PIP_HEIGHT = 360;
+
+ class PipOrgView extends SurfaceView implements SurfaceHolder.Callback {
+ PipOrgView(Context c) {
+ super(c);
+ getHolder().addCallback(this);
+ setZOrderOnTop(true);
+ }
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ try {
+ ActivityTaskManager.getService().registerTaskOrganizer(mOrganizer,
+ WindowConfiguration.WINDOWING_MODE_PINNED);
+ } catch (Exception e) {
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+
+ void reparentTask(IWindowContainer wc) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ SurfaceControl leash = null;
+ try {
+ leash = wc.getLeash();
+ } catch (Exception e) {
+ // System server died.. oh well
+ }
+ t.reparent(leash, getSurfaceControl())
+ .setPosition(leash, 0, 0)
+ .apply();
+ }
+ }
+
+ PipOrgView mPipView;
+
+ class Organizer extends ITaskOrganizer.Stub {
+ public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) {
+ mPipView.reparentTask(wc);
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.scheduleFinishEnterPip(wc, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT));
+ try {
+ ActivityTaskManager.getService().applyContainerTransaction(wct);
+ } catch (Exception e) {
+ }
+ }
+ public void taskVanished(IWindowContainer wc) {
+ }
+ public void transactionReady(int id, SurfaceControl.Transaction t) {
+ }
+ }
+
+ Organizer mOrganizer = new Organizer();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams();
+ wlp.setTitle("TaskOrganizerPipTest");
+ wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ wlp.width = wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+ FrameLayout layout = new FrameLayout(this);
+ ViewGroup.LayoutParams lp =
+ new ViewGroup.LayoutParams(PIP_WIDTH, PIP_HEIGHT);
+ mPipView = new PipOrgView(this);
+ layout.addView(mPipView, lp);
+
+ WindowManager wm = getSystemService(WindowManager.class);
+ wm.addView(layout, wlp);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+}
diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java
index eed7159f..ca4ba63 100644
--- a/tests/net/common/java/android/net/CaptivePortalTest.java
+++ b/tests/net/common/java/android/net/CaptivePortalTest.java
@@ -44,6 +44,11 @@
}
@Override
+ public void appRequest(final int request) throws RemoteException {
+ mCode = request;
+ }
+
+ @Override
public void logEvent(int eventId, String packageName) throws RemoteException {
mCode = eventId;
mPackageName = packageName;
@@ -80,6 +85,12 @@
}
@Test
+ public void testReevaluateNetwork() {
+ final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.reevaluateNetwork());
+ assertEquals(result.mCode, CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED);
+ }
+
+ @Test
public void testLogEvent() {
final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent(
MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY,
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index a7328ac..6005cc3 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -27,8 +27,8 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.net.LinkProperties.CompareResult;
import android.net.LinkProperties.ProvisioningChange;
+import android.net.util.LinkPropertiesUtils.CompareResult;
import android.system.OsConstants;
import android.util.ArraySet;
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 1e8d83c..11d5b25 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -35,10 +35,10 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
import android.net.NetworkSpecifier;
import android.net.SocketKeepalive;
import android.net.UidRange;
@@ -114,7 +114,7 @@
public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp) {
super(wrapper.mHandlerThread.getLooper(), wrapper.mContext, wrapper.mLogTag,
wrapper.mNetworkInfo, wrapper.mNetworkCapabilities, lp, wrapper.mScore,
- new NetworkMisc(), NetworkFactory.SerialNumber.NONE);
+ new NetworkAgentConfig(), NetworkProvider.ID_NONE);
mWrapper = wrapper;
}
@@ -222,7 +222,7 @@
@Override
public Network getNetwork() {
- return new Network(mNetworkAgent.netId);
+ return mNetworkAgent.network;
}
public void expectPreventReconnectReceived(long timeoutMs) {
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index daf187d..91c9a2a 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -22,6 +22,8 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.net.util.MacAddressUtils;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -122,11 +124,11 @@
for (MacAddress mac : multicastAddresses) {
String msg = mac.toString() + " expected to be a multicast address";
- assertTrue(msg, mac.isMulticastAddress());
+ assertTrue(msg, MacAddressUtils.isMulticastAddress(mac));
}
for (MacAddress mac : unicastAddresses) {
String msg = mac.toString() + " expected not to be a multicast address";
- assertFalse(msg, mac.isMulticastAddress());
+ assertFalse(msg, MacAddressUtils.isMulticastAddress(mac));
}
}
@@ -156,7 +158,7 @@
public void testMacAddressConversions() {
final int iterations = 10000;
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.createRandomUnicastAddress();
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
String stringRepr = mac.toString();
byte[] bytesRepr = mac.toByteArray();
@@ -188,7 +190,7 @@
final String expectedLocalOui = "26:5f:78";
final MacAddress base = MacAddress.fromString(anotherOui + ":0:0:0");
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.createRandomUnicastAddress(base, r);
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress(base, r);
String stringRepr = mac.toString();
assertTrue(stringRepr + " expected to be a locally assigned address",
@@ -199,7 +201,7 @@
}
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.createRandomUnicastAddress();
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
String stringRepr = mac.toString();
assertTrue(stringRepr + " expected to be a locally assigned address",
diff --git a/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
new file mode 100644
index 0000000..47afed4
--- /dev/null
+++ b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+
+import android.telephony.SubscriptionManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link android.net.TelephonyNetworkSpecifier}.
+ */
+@SmallTest
+public class TelephonyNetworkSpecifierTest {
+ private static final int TEST_SUBID = 5;
+
+ /**
+ * Validate that IllegalArgumentException will be thrown if build TelephonyNetworkSpecifier
+ * without calling {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+ */
+ @Test
+ public void testBuilderBuildWithDefault() {
+ try {
+ new TelephonyNetworkSpecifier.Builder().build();
+ } catch (IllegalArgumentException iae) {
+ // expected, test pass
+ }
+ }
+
+ /**
+ * Validate that no exception will be thrown even if pass invalid subscription id to
+ * {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+ */
+ @Test
+ public void testBuilderBuildWithInvalidSubId() {
+ TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .build();
+ assertEquals(specifier.getSubscriptionId(), SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ /**
+ * Validate the correctness of TelephonyNetworkSpecifier when provide valid subId.
+ */
+ @Test
+ public void testBuilderBuildWithValidSubId() {
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ assertEquals(TEST_SUBID, specifier.getSubscriptionId());
+ }
+
+ /**
+ * Validate that parcel marshalling/unmarshalling works.
+ */
+ @Test
+ public void testParcel() {
+ TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ assertParcelSane(specifier, 1 /* fieldCount */);
+ }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index b2d363e..1901a1d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -575,7 +575,7 @@
}
};
- assertEquals(na.netId, nmNetworkCaptor.getValue().netId);
+ assertEquals(na.network.netId, nmNetworkCaptor.getValue().netId);
mNmCallbacks = nmCbCaptor.getValue();
mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor);
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 535298f..e863266 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -36,9 +36,8 @@
import android.net.INetd;
import android.net.Network;
import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
import android.net.NetworkScore;
import android.os.INetworkManagementService;
import android.text.format.DateUtils;
@@ -75,7 +74,6 @@
@Mock INetd mNetd;
@Mock INetworkManagementService mNMS;
@Mock Context mCtx;
- @Mock NetworkMisc mMisc;
@Mock NetworkNotificationManager mNotifier;
@Mock Resources mResources;
@@ -358,8 +356,8 @@
NetworkScore ns = new NetworkScore();
ns.putIntExtension(NetworkScore.LEGACY_SCORE, 50);
NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
- caps, ns, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS,
- NetworkFactory.SerialNumber.NONE);
+ caps, ns, mCtx, null, null /* config */, mConnService, mNetd, mDnsResolver, mNMS,
+ NetworkProvider.ID_NONE);
nai.everValidated = true;
return nai;
}
diff --git a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index b783467..de1028c 100644
--- a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -51,6 +51,7 @@
import android.net.NetworkPolicyManager;
import android.net.NetworkTemplate;
import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.os.Handler;
import android.provider.Settings;
import android.telephony.TelephonyManager;
@@ -229,7 +230,7 @@
verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any());
// Simulate callback after capability changes
- final NetworkCapabilities capabilities = new NetworkCapabilities()
+ NetworkCapabilities capabilities = new NetworkCapabilities()
.addCapability(NET_CAPABILITY_INTERNET)
.addTransportType(TRANSPORT_CELLULAR)
.setNetworkSpecifier(new StringNetworkSpecifier("234"));
@@ -239,6 +240,19 @@
networkCallback.getValue().onCapabilitiesChanged(
TEST_NETWORK,
capabilities);
+
+ // make sure it also works with the new introduced TelephonyNetworkSpecifier
+ capabilities = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(234).build());
+ if (!roaming) {
+ capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING);
+ }
+ networkCallback.getValue().onCapabilitiesChanged(
+ TEST_NETWORK,
+ capabilities);
}
@Test
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index b709af1..9b24887 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -33,8 +33,8 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.NetworkAgentConfig;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.test.TestLooper;
@@ -63,7 +63,6 @@
static final int NETID = 42;
@Mock ConnectivityService mConnectivity;
- @Mock NetworkMisc mMisc;
@Mock IDnsResolver mDnsResolver;
@Mock INetd mNetd;
@Mock INetworkManagementService mNms;
@@ -72,6 +71,7 @@
TestLooper mLooper;
Handler mHandler;
+ NetworkAgentConfig mAgentConfig = new NetworkAgentConfig();
Nat464Xlat makeNat464Xlat() {
return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) {
@@ -93,7 +93,7 @@
mNai.networkInfo = new NetworkInfo(null);
mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI);
when(mNai.connService()).thenReturn(mConnectivity);
- when(mNai.netMisc()).thenReturn(mMisc);
+ when(mNai.netAgentConfig()).thenReturn(mAgentConfig);
when(mNai.handler()).thenReturn(mHandler);
when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig);
@@ -104,7 +104,7 @@
String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b "
+ "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
nai.networkInfo.getDetailedState(),
- mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+ mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
nai.linkProperties.getLinkAddresses());
assertEquals(msg, expected, Nat464Xlat.requiresClat(nai));
}
@@ -113,7 +113,7 @@
String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b "
+ "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
nai.networkInfo.getDetailedState(),
- mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+ mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
nai.linkProperties.getLinkAddresses());
assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai));
}
@@ -151,11 +151,11 @@
assertRequiresClat(true, mNai);
assertShouldStartClat(true, mNai);
- mMisc.skip464xlat = true;
+ mAgentConfig.skip464xlat = true;
assertRequiresClat(false, mNai);
assertShouldStartClat(false, mNai);
- mMisc.skip464xlat = false;
+ mAgentConfig.skip464xlat = false;
assertRequiresClat(true, mNai);
assertShouldStartClat(true, mNai);
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 6de068e..a9e0b9a 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -80,6 +80,7 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -102,6 +103,7 @@
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.TestableNetworkStatsProvider;
import libcore.io.IoUtils;
@@ -1001,6 +1003,88 @@
mService.unregisterUsageRequest(unknownRequest);
}
+ @Test
+ public void testStatsProviderUpdateStats() throws Exception {
+ // Pretend that network comes online.
+ expectDefaultSettings();
+ final NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)};
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ // Register custom provider and retrieve callback.
+ final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider();
+ final INetworkStatsProviderCallback cb =
+ mService.registerNetworkStatsProvider("TEST", provider);
+ assertNotNull(cb);
+
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+
+ // Verifies that one requestStatsUpdate will be called during iface update.
+ provider.expectStatsUpdate(0 /* unused */);
+
+ // Create some initial traffic and report to the service.
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ final NetworkStats expectedStats = new NetworkStats(0L, 1)
+ .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+ TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+ 128L, 2L, 128L, 2L, 1L))
+ .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+ 0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+ 64L, 1L, 64L, 1L, 1L));
+ cb.onStatsUpdated(0 /* unused */, expectedStats, expectedStats);
+
+ // Make another empty mutable stats object. This is necessary since the new NetworkStats
+ // object will be used to compare with the old one in NetworkStatsRecoder, two of them
+ // cannot be the same object.
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ forcePollAndWaitForIdle();
+
+ // Verifies that one requestStatsUpdate and setAlert will be called during polling.
+ provider.expectStatsUpdate(0 /* unused */);
+ provider.expectSetAlert(MB_IN_BYTES);
+
+ // Verifies that service recorded history, does not verify uid tag part.
+ assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
+
+ // Verifies that onStatsUpdated updates the stats accordingly.
+ final NetworkStats stats = mSession.getSummaryForAllUid(
+ sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(2, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L);
+
+ // Verifies that unregister the callback will remove the provider from service.
+ cb.unregister();
+ forcePollAndWaitForIdle();
+ provider.assertNoCallback();
+ }
+
+ @Test
+ public void testStatsProviderSetAlert() throws Exception {
+ // Pretend that network comes online.
+ expectDefaultSettings();
+ NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)};
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+
+ // Register custom provider and retrieve callback.
+ final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider();
+ final INetworkStatsProviderCallback cb =
+ mService.registerNetworkStatsProvider("TEST", provider);
+ assertNotNull(cb);
+
+ // Simulates alert quota of the provider has been reached.
+ cb.onAlertReached();
+ HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT);
+
+ // Verifies that polling is triggered by alert reached.
+ provider.expectStatsUpdate(0 /* unused */);
+ // Verifies that global alert will be re-armed.
+ provider.expectSetAlert(MB_IN_BYTES);
+ }
+
private static File getBaseDir(File statsDir) {
File baseDir = new File(statsDir, "netstats");
baseDir.mkdirs();
@@ -1102,6 +1186,7 @@
private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
throws Exception {
when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS);
+ when(mSettings.getPollDelay()).thenReturn(0L);
when(mSettings.getSampleEnabled()).thenReturn(true);
final Config config = new Config(bucketDuration, deleteAge, deleteAge);
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 3623b11..469128b 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -800,7 +800,12 @@
}
// This is a normal reference.
- return util::make_unique<Reference>(data, ref_type);
+ auto reference = util::make_unique<Reference>(data, ref_type);
+ if (res_value.dataType == android::Res_value::TYPE_DYNAMIC_REFERENCE ||
+ res_value.dataType == android::Res_value::TYPE_DYNAMIC_ATTRIBUTE) {
+ reference->is_dynamic = true;
+ }
+ return reference;
} break;
}
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index c016cb4..b08bf9a 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -109,6 +109,20 @@
EXPECT_TRUE(private_ref);
}
+TEST(ResourceUtilsTest, ParseBinaryDynamicReference) {
+ android::Res_value value = {};
+ value.data = util::HostToDevice32(0x01);
+ value.dataType = android::Res_value::TYPE_DYNAMIC_REFERENCE;
+ std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(ResourceType::kId,
+ android::ConfigDescription(),
+ android::ResStringPool(), value,
+ nullptr);
+
+ Reference* ref = ValueCast<Reference>(item.get());
+ EXPECT_TRUE(ref->is_dynamic);
+ EXPECT_EQ(ref->id.value().id, 0x01);
+}
+
TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
bool create = false;
bool private_ref = false;
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 7498e13..8a2f5af 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -269,6 +269,11 @@
}
}
+// Message holding a boolean, so it can be optionally encoded.
+message Boolean {
+ bool value = 1;
+}
+
// A value that is a reference to another resource. This reference can be by name or resource ID.
message Reference {
enum Type {
@@ -289,6 +294,9 @@
// Whether this reference is referencing a private resource (@*package:type/entry).
bool private = 4;
+
+ // Whether this reference is dynamic.
+ Boolean is_dynamic = 5;
}
// A value that represents an ID. This is just a placeholder, as ID values are used to occupy a
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 4555caa..5b6935b 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -895,7 +895,7 @@
// android:versionCode from the framework AndroidManifest.xml.
ExtractCompileSdkVersions(asset_source->GetAssetManager());
}
- } else if (asset_source->IsPackageDynamic(entry.first)) {
+ } else if (asset_source->IsPackageDynamic(entry.first, entry.second)) {
final_table_.included_packages_[entry.first] = entry.second;
}
}
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index efbf636..4cd6e93 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -634,6 +634,7 @@
std::string* out_error) {
out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type());
out_ref->private_reference = pb_ref.private_();
+ out_ref->is_dynamic = pb_ref.is_dynamic().value();
if (pb_ref.id() != 0) {
out_ref->id = ResourceId(pb_ref.id());
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index e4b3fce..d9f6c19 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -418,6 +418,9 @@
pb_ref->set_private_(ref.private_reference);
pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type));
+ if (ref.is_dynamic) {
+ pb_ref->mutable_is_dynamic()->set_value(ref.is_dynamic);
+ }
}
template <typename T>
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index e7f2330..61a8335 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -608,4 +608,41 @@
ASSERT_FALSE(search_result.value().entry->overlayable_item);
}
+TEST(ProtoSerializeTest, SerializeAndDeserializeDynamicReference) {
+ Reference ref(ResourceId(0x00010001));
+ ref.is_dynamic = true;
+
+ pb::Item pb_item;
+ SerializeItemToPb(ref, &pb_item);
+
+ ASSERT_TRUE(pb_item.has_ref());
+ EXPECT_EQ(pb_item.ref().id(), ref.id.value().id);
+ EXPECT_TRUE(pb_item.ref().is_dynamic().value());
+
+ std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(),
+ android::ConfigDescription(), nullptr,
+ nullptr, nullptr);
+ Reference* actual_ref = ValueCast<Reference>(item.get());
+ EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id);
+ EXPECT_TRUE(actual_ref->is_dynamic);
+}
+
+TEST(ProtoSerializeTest, SerializeAndDeserializeNonDynamicReference) {
+ Reference ref(ResourceId(0x00010001));
+
+ pb::Item pb_item;
+ SerializeItemToPb(ref, &pb_item);
+
+ ASSERT_TRUE(pb_item.has_ref());
+ EXPECT_EQ(pb_item.ref().id(), ref.id.value().id);
+ EXPECT_FALSE(pb_item.ref().has_is_dynamic());
+
+ std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(),
+ android::ConfigDescription(), nullptr,
+ nullptr, nullptr);
+ Reference* actual_ref = ValueCast<Reference>(item.get());
+ EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id);
+ EXPECT_FALSE(actual_ref->is_dynamic);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 83e20b5..897fa80 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -245,7 +245,8 @@
return package_map;
}
-bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const {
+bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId,
+ const std::string& package_name) const {
if (packageId == 0) {
return true;
}
@@ -253,7 +254,7 @@
for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) {
for (const std::unique_ptr<const android::LoadedPackage>& loaded_package
: assets->GetLoadedArsc()->GetPackages()) {
- if (packageId == loaded_package->GetPackageId() && loaded_package->IsDynamic()) {
+ if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) {
return true;
}
}
@@ -328,12 +329,12 @@
bool found = false;
ResourceId res_id = 0;
uint32_t type_spec_flags;
+ ResourceName real_name;
// There can be mangled resources embedded within other packages. Here we will
// look into each package and look-up the mangled name until we find the resource.
asset_manager_.ForEachPackage([&](const std::string& package_name, uint8_t id) -> bool {
- ResourceName real_name(name.package, name.type, name.entry);
-
+ real_name = ResourceName(name.package, name.type, name.entry);
if (package_name != name.package) {
real_name.entry = mangled_entry;
real_name.package = package_name;
@@ -353,12 +354,12 @@
}
std::unique_ptr<SymbolTable::Symbol> s;
- if (name.type == ResourceType::kAttr) {
+ if (real_name.type == ResourceType::kAttr) {
s = LookupAttributeInTable(asset_manager_, res_id);
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = res_id;
- s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id());
+ s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package);
}
if (s) {
@@ -406,7 +407,7 @@
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = id;
- s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id());
+ s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package);
}
if (s) {
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index 6997cd6..06eaf63 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -194,7 +194,7 @@
bool AddAssetPath(const android::StringPiece& path);
std::map<size_t, std::string> GetAssignedPackageIds() const;
- bool IsPackageDynamic(uint32_t packageId) const;
+ bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const;
std::unique_ptr<SymbolTable::Symbol> FindByName(
const ResourceName& name) override;
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index 46105f4..0b2077d 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -149,7 +149,12 @@
The package name of the class containing the field/method.
"""
full_class_name = signature.split(";->")[0]
- package_name = full_class_name[1:full_class_name.rindex("/")]
+ # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy
+ if (full_class_name[0] != "L"):
+ raise ValueError("Expected to start with 'L': %s" % full_class_name)
+ full_class_name = full_class_name[1:]
+ # If full_class_name doesn't contain '/', then package_name will be ''.
+ package_name = full_class_name.rpartition("/")[0]
return package_name.replace('/', '.')
class FlagsDict:
diff --git a/tools/lock_agent/Android.bp b/tools/lock_agent/Android.bp
index 79dce4a..7b2ca9a 100644
--- a/tools/lock_agent/Android.bp
+++ b/tools/lock_agent/Android.bp
@@ -25,6 +25,7 @@
srcs: ["agent.cpp"],
static_libs: [
"libbase",
+ "liblog",
"libz",
"slicer",
],
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 0b82a3d..7bbac13 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -291,6 +291,15 @@
}
if (field->options().GetExtension(os::statsd::state_field_option).option() ==
+ os::statsd::StateField::PRIMARY_FIELD_FIRST_UID) {
+ if (javaType != JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ errorCount++;
+ } else {
+ atomDecl->primaryFields.push_back(FIRST_UID_IN_CHAIN_ID);
+ }
+ }
+
+ if (field->options().GetExtension(os::statsd::state_field_option).option() ==
os::statsd::StateField::EXCLUSIVE) {
if (javaType == JAVA_TYPE_UNKNOWN ||
javaType == JAVA_TYPE_ATTRIBUTION_CHAIN ||
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 3efdd52..87d4d5d 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -36,6 +36,8 @@
const int PULL_ATOM_START_ID = 10000;
+const int FIRST_UID_IN_CHAIN_ID = 0;
+
/**
* The types for atom parameters.
*/
diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp
index 54a9982..66ae964 100644
--- a/tools/stats_log_api_gen/atoms_info_writer.cpp
+++ b/tools/stats_log_api_gen/atoms_info_writer.cpp
@@ -25,6 +25,8 @@
namespace stats_log_api_gen {
static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) {
+ fprintf(out, "static int FIRST_UID_IN_CHAIN = 0;\n");
+
fprintf(out, "struct StateAtomFieldOptions {\n");
fprintf(out, " std::vector<int> primaryFields;\n");
fprintf(out, " int exclusiveField;\n");
diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp
index 7fa47f6..b09dcd5 100644
--- a/tools/stats_log_api_gen/java_writer.cpp
+++ b/tools/stats_log_api_gen/java_writer.cpp
@@ -142,16 +142,16 @@
fprintf(out,
"%s final int count = valueMap.size();\n", indent.c_str());
fprintf(out,
- "%s final SparseIntArray intMap = new SparseIntArray();\n",
+ "%s SparseIntArray intMap = null;\n",
indent.c_str());
fprintf(out,
- "%s final SparseLongArray longMap = new SparseLongArray();\n",
+ "%s SparseLongArray longMap = null;\n",
indent.c_str());
fprintf(out,
- "%s final SparseArray<String> stringMap = new SparseArray<>();\n",
+ "%s SparseArray<String> stringMap = null;\n",
indent.c_str());
fprintf(out,
- "%s final SparseArray<Float> floatMap = new SparseArray<>();\n",
+ "%s SparseArray<Float> floatMap = null;\n",
indent.c_str());
fprintf(out,
"%s for (int i = 0; i < count; i++) {\n", indent.c_str());
@@ -163,18 +163,42 @@
fprintf(out,
"%s if (value instanceof Integer) {\n", indent.c_str());
fprintf(out,
+ "%s if (null == intMap) {\n", indent.c_str());
+ fprintf(out,
+ "%s intMap = new SparseIntArray();\n", indent.c_str());
+ fprintf(out,
+ "%s }\n", indent.c_str());
+ fprintf(out,
"%s intMap.put(key, (Integer) value);\n", indent.c_str());
fprintf(out,
"%s } else if (value instanceof Long) {\n", indent.c_str());
fprintf(out,
+ "%s if (null == longMap) {\n", indent.c_str());
+ fprintf(out,
+ "%s longMap = new SparseLongArray();\n", indent.c_str());
+ fprintf(out,
+ "%s }\n", indent.c_str());
+ fprintf(out,
"%s longMap.put(key, (Long) value);\n", indent.c_str());
fprintf(out,
"%s } else if (value instanceof String) {\n", indent.c_str());
fprintf(out,
+ "%s if (null == stringMap) {\n", indent.c_str());
+ fprintf(out,
+ "%s stringMap = new SparseArray<>();\n", indent.c_str());
+ fprintf(out,
+ "%s }\n", indent.c_str());
+ fprintf(out,
"%s stringMap.put(key, (String) value);\n", indent.c_str());
fprintf(out,
"%s } else if (value instanceof Float) {\n", indent.c_str());
fprintf(out,
+ "%s if (null == floatMap) {\n", indent.c_str());
+ fprintf(out,
+ "%s floatMap = new SparseArray<>();\n", indent.c_str());
+ fprintf(out,
+ "%s }\n", indent.c_str());
+ fprintf(out,
"%s floatMap.put(key, (Float) value);\n", indent.c_str());
fprintf(out,
"%s }\n", indent.c_str());
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 180368c..70c9bef 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -32,6 +32,7 @@
// framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
// to a separate package.
"java/android/net/wifi/WifiNetworkScoreCache.java",
+ "java/android/net/wifi/WifiOemConfigStoreMigrationHook.java",
"java/android/net/wifi/wificond/*.java",
":libwificond_ipc_aidl",
],
@@ -49,20 +50,47 @@
"//frameworks/opt/net/wifi/libs/WifiTrackerLib/tests",
"//external/robolectric-shadows:__subpackages__",
+ "//frameworks/base/packages/SettingsLib/tests/integ",
+ "//external/sl4a:__subpackages__",
]
+// wifi-service needs pre-jarjared version of framework-wifi so it can reference copied utility
+// classes before they are renamed.
java_library {
- name: "framework-wifi",
+ name: "framework-wifi-pre-jarjar",
// TODO(b/140299412) should be core_current once we build against framework-system-stubs
sdk_version: "core_platform",
+ static_libs: [
+ "framework-wifi-util-lib",
+ "android.hardware.wifi-V1.0-java-constants",
+ ],
libs: [
// TODO(b/140299412) should be framework-system-stubs once we fix all @hide dependencies
"framework-minus-apex",
- "unsupportedappusage",
+ "framework-annotations-lib",
+ "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+ "unsupportedappusage-annotation", // for dalvik.annotation.compat.UnsupportedAppUsage
],
srcs: [
":framework-wifi-updatable-sources",
],
+ installable: false,
+ visibility: [
+ "//frameworks/opt/net/wifi/service",
+ "//frameworks/opt/net/wifi/tests/wifitests",
+ ],
+}
+
+// post-jarjar version of framework-wifi
+java_library {
+ name: "framework-wifi",
+ // TODO(b/140299412) should be core_current once we build against framework-system-stubs
+ sdk_version: "core_platform",
+ static_libs: [
+ "framework-wifi-pre-jarjar",
+ ],
+ jarjar_rules: ":wifi-jarjar-rules",
+
installable: true,
optimize: {
enabled: false
@@ -121,3 +149,8 @@
],
visibility: test_access_hidden_api_whitelist,
}
+
+filegroup {
+ name: "wifi-jarjar-rules",
+ srcs: ["jarjar-rules.txt"],
+}
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
new file mode 100644
index 0000000..8f72040
--- /dev/null
+++ b/wifi/jarjar-rules.txt
@@ -0,0 +1,40 @@
+rule android.net.InterfaceConfigurationParcel* @0
+rule android.net.InterfaceConfiguration* com.android.server.x.wifi.net.InterfaceConfiguration@1
+
+# We don't jar-jar the entire package because, we still use some classes (like
+# AsyncChannel in com.android.internal.util) from these packages which are not
+# inside our jar (currently in framework.jar, but will be in wifisdk.jar in the future).
+rule com.android.internal.util.FastXmlSerializer* com.android.server.x.wifi.util.FastXmlSerializer@1
+rule com.android.internal.util.HexDump* com.android.server.x.wifi.util.HexDump@1
+rule com.android.internal.util.IState* com.android.server.x.wifi.util.IState@1
+rule com.android.internal.util.MessageUtils* com.android.server.x.wifi.util.MessageUtils@1
+rule com.android.internal.util.State* com.android.server.x.wifi.util.State@1
+rule com.android.internal.util.StateMachine* com.android.server.x.wifi.util.StateMachine@1
+rule com.android.internal.util.WakeupMessage* com.android.server.x.wifi.util.WakeupMessage@1
+
+rule android.util.BackupUtils* com.android.server.x.wifi.util.BackupUtils@1
+rule android.util.LocalLog* com.android.server.x.wifi.util.LocalLog@1
+rule android.util.Rational* com.android.server.x.wifi.util.Rational@1
+
+rule android.os.BasicShellCommandHandler* com.android.server.x.wifi.os.BasicShellCommandHandler@1
+
+# Use our statically linked bouncy castle library
+rule org.bouncycastle.** com.android.server.x.wifi.bouncycastle.@1
+# Use our statically linked protobuf library
+rule com.google.protobuf.** com.android.server.x.wifi.protobuf.@1
+# use statically linked SystemMessageProto
+rule com.android.internal.messages.SystemMessageProto* com.android.server.x.wifi.messages.SystemMessageProto@1
+# Use our statically linked PlatformProperties library
+rule android.sysprop.** com.android.server.x.wifi.sysprop.@1
+
+
+# used by both framework-wifi and wifi-service
+rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1
+rule android.content.pm.ParceledListSlice* android.x.net.wifi.util.ParceledListSlice@1
+rule android.net.shared.Inet4AddressUtils* android.x.net.wifi.util.Inet4AddressUtils@1
+rule android.os.HandlerExecutor* android.x.net.wifi.util.HandlerExecutor@1
+rule android.telephony.Annotation* android.x.net.wifi.util.TelephonyAnnotation@1
+rule com.android.internal.util.AsyncChannel* android.x.net.wifi.util.AsyncChannel@1
+rule com.android.internal.util.AsyncService* android.x.net.wifi.util.AsyncService@1
+rule com.android.internal.util.Preconditions* android.x.net.wifi.util.Preconditions@1
+rule com.android.internal.util.Protocol* android.x.net.wifi.util.Protocol@1
diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
index 482b491..f81bcb9 100644
--- a/wifi/java/android/net/wifi/ISoftApCallback.aidl
+++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl
@@ -55,9 +55,17 @@
/**
- * Service to manager callback providing information of softap.
+ * Service to manager callback providing capability of softap.
*
* @param capability is the softap capability. {@link SoftApCapability}
*/
void onCapabilityChanged(in SoftApCapability capability);
+
+ /**
+ * Service to manager callback providing blocked client of softap with specific reason code.
+ *
+ * @param client the currently blocked client.
+ * @param blockedReason one of blocked reason from {@link WifiManager.SapClientBlockedReason}
+ */
+ void onBlockedClientConnecting(in WifiClient client, int blockedReason);
}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 71942f0..f490766 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -250,4 +250,6 @@
void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName);
int calculateSignalLevel(int rssi);
+
+ List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<ScanResult> scanResults);
}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index c6aca07..3413305 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -23,6 +23,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -793,10 +795,19 @@
}
}
- /** empty scan result
+ /**
+ * Construct an empty scan result.
*
- * {@hide}
- * */
+ * Test code has a need to construct a ScanResult in a specific state.
+ * (Note that mocking using Mockito does not work if the object needs to be parceled and
+ * unparceled.)
+ * Export a @SystemApi default constructor to allow tests to construct an empty ScanResult
+ * object. The test can then directly set the fields it cares about.
+ *
+ * @hide
+ */
+ @SystemApi
+ @VisibleForTesting
public ScanResult() {
}
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 945a236..a77d30a 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -32,6 +32,9 @@
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -182,6 +185,22 @@
private final @SecurityType int mSecurityType;
/**
+ * The flag to indicate client need to authorize by user
+ * when client is connecting to AP.
+ */
+ private final boolean mClientControlByUser;
+
+ /**
+ * The list of blocked client that can't associate to the AP.
+ */
+ private final List<MacAddress> mBlockedClientList;
+
+ /**
+ * The list of allowed client that can associate to the AP.
+ */
+ private final List<MacAddress> mAllowedClientList;
+
+ /**
* Delay in milliseconds before shutting down soft AP when
* there are no connected devices.
*/
@@ -219,7 +238,9 @@
/** Private constructor for Builder and Parcelable implementation. */
private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid,
@Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel,
- @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis) {
+ @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis,
+ boolean clientControlByUser, @NonNull List<MacAddress> blockedList,
+ @NonNull List<MacAddress> allowedList) {
mSsid = ssid;
mBssid = bssid;
mPassphrase = passphrase;
@@ -229,6 +250,9 @@
mSecurityType = securityType;
mMaxNumberOfClients = maxNumberOfClients;
mShutdownTimeoutMillis = shutdownTimeoutMillis;
+ mClientControlByUser = clientControlByUser;
+ mBlockedClientList = new ArrayList<>(blockedList);
+ mAllowedClientList = new ArrayList<>(allowedList);
}
@Override
@@ -248,13 +272,17 @@
&& mChannel == other.mChannel
&& mSecurityType == other.mSecurityType
&& mMaxNumberOfClients == other.mMaxNumberOfClients
- && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis;
+ && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis
+ && mClientControlByUser == other.mClientControlByUser
+ && Objects.equals(mBlockedClientList, other.mBlockedClientList)
+ && Objects.equals(mAllowedClientList, other.mAllowedClientList);
}
@Override
public int hashCode() {
return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
- mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis);
+ mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis,
+ mClientControlByUser, mBlockedClientList, mAllowedClientList);
}
@Override
@@ -270,6 +298,9 @@
sbuf.append(" \n SecurityType=").append(getSecurityType());
sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients);
sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis);
+ sbuf.append(" \n ClientControlByUser=").append(mClientControlByUser);
+ sbuf.append(" \n BlockedClientList=").append(mBlockedClientList);
+ sbuf.append(" \n AllowedClientList=").append(mAllowedClientList);
return sbuf.toString();
}
@@ -284,6 +315,9 @@
dest.writeInt(mSecurityType);
dest.writeInt(mMaxNumberOfClients);
dest.writeInt(mShutdownTimeoutMillis);
+ dest.writeBoolean(mClientControlByUser);
+ dest.writeTypedList(mBlockedClientList);
+ dest.writeTypedList(mAllowedClientList);
}
@Override
@@ -299,7 +333,9 @@
in.readString(),
in.readParcelable(MacAddress.class.getClassLoader()),
in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(),
- in.readInt(), in.readInt());
+ in.readInt(), in.readInt(), in.readBoolean(),
+ in.createTypedArrayList(MacAddress.CREATOR),
+ in.createTypedArrayList(MacAddress.CREATOR));
}
@Override
@@ -326,19 +362,6 @@
return mBssid;
}
- // TODO: Remove it after update the caller
- /**
- * Returns String set to be passphrase for the WPA2-PSK AP.
- * {@link #setWpa2Passphrase(String)}.
- */
- @Nullable
- public String getWpa2Passphrase() {
- if (mSecurityType == SECURITY_TYPE_WPA2_PSK) {
- return mPassphrase;
- }
- return null;
- }
-
/**
* Returns String set to be passphrase for current AP.
* {@link #setPassphrase(String, @SecurityType int)}.
@@ -400,6 +423,34 @@
}
/**
+ * Returns a flag indicating whether clients need to be pre-approved by the user.
+ * (true: authorization required) or not (false: not required).
+ * {@link Builder#enableClientControlByUser(Boolean)}.
+ */
+ public boolean isClientControlByUserEnabled() {
+ return mClientControlByUser;
+ }
+
+ /**
+ * Returns List of clients which aren't allowed to associate to the AP.
+ *
+ * Clients are configured using {@link Builder#setClientList(List, List)}
+ */
+ @NonNull
+ public List<MacAddress> getBlockedClientList() {
+ return mBlockedClientList;
+ }
+
+ /**
+ * List of clients which are allowed to associate to the AP.
+ * Clients are configured using {@link Builder#setClientList(List, List)}
+ */
+ @NonNull
+ public List<MacAddress> getAllowedClientList() {
+ return mAllowedClientList;
+ }
+
+ /**
* Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a
* Soft AP.
*
@@ -416,6 +467,9 @@
private int mMaxNumberOfClients;
private int mSecurityType;
private int mShutdownTimeoutMillis;
+ private boolean mClientControlByUser;
+ private List<MacAddress> mBlockedClientList;
+ private List<MacAddress> mAllowedClientList;
/**
* Constructs a Builder with default values (see {@link Builder}).
@@ -430,6 +484,9 @@
mMaxNumberOfClients = 0;
mSecurityType = SECURITY_TYPE_OPEN;
mShutdownTimeoutMillis = 0;
+ mClientControlByUser = false;
+ mBlockedClientList = new ArrayList<>();
+ mAllowedClientList = new ArrayList<>();
}
/**
@@ -447,6 +504,9 @@
mMaxNumberOfClients = other.mMaxNumberOfClients;
mSecurityType = other.mSecurityType;
mShutdownTimeoutMillis = other.mShutdownTimeoutMillis;
+ mClientControlByUser = other.mClientControlByUser;
+ mBlockedClientList = new ArrayList<>(other.mBlockedClientList);
+ mAllowedClientList = new ArrayList<>(other.mAllowedClientList);
}
/**
@@ -458,7 +518,8 @@
public SoftApConfiguration build() {
return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients,
- mShutdownTimeoutMillis);
+ mShutdownTimeoutMillis, mClientControlByUser, mBlockedClientList,
+ mAllowedClientList);
}
/**
@@ -505,22 +566,6 @@
return this;
}
- // TODO: Remove it after update the caller
- /**
- * Specifies that this AP should use WPA2-PSK with the given ASCII WPA2 passphrase.
- * When set to null, an open network is created.
- * <p>
- *
- * @param passphrase The passphrase to use, or null to unset a previously-set WPA2-PSK
- * configuration.
- * @return Builder for chaining.
- * @throws IllegalArgumentException when the passphrase is the empty string
- */
- @NonNull
- public Builder setWpa2Passphrase(@Nullable String passphrase) {
- return setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK);
- }
-
/**
* Specifies that this AP should use specific security type with the given ASCII passphrase.
*
@@ -691,5 +736,82 @@
mShutdownTimeoutMillis = timeoutMillis;
return this;
}
+
+ /**
+ * Configure the Soft AP to require manual user control of client association.
+ * If disabled (the default) then any client can associate to this Soft AP using the
+ * correct credentials until the Soft AP capacity is reached (capacity is hardware, carrier,
+ * or user limited - using {@link #setMaxNumberOfClients(int)}).
+ *
+ * If manual user control is enabled then clients will be accepted, rejected, or require
+ * a user approval based on the configuration provided by
+ * {@link #setClientList(List, List)}.
+ *
+ * <p>
+ * This method requires hardware support. Hardware support can be determined using
+ * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+ * {@link SoftApCapability#isFeatureSupported(int)}
+ * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
+ *
+ * <p>
+ * If the method is called on a device without hardware support then starting the soft AP
+ * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
+ * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+ *
+ * <p>
+ * <li>If not set, defaults to false (i.e The authoriztion is not required).</li>
+ *
+ * @param enabled true for enabling the control by user, false otherwise.
+ * @return Builder for chaining.
+ */
+ @NonNull
+ public Builder enableClientControlByUser(boolean enabled) {
+ mClientControlByUser = enabled;
+ return this;
+ }
+
+
+ /**
+ * This method together with {@link enableClientControlByUser(boolean)} control client
+ * connections to the AP. If {@link enableClientControlByUser(false)} is configured than
+ * this API has no effect and clients are allowed to associate to the AP (within limit of
+ * max number of clients).
+ *
+ * If {@link enableClientControlByUser(true)} is configured then this API configures
+ * 2 lists:
+ * <ul>
+ * <li>List of clients which are blocked. These are rejected.</li>
+ * <li>List of clients which are explicitly allowed. These are auto-accepted.</li>
+ * </ul>
+ *
+ * <p>
+ * All other clients which attempt to associate, whose MAC addresses are on neither list,
+ * are:
+ * <ul>
+ * <li>Rejected</li>
+ * <li>A callback {@link WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient)}
+ * is issued (which allows the user to add them to the allowed client list if desired).<li>
+ * </ul>
+ *
+ * @param blockedClientList list of clients which are not allowed to associate to the AP.
+ * @param allowedClientList list of clients which are allowed to associate to the AP
+ * without user pre-approval.
+ * @return Builder for chaining.
+ */
+ @NonNull
+ public Builder setClientList(@NonNull List<MacAddress> blockedClientList,
+ @NonNull List<MacAddress> allowedClientList) {
+ mBlockedClientList = new ArrayList<>(blockedClientList);
+ mAllowedClientList = new ArrayList<>(allowedClientList);
+ Iterator<MacAddress> iterator = mAllowedClientList.iterator();
+ while (iterator.hasNext()) {
+ MacAddress client = iterator.next();
+ int index = mBlockedClientList.indexOf(client);
+ if (index != -1) {
+ throw new IllegalArgumentException("A MacAddress exist in both list");
+ }
+ }
+ return this;
+ }
}
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index f4c5b91..a9621fc 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -29,6 +29,7 @@
import android.net.ProxyInfo;
import android.net.StaticIpConfiguration;
import android.net.Uri;
+import android.net.util.MacAddressUtils;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -39,6 +40,8 @@
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
@@ -370,7 +373,6 @@
* ECDHE_ECDSA
* ECDHE_RSA
* </pre>
- * @hide
*/
public static class SuiteBCipher {
private SuiteBCipher() { }
@@ -1164,7 +1166,7 @@
* @return true if mac is good to use
*/
public static boolean isValidMacAddressForRandomization(MacAddress mac) {
- return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned()
+ return mac != null && !MacAddressUtils.isMulticastAddress(mac) && mac.isLocallyAssigned()
&& !MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS).equals(mac);
}
@@ -1208,22 +1210,27 @@
*/
@SystemApi
public static class NetworkSelectionStatus {
- // Quality Network Selection Status enable, temporary disabled, permanently disabled
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "NETWORK_SELECTION_",
+ value = {
+ NETWORK_SELECTION_ENABLED,
+ NETWORK_SELECTION_TEMPORARY_DISABLED,
+ NETWORK_SELECTION_PERMANENTLY_DISABLED})
+ public @interface NetworkEnabledStatus {}
/**
- * This network is allowed to join Quality Network Selection
- * @hide
+ * This network will be considered as a potential candidate to connect to during network
+ * selection.
*/
public static final int NETWORK_SELECTION_ENABLED = 0;
/**
- * network was temporary disabled. Can be re-enabled after a time period expire
- * @hide
+ * This network was temporary disabled. May be re-enabled after a time out.
*/
- public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1;
+ public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1;
/**
- * network was permanently disabled.
- * @hide
+ * This network was permanently disabled.
*/
- public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2;
+ public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2;
/**
* Maximum Network selection status
* @hide
@@ -1455,6 +1462,7 @@
* Network selection status, should be in one of three status: enable, temporaily disabled
* or permanently disabled
*/
+ @NetworkEnabledStatus
private int mStatus;
/**
@@ -1635,6 +1643,56 @@
}
/**
+ * NetworkSelectionStatus exports an immutable public API.
+ * However, test code has a need to construct a NetworkSelectionStatus in a specific state.
+ * (Note that mocking using Mockito does not work if the object needs to be parceled and
+ * unparceled.)
+ * Export a @SystemApi Builder to allow tests to construct a NetworkSelectionStatus object
+ * in the desired state, without sacrificing NetworkSelectionStatus's immutability.
+ */
+ @VisibleForTesting
+ public static final class Builder {
+ private final NetworkSelectionStatus mNetworkSelectionStatus =
+ new NetworkSelectionStatus();
+
+ /**
+ * Set the current network selection status.
+ * One of:
+ * {@link #NETWORK_SELECTION_ENABLED},
+ * {@link #NETWORK_SELECTION_TEMPORARY_DISABLED},
+ * {@link #NETWORK_SELECTION_PERMANENTLY_DISABLED}
+ * @see NetworkSelectionStatus#getNetworkSelectionStatus()
+ */
+ @NonNull
+ public Builder setNetworkSelectionStatus(@NetworkEnabledStatus int status) {
+ mNetworkSelectionStatus.setNetworkSelectionStatus(status);
+ return this;
+ }
+
+ /**
+ *
+ * Set the current network's disable reason.
+ * One of the {@link #NETWORK_SELECTION_ENABLE} or DISABLED_* constants.
+ * e.g. {@link #DISABLED_ASSOCIATION_REJECTION}.
+ * @see NetworkSelectionStatus#getNetworkSelectionDisableReason()
+ */
+ @NonNull
+ public Builder setNetworkSelectionDisableReason(
+ @NetworkSelectionDisableReason int reason) {
+ mNetworkSelectionStatus.setNetworkSelectionDisableReason(reason);
+ return this;
+ }
+
+ /**
+ * Build a NetworkSelectionStatus object.
+ */
+ @NonNull
+ public NetworkSelectionStatus build() {
+ return mNetworkSelectionStatus;
+ }
+ }
+
+ /**
* Get the network disable reason string for a reason code (for debugging).
* @param reason specific error reason. One of the {@link #NETWORK_SELECTION_ENABLE} or
* DISABLED_* constants e.g. {@link #DISABLED_ASSOCIATION_REJECTION}.
@@ -1660,10 +1718,13 @@
}
/**
- * get current network network selection status
- * @return return current network network selection status
- * @hide
+ * Get the current network network selection status.
+ * One of:
+ * {@link #NETWORK_SELECTION_ENABLED},
+ * {@link #NETWORK_SELECTION_TEMPORARY_DISABLED},
+ * {@link #NETWORK_SELECTION_PERMANENTLY_DISABLED}
*/
+ @NetworkEnabledStatus
public int getNetworkSelectionStatus() {
return mStatus;
}
@@ -1965,10 +2026,11 @@
}
/**
- * Set the network selection status
+ * Set the network selection status.
* @hide
*/
- public void setNetworkSelectionStatus(NetworkSelectionStatus status) {
+ @SystemApi
+ public void setNetworkSelectionStatus(@NonNull NetworkSelectionStatus status) {
mNetworkSelectionStatus = status;
}
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 41f7c6e..5edcc2d 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -1028,8 +1028,10 @@
}
/**
- * @hide
+ * Get the client private key as supplied in {@link #setClientKeyEntryWithCertificateChain}, or
+ * null if unset.
*/
+ @Nullable
public PrivateKey getClientPrivateKey() {
return mClientPrivateKey;
}
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 62337cb..7cd00b9 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -17,6 +17,7 @@
package android.net.wifi;
import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -27,6 +28,8 @@
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -356,6 +359,72 @@
}
}
+ /**
+ * WifiInfo exports an immutable public API.
+ * However, test code has a need to construct a WifiInfo in a specific state.
+ * (Note that mocking using Mockito does not work if the object needs to be parceled and
+ * unparceled.)
+ * Export a @SystemApi Builder to allow tests to construct a WifiInfo object
+ * in the desired state, without sacrificing WifiInfo's immutability.
+ *
+ * @hide
+ */
+ // This builder was not made public to reduce confusion for external developers as there are
+ // no legitimate uses for this builder except for testing.
+ @SystemApi
+ @VisibleForTesting
+ public static final class Builder {
+ private final WifiInfo mWifiInfo = new WifiInfo();
+
+ /**
+ * Set the SSID, in the form of a raw byte array.
+ * @see WifiInfo#getSSID()
+ */
+ @NonNull
+ public Builder setSsid(@NonNull byte[] ssid) {
+ mWifiInfo.setSSID(WifiSsid.createFromByteArray(ssid));
+ return this;
+ }
+
+ /**
+ * Set the BSSID.
+ * @see WifiInfo#getBSSID()
+ */
+ @NonNull
+ public Builder setBssid(@NonNull String bssid) {
+ mWifiInfo.setBSSID(bssid);
+ return this;
+ }
+
+ /**
+ * Set the RSSI, in dBm.
+ * @see WifiInfo#getRssi()
+ */
+ @NonNull
+ public Builder setRssi(int rssi) {
+ mWifiInfo.setRssi(rssi);
+ return this;
+ }
+
+ /**
+ * Set the network ID.
+ * @see WifiInfo#getNetworkId()
+ */
+ @NonNull
+ public Builder setNetworkId(int networkId) {
+ mWifiInfo.setNetworkId(networkId);
+ return this;
+ }
+
+ /**
+ * Build a WifiInfo object.
+ */
+ @NonNull
+ public WifiInfo build() {
+ return mWifiInfo;
+ }
+ }
+
/** @hide */
public void setSSID(WifiSsid wifiSsid) {
mWifiSsid = wifiSsid;
@@ -631,6 +700,11 @@
/**
* Returns the Fully Qualified Domain Name of the network if it is a Passpoint network.
+ * <p>
+ * The FQDN may be
+ * <lt>{@code null} if no network currently connected, currently connected network is not
+ * passpoint network or the caller has insufficient permissions to access the FQDN.</lt>
+ * </p>
*/
public @Nullable String getPasspointFqdn() {
return mFqdn;
@@ -643,6 +717,12 @@
/**
* Returns the Provider Friendly Name of the network if it is a Passpoint network.
+ * <p>
+ * The Provider Friendly Name may be
+ * <lt>{@code null} if no network currently connected, currently connected network is not
+ * passpoint network or the caller has insufficient permissions to access the Provider Friendly
+ * Name. </lt>
+ * </p>
*/
public @Nullable String getPasspointProviderFriendlyName() {
return mProviderFriendlyName;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e870481..2f7cf54 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -233,16 +233,20 @@
public @interface SuggestionConnectionStatusCode {}
/**
- * Broadcast intent action indicating whether Wi-Fi scanning is allowed currently
- * @hide
+ * Broadcast intent action indicating whether Wi-Fi scanning is currently available.
+ * Available extras:
+ * - {@link #EXTRA_SCAN_AVAILABLE}
*/
- public static final String WIFI_SCAN_AVAILABLE = "wifi_scan_available";
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WIFI_SCAN_AVAILABLE =
+ "android.net.wifi.action.WIFI_SCAN_AVAILABLE";
/**
- * Extra int indicating scan availability, WIFI_STATE_ENABLED and WIFI_STATE_DISABLED
- * @hide
+ * A boolean extra indicating whether scanning is currently available.
+ * Sent in the broadcast {@link #ACTION_WIFI_SCAN_AVAILABLE}.
+ * Its value is true if scanning is currently available, false otherwise.
*/
- public static final String EXTRA_SCAN_AVAILABLE = "scan_enabled";
+ public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE";
/**
* Broadcast intent action indicating that the credential of a Wi-Fi network
@@ -666,7 +670,8 @@
public @interface SapStartFailure {}
/**
- * All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL}.
+ * All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL} and
+ * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
*
* @hide
*/
@@ -691,6 +696,37 @@
@SystemApi
public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "SAP_CLIENT_BLOCKED_REASON_" }, value = {
+ SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER,
+ SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SapClientBlockedReason {}
+
+ /**
+ * If Soft Ap client is blocked, this reason code means that client doesn't exist in the
+ * specified configuration {@link SoftApConfiguration.Builder#setClientList(List, List)}
+ * and the {@link SoftApConfiguration.Builder#enableClientControlByUser(true)}
+ * is configured as well.
+ * @hide
+ */
+ @SystemApi
+ public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0;
+
+ /**
+ * If Soft Ap client is blocked, this reason code means that no more clients can be
+ * associated to this AP since it reached maximum capacity. The maximum capacity is
+ * the minimum of {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} and
+ * {@link SoftApCapability#getMaxSupportedClients} which get from
+ * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"IFACE_IP_MODE_"}, value = {
@@ -1366,6 +1402,36 @@
}
/**
+ * Retrieve a list of {@link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+ * matching the given list of {@link ScanResult}.
+ *
+ * An available {@link WifiNetworkSuggestion} must satisfy:
+ * <ul>
+ * <li> Matching one of the {@link ScanResult} from the given list.
+ * <li> and {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} set
+ * to true.
+ * </ul>
+ *
+ * @param scanResults a list of scanResult.
+ * @return a list of @link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD
+ })
+ @NonNull
+ public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+ @NonNull List<ScanResult> scanResults) {
+ try {
+ return mService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given
* list of ScanResult.
*
@@ -2540,6 +2606,8 @@
* the same permissions as {@link #getScanResults}. If such access is not allowed,
* {@link WifiInfo#getSSID} will return {@link #UNKNOWN_SSID} and
* {@link WifiInfo#getBSSID} will return {@code "02:00:00:00:00:00"}.
+ * {@link WifiInfo#getPasspointFqdn()} will return null.
+ * {@link WifiInfo#getPasspointProviderFriendlyName()} will return null.
*
* @return the Wi-Fi information, contained in {@link WifiInfo}.
*/
@@ -3199,8 +3267,17 @@
/**
* Sets the Wi-Fi AP Configuration.
*
+ * If the API is called while the soft AP is enabled, the configuration will apply to
+ * the current soft AP if the new configuration only includes
+ * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)}
+ * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)}
+ * or {@link SoftApConfiguration.Builder#enableClientControlByUser(boolean)}
+ * or {@link SoftApConfiguration.Builder#setClientList(List, List)}.
+ *
+ * Otherwise, the configuration changes will be applied when the Soft AP is next started
+ * (the framework will not stop/start the AP).
+ *
* @param softApConfig A valid SoftApConfiguration specifying the configuration of the SAP.
-
* @return {@code true} if the operation succeeded, {@code false} otherwise
*
* @hide
@@ -3430,7 +3507,8 @@
* {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
* @param failureReason reason when in failed state. One of
* {@link #SAP_START_FAILURE_GENERAL},
- * {@link #SAP_START_FAILURE_NO_CHANNEL}
+ * {@link #SAP_START_FAILURE_NO_CHANNEL},
+ * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
*/
default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {}
@@ -3459,6 +3537,22 @@
// Do nothing: can be updated to add SoftApCapability details (e.g. meximum supported
// client number) to the UI.
}
+
+ /**
+ * Called when client trying to connect but device blocked the client with specific reason.
+ *
+ * Can be used to ask user to update client to allowed list or blocked list
+ * when reason is {@link SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER}, or
+ * indicate the block due to maximum supported client number limitation when reason is
+ * {@link SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS}.
+ *
+ * @param client the currently blocked client.
+ * @param blockedReason one of blocked reason from {@link SapClientBlockedReason}
+ */
+ default void onBlockedClientConnecting(@NonNull WifiClient client,
+ @SapClientBlockedReason int blockedReason) {
+ // Do nothing: can be used to ask user to update client to allowed list or blocked list.
+ }
}
/**
@@ -3525,6 +3619,19 @@
mCallback.onCapabilityChanged(capability);
});
}
+
+ @Override
+ public void onBlockedClientConnecting(@NonNull WifiClient client, int blockedReason) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "SoftApCallbackProxy: onBlockedClientConnecting: client=" + client
+ + " with reason = " + blockedReason);
+ }
+
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> {
+ mCallback.onBlockedClientConnecting(client, blockedReason);
+ });
+ }
}
/**
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 9c1475f..c0e0890 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -116,12 +116,12 @@
/**
* Whether this network is shared credential with user to allow user manually connect.
*/
- private boolean mIsUserAllowed;
+ private boolean mIsSharedWithUser;
/**
- * Whether the setIsUserAllowedToManuallyConnect have been called.
+ * Whether the setCredentialSharedWithUser have been called.
*/
- private boolean mIsUserAllowedBeenSet;
+ private boolean mIsSharedWithUserSet;
/**
* Pre-shared key for use with WAPI-PSK networks.
*/
@@ -146,8 +146,8 @@
mIsAppInteractionRequired = false;
mIsUserInteractionRequired = false;
mIsMetered = false;
- mIsUserAllowed = true;
- mIsUserAllowedBeenSet = false;
+ mIsSharedWithUser = true;
+ mIsSharedWithUserSet = false;
mPriority = UNASSIGNED_PRIORITY;
mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
mWapiPskPassphrase = null;
@@ -430,13 +430,13 @@
* <li>If not set, defaults to true (i.e. allow user to manually connect) for secure
* networks and false for open networks.</li>
*
- * @param isAllowed {@code true} to indicate that the credentials may be used by the user to
+ * @param isShared {@code true} to indicate that the credentials may be used by the user to
* manually connect to the network, {@code false} otherwise.
* @return Instance of {@link Builder} to enable chaining of the builder method.
*/
- public @NonNull Builder setIsUserAllowedToManuallyConnect(boolean isAllowed) {
- mIsUserAllowed = isAllowed;
- mIsUserAllowedBeenSet = true;
+ public @NonNull Builder setCredentialSharedWithUser(boolean isShared) {
+ mIsSharedWithUser = isShared;
+ mIsSharedWithUserSet = true;
return this;
}
@@ -602,11 +602,11 @@
}
wifiConfiguration = buildWifiConfiguration();
if (wifiConfiguration.isOpenNetwork()) {
- if (mIsUserAllowedBeenSet && mIsUserAllowed) {
+ if (mIsSharedWithUserSet && mIsSharedWithUser) {
throw new IllegalStateException("Open network should not be "
- + "setIsUserAllowedToManuallyConnect to true");
+ + "setCredentialSharedWithUser to true");
}
- mIsUserAllowed = false;
+ mIsSharedWithUser = false;
}
}
@@ -615,7 +615,7 @@
mPasspointConfiguration,
mIsAppInteractionRequired,
mIsUserInteractionRequired,
- mIsUserAllowed);
+ mIsSharedWithUser);
}
}
diff --git a/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
new file mode 100755
index 0000000..642dcb9
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Class used to provide one time hooks for existing OEM devices to migrate their config store
+ * data to the wifi mainline module.
+ * <p>
+ * Note:
+ * <li> OEM's need to implement {@link #load()} only if their
+ * existing config store format or file locations differs from the vanilla AOSP implementation (
+ * which is what the wifi mainline module understands).
+ * </li>
+ * <li> The wifi mainline module will invoke {@link #load()} method on every bootup, its
+ * the responsibility of the OEM implementation to ensure that this method returns non-null data
+ * only on the first bootup. Once the migration is done, the OEM can safely delete their config
+ * store files and then return null on any subsequent reboots. The first & only relevant invocation
+ * of {@link #load()} occurs when a previously released device upgrades to the wifi
+ * mainline module from an OEM implementation of the wifi stack.
+ * </li>
+ * @hide
+ */
+@SystemApi
+public final class WifiOemConfigStoreMigrationHook {
+ /**
+ * Container for all the wifi config data to migrate.
+ */
+ public static final class MigrationData implements Parcelable {
+ /**
+ * Builder to create instance of {@link MigrationData}.
+ */
+ public static final class Builder {
+ private List<WifiConfiguration> mUserSavedNetworkConfigurations;
+ private SoftApConfiguration mUserSoftApConfiguration;
+
+ public Builder() {
+ mUserSavedNetworkConfigurations = null;
+ mUserSoftApConfiguration = null;
+ }
+
+ /**
+ * Sets the list of all user's saved network configurations parsed from OEM config
+ * store files.
+ *
+ * @param userSavedNetworkConfigurations List of {@link WifiConfiguration} representing
+ * the list of user's saved networks
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setUserSavedNetworkConfigurations(
+ @NonNull List<WifiConfiguration> userSavedNetworkConfigurations) {
+ checkNotNull(userSavedNetworkConfigurations);
+ mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
+ return this;
+ }
+
+ /**
+ * Sets the user's softap configuration parsed from OEM config store files.
+ *
+ * @param userSoftApConfiguration {@link SoftApConfiguration} representing user's
+ * SoftAp configuration
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setUserSoftApConfiguration(
+ @NonNull SoftApConfiguration userSoftApConfiguration) {
+ checkNotNull(userSoftApConfiguration);
+ mUserSoftApConfiguration = userSoftApConfiguration;
+ return this;
+ }
+
+ /**
+ * Build an instance of {@link MigrationData}.
+ *
+ * @return Instance of {@link MigrationData}.
+ */
+ public @NonNull MigrationData build() {
+ return new MigrationData(mUserSavedNetworkConfigurations, mUserSoftApConfiguration);
+ }
+ }
+
+ private final List<WifiConfiguration> mUserSavedNetworkConfigurations;
+ private final SoftApConfiguration mUserSoftApConfiguration;
+
+ private MigrationData(
+ @Nullable List<WifiConfiguration> userSavedNetworkConfigurations,
+ @Nullable SoftApConfiguration userSoftApConfiguration) {
+ mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
+ mUserSoftApConfiguration = userSoftApConfiguration;
+ }
+
+ public static final @NonNull Parcelable.Creator<MigrationData> CREATOR =
+ new Parcelable.Creator<MigrationData>() {
+ @Override
+ public MigrationData createFromParcel(Parcel in) {
+ List<WifiConfiguration> userSavedNetworkConfigurations =
+ in.readArrayList(null);
+ SoftApConfiguration userSoftApConfiguration = in.readParcelable(null);
+ return new MigrationData(
+ userSavedNetworkConfigurations, userSoftApConfiguration);
+ }
+
+ @Override
+ public MigrationData[] newArray(int size) {
+ return new MigrationData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeList(mUserSavedNetworkConfigurations);
+ dest.writeParcelable(mUserSoftApConfiguration, flags);
+ }
+
+ /**
+ * Returns list of all user's saved network configurations.
+ *
+ * Note: Only to be returned if there is any format change in how OEM persisted this info.
+ * @return List of {@link WifiConfiguration} representing the list of user's saved networks,
+ * or null if no migration necessary.
+ */
+ @Nullable
+ public List<WifiConfiguration> getUserSavedNetworkConfigurations() {
+ return mUserSavedNetworkConfigurations;
+ }
+
+ /**
+ * Returns user's softap configuration.
+ *
+ * Note: Only to be returned if there is any format change in how OEM persisted this info.
+ * @return {@link SoftApConfiguration} representing user's SoftAp configuration,
+ * or null if no migration necessary.
+ */
+ @Nullable
+ public SoftApConfiguration getUserSoftApConfiguration() {
+ return mUserSoftApConfiguration;
+ }
+ }
+
+ private WifiOemConfigStoreMigrationHook() { }
+
+ /**
+ * Load data from OEM's config store.
+ *
+ * @return Instance of {@link MigrationData} for migrating data, null if no
+ * migration is necessary.
+ */
+ @Nullable
+ public static MigrationData load() {
+ // Note: OEM's should add code to parse data from their config store format here!
+ return null;
+ }
+}
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 2c39c32a..4f602fa 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -833,6 +833,7 @@
* delivered to the listener. It is possible that onFullResult will not be called for all
* results of the first scan if the listener was registered during the scan.
*
+ * @param executor the Executor on which to run the callback.
* @param listener specifies the object to report events to. This object is also treated as a
* key for this request, and must also be specified to cancel the request.
* Multiple requests should also not share this object.
@@ -955,15 +956,32 @@
* starts a single scan and reports results asynchronously
* @param settings specifies various parameters for the scan; for more information look at
* {@link ScanSettings}
- * @param workSource WorkSource to blame for power usage
* @param listener specifies the object to report events to. This object is also treated as a
* key for this scan, and must also be specified to cancel the scan. Multiple
* scans should also not share this object.
+ * @param workSource WorkSource to blame for power usage
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
+ startScan(settings, null, listener, workSource);
+ }
+
+ /**
+ * starts a single scan and reports results asynchronously
+ * @param settings specifies various parameters for the scan; for more information look at
+ * {@link ScanSettings}
+ * @param executor the Executor on which to run the callback.
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this scan, and must also be specified to cancel the scan. Multiple
+ * scans should also not share this object.
+ * @param workSource WorkSource to blame for power usage
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void startScan(ScanSettings settings, @Nullable @CallbackExecutor Executor executor,
+ ScanListener listener, WorkSource workSource) {
Objects.requireNonNull(listener, "listener cannot be null");
- int key = addListener(listener);
+ int key = addListener(listener, executor);
if (key == INVALID_KEY) return;
validateChannel();
Bundle scanParams = new Bundle();
@@ -1029,16 +1047,17 @@
* {@link ScanSettings}
* @param pnoSettings specifies various parameters for PNO; for more information look at
* {@link PnoSettings}
+ * @param executor the Executor on which to run the callback.
* @param listener specifies the object to report events to. This object is also treated as a
* key for this scan, and must also be specified to cancel the scan. Multiple
* scans should also not share this object.
* {@hide}
*/
public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
- PnoScanListener listener) {
+ @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) {
Objects.requireNonNull(listener, "listener cannot be null");
Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null");
- int key = addListener(listener);
+ int key = addListener(listener, executor);
if (key == INVALID_KEY) return;
validateChannel();
pnoSettings.isConnected = true;
@@ -1057,10 +1076,10 @@
*/
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
- PnoScanListener listener) {
+ @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) {
Objects.requireNonNull(listener, "listener cannot be null");
Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null");
- int key = addListener(listener);
+ int key = addListener(listener, executor);
if (key == INVALID_KEY) return;
validateChannel();
pnoSettings.isConnected = false;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
index 0de7ba6..dad431c1 100644
--- a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
@@ -17,7 +17,7 @@
package android.net.wifi.p2p.nsd;
import android.compat.annotation.UnsupportedAppUsage;
-import android.net.nsd.DnsSdTxtRecord;
+import android.net.util.nsd.DnsSdTxtRecord;
import android.os.Build;
import android.text.TextUtils;
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index d91efbc..3c13562d 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -589,4 +589,10 @@
public int calculateSignalLevel(int rssi) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+ List<ScanResult> scanResults) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp
new file mode 100644
index 0000000..6a39959
--- /dev/null
+++ b/wifi/tests/Android.bp
@@ -0,0 +1,50 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Make test APK
+// ============================================================
+
+android_test {
+ name: "FrameworksWifiApiTests",
+
+ defaults: ["framework-wifi-test-defaults"],
+
+ srcs: ["**/*.java"],
+
+ jacoco: {
+ include_filter: ["android.net.wifi.*"],
+ // TODO(b/147521214) need to exclude test classes
+ exclude_filter: [],
+ },
+
+ static_libs: [
+ "androidx.test.rules",
+ "core-test-rules",
+ "guava",
+ "mockito-target-minus-junit4",
+ "net-tests-utils",
+ "frameworks-base-testutils",
+ "truth-prebuilt",
+ ],
+
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+
+ test_suites: [
+ "device-tests",
+ "mts",
+ ],
+}
diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk
deleted file mode 100644
index d2c385b4..0000000
--- a/wifi/tests/Android.mk
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-# Make test APK
-# ============================================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-# This list is generated from the java source files in this module
-# The list is a comma separated list of class names with * matching zero or more characters.
-# Example:
-# Input files: src/com/android/server/wifi/Test.java src/com/android/server/wifi/AnotherTest.java
-# Generated exclude list: com.android.server.wifi.Test*,com.android.server.wifi.AnotherTest*
-
-# Filter all src files to just java files
-local_java_files := $(filter %.java,$(LOCAL_SRC_FILES))
-# Transform java file names into full class names.
-# This only works if the class name matches the file name and the directory structure
-# matches the package.
-local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
-# Convert class name list to jacoco exclude list
-# This appends a * to all classes and replace the space separators with commas.
-# These patterns will match all classes in this module and their inner classes.
-jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
-
-jacoco_include := android.net.wifi.*
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := $(jacoco_include)
-LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- androidx.test.rules \
- core-test-rules \
- guava \
- mockito-target-minus-junit4 \
- net-tests-utils \
- frameworks-base-testutils \
- truth-prebuilt \
-
-LOCAL_JAVA_LIBRARIES := \
- android.test.runner \
- android.test.base \
-
-LOCAL_PACKAGE_NAME := FrameworksWifiApiTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := \
- device-tests \
- mts \
-
-include $(BUILD_PACKAGE)
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index eeea7e2..6884a4e 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -25,6 +25,8 @@
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Random;
@SmallTest
@@ -112,12 +114,18 @@
@Test
public void testWpa2WithAllFieldCustomized() {
+ List<MacAddress> testBlockedClientList = new ArrayList<>();
+ List<MacAddress> testAllowedClientList = new ArrayList<>();
+ testBlockedClientList.add(MacAddress.fromString("11:22:33:44:55:66"));
+ testAllowedClientList.add(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
SoftApConfiguration original = new SoftApConfiguration.Builder()
.setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setChannel(149, SoftApConfiguration.BAND_5GHZ)
.setHiddenSsid(true)
.setMaxNumberOfClients(10)
.setShutdownTimeoutMillis(500000)
+ .enableClientControlByUser(true)
+ .setClientList(testBlockedClientList, testAllowedClientList)
.build();
assertThat(original.getPassphrase()).isEqualTo("secretsecret");
assertThat(original.getSecurityType()).isEqualTo(
@@ -127,6 +135,9 @@
assertThat(original.isHiddenSsid()).isEqualTo(true);
assertThat(original.getMaxNumberOfClients()).isEqualTo(10);
assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000);
+ assertThat(original.isClientControlByUserEnabled()).isEqualTo(true);
+ assertThat(original.getBlockedClientList()).isEqualTo(testBlockedClientList);
+ assertThat(original.getAllowedClientList()).isEqualTo(testAllowedClientList);
SoftApConfiguration unparceled = parcelUnparcel(original);
assertThat(unparceled).isNotSameAs(original);
@@ -238,4 +249,17 @@
.setShutdownTimeoutMillis(-1)
.build();
}
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testsetClientListExceptionWhenExistMacAddressInBothList() {
+ final MacAddress testMacAddress_1 = MacAddress.fromString("22:33:44:55:66:77");
+ final MacAddress testMacAddress_2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+ ArrayList<MacAddress> testAllowedClientList = new ArrayList<>();
+ testAllowedClientList.add(testMacAddress_1);
+ testAllowedClientList.add(testMacAddress_2);
+ ArrayList<MacAddress> testBlockedClientList = new ArrayList<>();
+ testBlockedClientList.add(testMacAddress_1);
+ SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+ configBuilder.setClientList(testBlockedClientList, testAllowedClientList);
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 8689a38..0ef75aa 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -27,6 +27,7 @@
import static org.junit.Assert.assertTrue;
import android.net.MacAddress;
+import android.net.util.MacAddressUtils;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.os.Parcel;
@@ -63,7 +64,7 @@
config.updateIdentifier = "1234";
config.fromWifiNetworkSpecifier = true;
config.fromWifiNetworkSuggestion = true;
- config.setRandomizedMacAddress(MacAddress.createRandomUnicastAddress());
+ config.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress());
MacAddress macBeforeParcel = config.getRandomizedMacAddress();
Parcel parcelW = Parcel.obtain();
config.writeToParcel(parcelW, 0);
@@ -169,7 +170,7 @@
MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
assertEquals(defaultMac, config.getRandomizedMacAddress());
- MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress();
+ MacAddress macToChangeInto = MacAddressUtils.createRandomUnicastAddress();
config.setRandomizedMacAddress(macToChangeInto);
MacAddress macAfterChange = config.getRandomizedMacAddress();
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 5ac50a0..5bdc344 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -70,6 +70,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.DhcpInfo;
+import android.net.MacAddress;
import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
import android.net.wifi.WifiManager.LocalOnlyHotspotObserver;
import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
@@ -897,6 +898,25 @@
}
/*
+ * Verify client-provided callback is being called through callback proxy
+ */
+ @Test
+ public void softApCallbackProxyCallsOnBlockedClientConnecting() throws Exception {
+ WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"));
+ ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+ mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+ anyInt());
+
+ callbackCaptor.getValue().onBlockedClientConnecting(testWifiClient,
+ WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+ mLooper.dispatchAll();
+ verify(mSoftApCallback).onBlockedClientConnecting(testWifiClient,
+ WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+ }
+
+ /*
* Verify client-provided callback is being called through callback proxy on multiple events
*/
@Test
@@ -2185,4 +2205,18 @@
result = WifiManager.parseDppChannelList(channelList);
assertEquals(result.size(), 0);
}
+
+ /**
+ * Test getWifiConfigsForMatchedNetworkSuggestions for given scanResults.
+ */
+ @Test
+ public void testGetWifiConfigsForMatchedNetworkSuggestions() throws Exception {
+ List<WifiConfiguration> testResults = new ArrayList<>();
+ testResults.add(new WifiConfiguration());
+
+ when(mWifiService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(any(List.class)))
+ .thenReturn(testResults);
+ assertEquals(testResults, mWifiManager
+ .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(new ArrayList<>()));
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 4cdc4bc..ac91544 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -76,7 +76,7 @@
.setSsid(TEST_SSID)
.setWpa2Passphrase(TEST_PRESHARED_KEY)
.setIsAppInteractionRequired(true)
- .setIsUserAllowedToManuallyConnect(false)
+ .setCredentialSharedWithUser(false)
.setPriority(0)
.build();
@@ -151,7 +151,7 @@
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID)
.setWpa3Passphrase(TEST_PRESHARED_KEY)
- .setIsUserAllowedToManuallyConnect(true)
+ .setCredentialSharedWithUser(true)
.build();
assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
@@ -709,14 +709,14 @@
/**
* Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
- * when {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} to
+ * when {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to
* true on a open network suggestion.
*/
@Test(expected = IllegalStateException.class)
public void testSetIsUserAllowedToManuallyConnectToWithOpenNetwork() {
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID)
- .setIsUserAllowedToManuallyConnect(true)
+ .setCredentialSharedWithUser(true)
.build();
}
}
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index 1af0bcb..0cc76b6 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -366,7 +366,7 @@
/**
* Test behavior of {@link WifiScanner#startDisconnectedPnoScan(ScanSettings, PnoSettings,
- * WifiScanner.PnoScanListener)}
+ * Executor, WifiScanner.PnoScanListener)}
* @throws Exception
*/
@Test
@@ -375,7 +375,8 @@
PnoSettings pnoSettings = new PnoSettings();
WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
- mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+ mWifiScanner.startDisconnectedPnoScan(
+ scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
mLooper.dispatchAll();
ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -396,7 +397,7 @@
/**
* Test behavior of {@link WifiScanner#startConnectedPnoScan(ScanSettings, PnoSettings,
- * WifiScanner.PnoScanListener)}
+ * Executor, WifiScanner.PnoScanListener)}
* @throws Exception
*/
@Test
@@ -405,7 +406,8 @@
PnoSettings pnoSettings = new PnoSettings();
WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
- mWifiScanner.startConnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+ mWifiScanner.startConnectedPnoScan(
+ scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
mLooper.dispatchAll();
ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -426,7 +428,7 @@
/**
* Test behavior of {@link WifiScanner#stopPnoScan(ScanListener)}
- * WifiScanner.PnoScanListener)}
+ * Executor, WifiScanner.PnoScanListener)}
* @throws Exception
*/
@Test
@@ -435,7 +437,8 @@
PnoSettings pnoSettings = new PnoSettings();
WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
- mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+ mWifiScanner.startDisconnectedPnoScan(
+ scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
mLooper.dispatchAll();
mWifiScanner.stopPnoScan(pnoScanListener);
mLooper.dispatchAll();