Merge "Expose NotificationChannel#setBlockableSystem as SystemApi"
diff --git a/Android.bp b/Android.bp
index 9613a11..b3faef1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -221,7 +221,6 @@
":framework-sax-sources",
":framework-telecomm-sources",
":framework-telephony-common-sources",
- ":framework-telephony-sources",
":framework-wifi-annotations",
":framework-wifi-non-updatable-sources",
":PacProcessor-aidl-sources",
@@ -256,6 +255,9 @@
// etc.
":framework-javastream-protos",
":framework-statslog-gen",
+
+ // telephony annotations
+ ":framework-telephony-annotations",
],
}
@@ -268,7 +270,9 @@
":framework-tethering-srcs",
":updatable-media-srcs",
":framework-mediaprovider-sources",
+ ":framework-permission-sources",
":framework-wifi-updatable-sources",
+ ":framework-telephony-sources",
":ike-srcs",
]
}
@@ -302,7 +306,7 @@
"rs/java",
"sax/java",
"telecomm/java",
- "telephony/java",
+ "wifi/java",
],
},
@@ -381,6 +385,7 @@
"updatable_media_stubs",
"framework_mediaprovider_stubs",
"framework-tethering",
+ "framework-telephony-stubs",
],
jarjar_rules: ":framework-jarjar-rules",
@@ -437,8 +442,9 @@
srcs: [":framework-non-updatable-sources"],
libs: [
"framework-appsearch-stubs",
- // TODO(b/146167933): Use framework-statsd-stubs
- "framework-statsd",
+ "framework-sdkextensions-stubs-systemapi",
+ "framework-statsd", // TODO(b/146167933): Use framework-statsd-stubs
+ "framework-permission-stubs",
"framework-wifi-stubs",
"ike-stubs",
],
@@ -464,7 +470,9 @@
"//frameworks/base/apex/appsearch/framework",
"//frameworks/base/apex/blobstore/framework",
"//frameworks/base/apex/jobscheduler/framework",
+ "//frameworks/base/apex/permission/framework",
"//frameworks/base/apex/statsd/service",
+ "//frameworks/base/telephony",
"//frameworks/base/wifi",
"//frameworks/opt/net/wifi/service",
],
@@ -488,6 +496,7 @@
"updatable_media_stubs",
"framework_mediaprovider_stubs",
"framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
+ "framework-permission-stubs",
"framework-sdkextensions-stubs-systemapi",
// TODO(b/146167933): Use framework-statsd-stubs instead.
"framework-statsd",
@@ -496,6 +505,9 @@
"ike-stubs",
// TODO(b/147200698): should be the stub of framework-tethering
"framework-tethering",
+ // TODO (b/147688669) should be framework-telephony-stubs
+ "framework-telephony",
+ // TODO(jiyong): add stubs for APEXes here
],
sdk_version: "core_platform",
apex_available: ["//apex_available:platform"],
@@ -1221,3 +1233,82 @@
"StubLibraries.bp",
"ApiDocs.bp",
]
+
+// TODO(b/147699819): move to frameworks/base/telephony/ folder
+droidstubs {
+ name: "framework-telephony-stubs-srcs",
+ srcs: [
+ ":framework-telephony-sources",
+ ":framework_native_aidl",
+ ":framework-javastream-protos",
+ ],
+ aidl: {
+ local_include_dirs: [
+ "core/java",
+ "telecomm/java"
+ ],
+ },
+ libs: [
+ "framework-annotations-lib",
+ "android.hardware.radio-V1.5-java",
+ ],
+ defaults: ["framework-module-stubs-defaults-systemapi"],
+ filter_packages: ["android.telephony"],
+ sdk_version: "system_current",
+}
+
+java_library {
+ name: "framework-telephony-stubs",
+ srcs: [":framework-telephony-stubs-srcs"],
+ // TODO(b/147699819): move public aidls to a separate folder and potentially remove
+ // below aidl exports.
+ aidl: {
+ export_include_dirs: ["telephony/java"],
+ },
+ sdk_version: "system_current",
+}
+
+java_library {
+ name: "framework-telephony",
+ srcs: [
+ ":framework-telephony-sources",
+ ],
+ // TODO: change to framework-system-stub to build against system APIs.
+ libs: [
+ "framework-minus-apex",
+ "unsupportedappusage",
+ ],
+ static_libs: [
+ "libphonenumber-platform",
+ "app-compat-annotations",
+ ],
+ sdk_version: "core_platform",
+ aidl: {
+ export_include_dirs: ["telephony/java"],
+ include_dirs: [
+ "frameworks/native/aidl/binder",
+ "frameworks/native/aidl/gui",
+ ]
+ },
+ jarjar_rules: ":telephony-framework-jarjar-rules",
+ dxflags: [
+ "--core-library",
+ "--multi-dex",
+ ],
+ // This is to break the dependency from boot jars.
+ dex_preopt: {
+ enabled: false,
+ },
+ installable: true,
+}
+
+filegroup {
+ // TODO (b/147690217): move to frameworks/base/telephony/common.
+ name: "framework-telephony-annotations",
+ srcs: ["telephony/java/android/telephony/Annotation.java"],
+}
+
+filegroup {
+ name: "telephony-framework-jarjar-rules",
+ srcs: ["telephony/framework-telephony-jarjar-rules.txt"],
+}
diff --git a/StubLibraries.bp b/StubLibraries.bp
index baa3c61..cdc0d32 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -51,6 +51,10 @@
":core_public_api_files",
":ike-api-srcs",
],
+ // TODO(b/147699819): remove below aidl includes.
+ aidl: {
+ local_include_dirs: ["telephony/java"],
+ },
libs: ["framework-internal-utils"],
installable: false,
annotations_enabled: true,
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 58bb605..83195dc 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -18,6 +18,7 @@
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;
@@ -25,6 +26,7 @@
import com.google.android.icing.proto.SchemaProto;
+import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -95,4 +97,34 @@
}
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/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index 8085aa8..fc83d8c 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -28,4 +28,5 @@
* if setSchema fails.
*/
void setSchema(in byte[] schemaProto, in AndroidFuture callback);
+ void put(in byte[] documentBytes, in AndroidFuture callback);
}
diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp
index 7327231..04f385e 100644
--- a/apex/appsearch/service/Android.bp
+++ b/apex/appsearch/service/Android.bp
@@ -20,6 +20,5 @@
"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 96316b3..ce7e04c 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -17,9 +17,14 @@
import android.app.appsearch.IAppSearchManager;
import android.content.Context;
+import android.os.Binder;
+import android.os.UserHandle;
import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
+import com.android.server.appsearch.impl.AppSearchImpl;
+import com.android.server.appsearch.impl.ImplInstanceManager;
import com.google.android.icing.proto.SchemaProto;
@@ -40,9 +45,27 @@
private class Stub extends IAppSearchManager.Stub {
@Override
public void setSchema(byte[] schemaBytes, AndroidFuture callback) {
+ Preconditions.checkNotNull(schemaBytes);
+ Preconditions.checkNotNull(callback);
+ int callingUid = Binder.getCallingUidOrThrow();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ long callingIdentity = Binder.clearCallingIdentity();
try {
SchemaProto schema = SchemaProto.parseFrom(schemaBytes);
- throw new UnsupportedOperationException("setSchema not yet implemented: " + schema);
+ AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
+ impl.setSchema(callingUid, schema);
+ callback.complete(null);
+ } catch (Throwable t) {
+ callback.completeExceptionally(t);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override
+ public void put(byte[] documentBytes, AndroidFuture callback) {
+ try {
+ throw new UnsupportedOperationException("Put document not yet implemented");
} catch (Throwable t) {
callback.completeExceptionally(t);
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
new file mode 100644
index 0000000..7c97b0b
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.impl;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+
+/**
+ * Manages interaction with {@link FakeIcing} and other components to implement AppSearch
+ * functionality.
+ */
+public final class AppSearchImpl {
+ private final Context mContext;
+ private final @UserIdInt int mUserId;
+ private final FakeIcing mFakeIcing = new FakeIcing();
+
+ AppSearchImpl(@NonNull Context context, @UserIdInt int userId) {
+ mContext = context;
+ mUserId = userId;
+ }
+
+ /**
+ * Updates the AppSearch schema for this app.
+ *
+ * @param callingUid The uid of the app calling AppSearch.
+ * @param origSchema The schema to set for this app.
+ */
+ public void setSchema(int callingUid, @NonNull SchemaProto origSchema) {
+ // Rewrite schema type names to include the calling app's package and uid.
+ String typePrefix = getTypePrefix(callingUid);
+ SchemaProto.Builder schemaBuilder = origSchema.toBuilder();
+ rewriteSchemaTypes(typePrefix, schemaBuilder);
+
+ // TODO(b/145635424): Save in schema type map
+ // TODO(b/145635424): Apply the schema to Icing and report results
+ }
+
+ /**
+ * Rewrites all types mentioned in the given {@code schemaBuilder} to prepend
+ * {@code typePrefix}.
+ *
+ * @param typePrefix The prefix to add
+ * @param schemaBuilder The schema to mutate
+ */
+ @VisibleForTesting
+ void rewriteSchemaTypes(
+ @NonNull String typePrefix, @NonNull SchemaProto.Builder schemaBuilder) {
+ for (int typeIdx = 0; typeIdx < schemaBuilder.getTypesCount(); typeIdx++) {
+ SchemaTypeConfigProto.Builder typeConfigBuilder =
+ schemaBuilder.getTypes(typeIdx).toBuilder();
+
+ // Rewrite SchemaProto.types.schema_type
+ String newSchemaType = typePrefix + typeConfigBuilder.getSchemaType();
+ typeConfigBuilder.setSchemaType(newSchemaType);
+
+ // Rewrite SchemaProto.types.properties.schema_type
+ for (int propertyIdx = 0;
+ propertyIdx < typeConfigBuilder.getPropertiesCount();
+ propertyIdx++) {
+ PropertyConfigProto.Builder propertyConfigBuilder =
+ typeConfigBuilder.getProperties(propertyIdx).toBuilder();
+ if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
+ String newPropertySchemaType =
+ typePrefix + propertyConfigBuilder.getSchemaType();
+ propertyConfigBuilder.setSchemaType(newPropertySchemaType);
+ typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
+ }
+ }
+
+ schemaBuilder.setTypes(typeIdx, typeConfigBuilder);
+ }
+ }
+
+ /**
+ * Returns a type prefix in a format like {@code com.example.package@1000/} or
+ * {@code com.example.sharedname:5678@1000/}.
+ */
+ @NonNull
+ private String getTypePrefix(int callingUid) {
+ // For regular apps, this call will return the package name. If callingUid is an
+ // android:sharedUserId, this value may be another type of name and have a :uid suffix.
+ String callingUidName = mContext.getPackageManager().getNameForUid(callingUid);
+ if (callingUidName == null) {
+ // Not sure how this is possible --- maybe app was uninstalled?
+ throw new IllegalStateException("Failed to look up package name for uid " + callingUid);
+ }
+ return callingUidName + "@" + mUserId + "/";
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java
new file mode 100644
index 0000000..395e30e
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.impl;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.util.SparseArray;
+
+/**
+ * Manages the lifecycle of instances of {@link AppSearchImpl}.
+ *
+ * <p>These instances are managed per unique device-user.
+ */
+public final class ImplInstanceManager {
+ private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>();
+
+ /**
+ * Gets an instance of AppSearchImpl for the given user.
+ *
+ * <p>If no AppSearchImpl instance exists for this user, Icing will be initialized and one will
+ * be created.
+ *
+ * @param context The Android context
+ * @param userId The multi-user userId of the device user calling AppSearch
+ * @return An initialized {@link AppSearchImpl} for this user
+ */
+ @NonNull
+ public static AppSearchImpl getInstance(@NonNull Context context, @UserIdInt int userId) {
+ AppSearchImpl instance = sInstances.get(userId);
+ if (instance == null) {
+ synchronized (ImplInstanceManager.class) {
+ instance = sInstances.get(userId);
+ if (instance == null) {
+ instance = new AppSearchImpl(context, userId);
+ sInstances.put(userId, instance);
+ }
+ }
+ }
+ return instance;
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index f6512a6..102e848 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -257,7 +257,7 @@
private final List<JobRestriction> mJobRestrictions;
private final CountQuotaTracker mQuotaTracker;
- private static final String QUOTA_TRACKER_SCHEDULE_TAG = ".schedule()";
+ private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
private final PlatformCompat mPlatformCompat;
/**
@@ -522,7 +522,7 @@
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 = 500;
+ 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;
@@ -740,6 +740,8 @@
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(
@@ -1054,44 +1056,48 @@
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
int userId, String tag) {
- final String pkg = packageName == null ? job.getService().getPackageName() : packageName;
- if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_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;
+ 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);
}
- } catch (RemoteException e) {
- throw new RuntimeException(e);
}
+ isDebuggable = mDebuggableApps.get(packageName);
}
- 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");
+ }
}
- 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;
}
- return JobScheduler.RESULT_FAILURE;
+ mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
}
- mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_TAG);
try {
if (ActivityManager.getService().isAppStartModeDisabled(uId,
diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp
index f232e62..d746ea6 100644
--- a/apex/permission/Android.bp
+++ b/apex/permission/Android.bp
@@ -22,6 +22,10 @@
name: "com.android.permission-defaults",
key: "com.android.permission.key",
certificate: ":com.android.permission.certificate",
+ java_libs: [
+ "framework-permission",
+ "service-permission",
+ ],
apps: ["PermissionController"],
}
diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp
new file mode 100644
index 0000000..8b03da3
--- /dev/null
+++ b/apex/permission/framework/Android.bp
@@ -0,0 +1,60 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+filegroup {
+ name: "framework-permission-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ],
+ path: "java",
+}
+
+java_library {
+ name: "framework-permission",
+ srcs: [
+ ":framework-permission-sources",
+ ],
+ sdk_version: "system_current",
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ hostdex: true,
+ installable: true,
+ visibility: [
+ "//frameworks/base/apex/permission:__subpackages__",
+ ],
+}
+
+droidstubs {
+ name: "framework-permission-stubs-sources",
+ srcs: [
+ ":framework-annotations",
+ ":framework-permission-sources",
+ ],
+ sdk_version: "system_current",
+ defaults: [
+ "framework-module-stubs-defaults-systemapi",
+ ],
+}
+
+java_library {
+ name: "framework-permission-stubs",
+ srcs: [
+ ":framework-permission-stubs-sources",
+ ],
+ sdk_version: "system_current",
+ installable: false,
+}
diff --git a/apex/permission/framework/java/android/permission/PermissionState.java b/apex/permission/framework/java/android/permission/PermissionState.java
new file mode 100644
index 0000000..e810db8
--- /dev/null
+++ b/apex/permission/framework/java/android/permission/PermissionState.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission;
+
+/**
+ * @hide
+ */
+public class PermissionState {}
diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp
new file mode 100644
index 0000000..972b362
--- /dev/null
+++ b/apex/permission/service/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_library {
+ name: "service-permission",
+ srcs: [
+ "java/**/*.java",
+ ],
+ sdk_version: "system_current",
+ libs: [
+ "framework-permission",
+ ],
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ installable: true,
+}
diff --git a/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java b/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java
new file mode 100644
index 0000000..a534e22
--- /dev/null
+++ b/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission;
+
+/**
+ * Persistence for runtime permissions.
+ */
+public class RuntimePermissionPersistence {}
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index c409f51..0ecf2f0 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -212,12 +212,17 @@
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS
*/
- oneway void unregisterPullerCallback(int atomTag, String packageName);
+ oneway void unregisterPullerCallback(int atomTag, String packageName);
- /**
- * Unregisters any pullAtomCallback for the given uid/atom.
- */
- oneway void unregisterPullAtomCallback(int uid, int atomTag);
+ /**
+ * Unregisters any pullAtomCallback for the given uid/atom.
+ */
+ oneway void unregisterPullAtomCallback(int uid, int atomTag);
+
+ /**
+ * Unregisters any pullAtomCallback for the given atom.
+ */
+ oneway void unregisterNativePullAtomCallback(int atomTag);
/**
* The install requires staging.
diff --git a/apex/statsd/framework/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/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index 6444b4e..8bc14d7 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -714,111 +714,6 @@
}
}
- /**
- * Helper method to extract the Parcelable controller info from a
- * SynchronousResultReceiver.
- */
- private static <T extends Parcelable> T awaitControllerInfo(
- @Nullable SynchronousResultReceiver receiver) {
- if (receiver == null) {
- return null;
- }
-
- try {
- final SynchronousResultReceiver.Result result =
- receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
- if (result.bundle != null) {
- // This is the final destination for the Bundle.
- result.bundle.setDefusable(true);
-
- final T data = result.bundle.getParcelable(
- RESULT_RECEIVER_CONTROLLER_KEY);
- if (data != null) {
- return data;
- }
- }
- Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
- } catch (TimeoutException e) {
- Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
- }
- return null;
- }
-
- private void pullWifiActivityInfo(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- WifiManager wifiManager;
- synchronized (this) {
- if (mWifiManager == null) {
- mWifiManager = mContext.getSystemService(WifiManager.class);
- }
- wifiManager = mWifiManager;
- }
- if (wifiManager == null) {
- return;
- }
- long token = Binder.clearCallingIdentity();
- try {
- SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi");
- wifiManager.getWifiActivityEnergyInfoAsync(
- new Executor() {
- @Override
- public void execute(Runnable runnable) {
- // run the listener on the binder thread, if it was run on the main
- // thread it would deadlock since we would be waiting on ourselves
- runnable.run();
- }
- },
- info -> {
- Bundle bundle = new Bundle();
- bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info);
- wifiReceiver.send(0, bundle);
- }
- );
- final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
- if (wifiInfo == null) {
- return;
- }
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeLong(wifiInfo.getTimeSinceBootMillis());
- e.writeInt(wifiInfo.getStackState());
- e.writeLong(wifiInfo.getControllerTxDurationMillis());
- e.writeLong(wifiInfo.getControllerRxDurationMillis());
- e.writeLong(wifiInfo.getControllerIdleDurationMillis());
- e.writeLong(wifiInfo.getControllerEnergyUsedMicroJoules());
- pulledData.add(e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private void pullModemActivityInfo(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- synchronized (this) {
- if (mTelephony == null) {
- mTelephony = mContext.getSystemService(TelephonyManager.class);
- }
- }
- if (mTelephony != null) {
- SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony");
- mTelephony.requestModemActivityInfo(modemReceiver);
- final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeLong(modemInfo.getTimestamp());
- e.writeLong(modemInfo.getSleepTimeMillis());
- e.writeLong(modemInfo.getIdleTimeMillis());
- e.writeLong(modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis());
- e.writeLong(modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis());
- e.writeLong(modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis());
- e.writeLong(modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis());
- e.writeLong(modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis());
- e.writeLong(modemInfo.getReceiveTimeMillis());
- pulledData.add(e);
- }
- }
-
private void pullSystemElapsedRealtime(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
@@ -1404,78 +1299,6 @@
}
}
- private BatteryStatsHelper getBatteryStatsHelper() {
- if (mBatteryStatsHelper == null) {
- final long callingToken = Binder.clearCallingIdentity();
- try {
- // clearCallingIdentity required for BatteryStatsHelper.checkWifiOnly().
- mBatteryStatsHelper = new BatteryStatsHelper(mContext, false);
- } finally {
- Binder.restoreCallingIdentity(callingToken);
- }
- mBatteryStatsHelper.create((Bundle) null);
- }
- long currentTime = SystemClock.elapsedRealtime();
- if (currentTime - mBatteryStatsHelperTimestampMs >= MAX_BATTERY_STATS_HELPER_FREQUENCY_MS) {
- // Load BatteryStats and do all the calculations.
- mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL);
- // Calculations are done so we don't need to save the raw BatteryStats data in RAM.
- mBatteryStatsHelper.clearStats();
- mBatteryStatsHelperTimestampMs = currentTime;
- }
- return mBatteryStatsHelper;
- }
-
- private long milliAmpHrsToNanoAmpSecs(double mAh) {
- final long MILLI_AMP_HR_TO_NANO_AMP_SECS = 1_000_000L * 3600L;
- return (long) (mAh * MILLI_AMP_HR_TO_NANO_AMP_SECS + 0.5);
- }
-
- private void pullDeviceCalculatedPowerUse(int tagId,
- long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
- BatteryStatsHelper bsHelper = getBatteryStatsHelper();
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeLong(milliAmpHrsToNanoAmpSecs(bsHelper.getComputedPower()));
- pulledData.add(e);
- }
-
- private void pullDeviceCalculatedPowerBlameUid(int tagId,
- long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
- final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
- if (sippers == null) {
- return;
- }
- for (BatterySipper bs : sippers) {
- if (bs.drainType != bs.drainType.APP) {
- continue;
- }
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeInt(bs.uidObj.getUid());
- e.writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah));
- pulledData.add(e);
- }
- }
-
- private void pullDeviceCalculatedPowerBlameOther(int tagId,
- long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
- final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
- if (sippers == null) {
- return;
- }
- for (BatterySipper bs : sippers) {
- if (bs.drainType == bs.drainType.APP) {
- continue; // This is a separate atom; see pullDeviceCalculatedPowerBlameUid().
- }
- if (bs.drainType == bs.drainType.USER) {
- continue; // This is not supported. We purposefully calculate over USER_ALL.
- }
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeInt(bs.drainType.ordinal());
- e.writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah));
- pulledData.add(e);
- }
- }
-
private void pullDiskIo(int tagId, long elapsedNanos, final long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
mStoragedUidIoStatsReader.readAbsolute((uid, fgCharsRead, fgCharsWrite, fgBytesRead,
@@ -1995,16 +1818,6 @@
long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
switch (tagId) {
- case StatsLog.WIFI_ACTIVITY_INFO: {
- pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
- case StatsLog.MODEM_ACTIVITY_INFO: {
- pullModemActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
case StatsLog.SYSTEM_ELAPSED_REALTIME: {
pullSystemElapsedRealtime(tagId, elapsedNanos, wallClockNanos, ret);
break;
@@ -2107,21 +1920,6 @@
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;
diff --git a/api/current.txt b/api/current.txt
index 5cef398..e27c318 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6839,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);
@@ -10404,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";
@@ -10629,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";
@@ -17286,6 +17289,7 @@
field public static final int LENS_OPTICAL_STABILIZATION_MODE_ON = 1; // 0x1
field public static final int LENS_POSE_REFERENCE_GYROSCOPE = 1; // 0x1
field public static final int LENS_POSE_REFERENCE_PRIMARY_CAMERA = 0; // 0x0
+ field public static final int LENS_POSE_REFERENCE_UNDEFINED = 2; // 0x2
field public static final int LENS_STATE_MOVING = 1; // 0x1
field public static final int LENS_STATE_STATIONARY = 0; // 0x0
field public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE = 0; // 0x0
@@ -30521,6 +30525,11 @@
field @Deprecated public static final String[] strings;
}
+ @Deprecated public static class WifiConfiguration.SuiteBCipher {
+ field @Deprecated public static final int ECDHE_ECDSA = 0; // 0x0
+ field @Deprecated public static final int ECDHE_RSA = 1; // 0x1
+ }
+
public class WifiEnterpriseConfig implements android.os.Parcelable {
ctor public WifiEnterpriseConfig();
ctor public WifiEnterpriseConfig(android.net.wifi.WifiEnterpriseConfig);
@@ -30646,6 +30655,7 @@
method public boolean isP2pSupported();
method public boolean isPreferredNetworkOffloadSupported();
method @Deprecated public boolean isScanAlwaysAvailable();
+ method public boolean isStaApConcurrencySupported();
method public boolean isTdlsSupported();
method public boolean isWapiSupported();
method public boolean isWifiEnabled();
@@ -30801,6 +30811,7 @@
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsAppInteractionRequired(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsEnhancedOpen(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsHiddenSsid(boolean);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsInitialAutoJoinEnabled(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsMetered(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserInteractionRequired(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration);
@@ -39058,7 +39069,7 @@
field public static final String COLUMN_MIME_TYPE = "mime_type";
field public static final String COLUMN_SIZE = "_size";
field public static final String COLUMN_SUMMARY = "summary";
- field public static final int FLAG_DIR_BLOCKS_TREE = 32768; // 0x8000
+ field public static final int FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE = 32768; // 0x8000
field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10
field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20
field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
@@ -44075,6 +44086,7 @@
method public java.util.List<android.telecom.Call> getChildren();
method public java.util.List<android.telecom.Call> getConferenceableCalls();
method public android.telecom.Call.Details getDetails();
+ method @Nullable public android.telecom.Call getGenericConferenceActiveChildCall();
method public android.telecom.Call getParent();
method public String getRemainingPostDialSequence();
method @Nullable public android.telecom.Call.RttCall getRttCall();
@@ -44158,6 +44170,7 @@
method public int getCallerDisplayNamePresentation();
method public int getCallerNumberVerificationStatus();
method public final long getConnectTimeMillis();
+ method @Nullable public String getContactDisplayName();
method public long getCreationTimeMillis();
method public android.telecom.DisconnectCause getDisconnectCause();
method public android.os.Bundle getExtras();
@@ -45167,6 +45180,39 @@
field public static final int PRIORITY_MED = 2; // 0x2
}
+ public final class BarringInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.telephony.BarringInfo.BarringServiceInfo getBarringServiceInfo(int);
+ method public boolean isServiceBarred(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int BARRING_SERVICE_TYPE_CS_FALLBACK = 5; // 0x5
+ field public static final int BARRING_SERVICE_TYPE_CS_SERVICE = 0; // 0x0
+ field public static final int BARRING_SERVICE_TYPE_CS_VOICE = 2; // 0x2
+ field public static final int BARRING_SERVICE_TYPE_EMERGENCY = 8; // 0x8
+ field public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO = 7; // 0x7
+ field public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE = 6; // 0x6
+ field public static final int BARRING_SERVICE_TYPE_MO_DATA = 4; // 0x4
+ field public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING = 3; // 0x3
+ field public static final int BARRING_SERVICE_TYPE_PS_SERVICE = 1; // 0x1
+ field public static final int BARRING_SERVICE_TYPE_SMS = 9; // 0x9
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.BarringInfo> CREATOR;
+ }
+
+ public static final class BarringInfo.BarringServiceInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getBarringType();
+ method public int getConditionalBarringFactor();
+ method public int getConditionalBarringTimeSeconds();
+ method public boolean isBarred();
+ method public boolean isConditionallyBarred();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int BARRING_TYPE_CONDITIONAL = 1; // 0x1
+ field public static final int BARRING_TYPE_NONE = 0; // 0x0
+ field public static final int BARRING_TYPE_UNCONDITIONAL = 2; // 0x2
+ field public static final int BARRING_TYPE_UNKNOWN = -1; // 0xffffffff
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.BarringInfo.BarringServiceInfo> CREATOR;
+ }
+
public class CarrierConfigManager {
method @Nullable public android.os.PersistableBundle getConfig();
method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
@@ -45860,6 +45906,7 @@
ctor public PhoneStateListener();
ctor public PhoneStateListener(@NonNull java.util.concurrent.Executor);
method public void onActiveDataSubscriptionIdChanged(int);
+ method public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onCallDisconnectCauseChanged(int, int);
method public void onCallForwardingIndicatorChanged(boolean);
method public void onCallStateChanged(int, String);
@@ -45877,6 +45924,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
@@ -46475,6 +46523,7 @@
field public static final int NETWORK_TYPE_UNKNOWN = 0; // 0x0
field public static final int PHONE_TYPE_CDMA = 2; // 0x2
field public static final int PHONE_TYPE_GSM = 1; // 0x1
+ field public static final int PHONE_TYPE_IMS = 5; // 0x5
field public static final int PHONE_TYPE_NONE = 0; // 0x0
field public static final int PHONE_TYPE_SIP = 3; // 0x3
field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index c8253a0..1cb1c20 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -124,3 +124,11 @@
}
+package android.util {
+
+ public final class Log {
+ method public static int logToRadioBuffer(int, @Nullable String, @Nullable String);
+ }
+
+}
+
diff --git a/api/system-current.txt b/api/system-current.txt
index 09684d8..e532a3a 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -126,6 +126,7 @@
field @Deprecated public static final String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING";
field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
+ field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE";
field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE";
field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING";
@@ -242,8 +243,10 @@
public static final class R.attr {
field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
field public static final int isVrOnly = 16844152; // 0x1010578
+ field public static final int minExtensionVersion = 16844306; // 0x1010612
field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
+ field public static final int sdkVersion = 16844305; // 0x1010611
field public static final int supportsAmbientMode = 16844173; // 0x101058d
field public static final int userRestriction = 16844164; // 0x1010584
}
@@ -807,7 +810,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);
@@ -6244,6 +6246,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean setSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsConfiguratorInitiator(@NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
@@ -6593,6 +6596,7 @@
public final class PasspointConfiguration implements android.os.Parcelable {
method public boolean isAutoJoinEnabled();
+ method public boolean isMacRandomizationEnabled();
}
public abstract class ProvisioningCallback {
@@ -7714,7 +7718,10 @@
public final class PermissionManager {
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public int getRuntimePermissionsVersion();
method @NonNull public java.util.List<android.permission.PermissionManager.SplitPermissionInfo> getSplitPermissions();
+ method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToEnabledImsServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToEnabledTelephonyDataServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToLuiApp(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void revokeDefaultPermissionsFromLuiApps(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public void setRuntimePermissionsVersion(@IntRange(from=0) int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, int, int);
@@ -8015,6 +8022,8 @@
field public static final int COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE = 5; // 0x5
field public static final int COLUMN_INDEX_XML_RES_RANK = 0; // 0x0
field public static final int COLUMN_INDEX_XML_RES_RESID = 1; // 0x1
+ field public static final String DYNAMIC_INDEXABLES_RAW = "dynamic_indexables_raw";
+ field public static final String DYNAMIC_INDEXABLES_RAW_PATH = "settings/dynamic_indexables_raw";
field public static final String INDEXABLES_RAW = "indexables_raw";
field public static final String[] INDEXABLES_RAW_COLUMNS;
field public static final String INDEXABLES_RAW_PATH = "settings/indexables_raw";
@@ -8072,6 +8081,7 @@
method public String getType(android.net.Uri);
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public android.database.Cursor query(android.net.Uri, String[], String, String[], String);
+ method @Nullable public android.database.Cursor queryDynamicRawData(@Nullable String[]);
method public abstract android.database.Cursor queryNonIndexableKeys(String[]);
method public abstract android.database.Cursor queryRawData(String[]);
method @Nullable public android.database.Cursor querySliceUriPairs();
@@ -8097,6 +8107,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";
@@ -8109,13 +8120,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";
}
@@ -8156,6 +8175,10 @@
field public static final int VOLUME_HUSH_VIBRATE = 1; // 0x1
}
+ public static final class Settings.System extends android.provider.Settings.NameValueTable {
+ method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean);
+ }
+
public static interface Telephony.CarrierColumns extends android.provider.BaseColumns {
field @NonNull public static final android.net.Uri CONTENT_URI;
field public static final String EXPIRATION_TIME = "expiration_time";
@@ -9467,6 +9490,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();
@@ -10350,6 +10378,7 @@
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);
@@ -10507,6 +10536,7 @@
}
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);
}
@@ -10727,6 +10757,7 @@
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";
@@ -10766,6 +10797,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
@@ -10789,6 +10821,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);
@@ -12121,7 +12154,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);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 32959c9..28119e3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -73,6 +73,7 @@
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener);
method public static void resumeAppSwitches() throws android.os.RemoteException;
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
+ method @RequiresPermission("android.permission.MANAGE_USERS") public boolean switchUser(@NonNull android.os.UserHandle);
field public static final int PROCESS_CAPABILITY_ALL = 1; // 0x1
field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1
field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
@@ -756,6 +757,7 @@
method public int getUserId();
method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
+ method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
field public static final String BLOB_STORE_SERVICE = "blob_store";
field public static final String BUGREPORT_SERVICE = "bugreport";
field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
@@ -2512,10 +2514,12 @@
method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static float getFloat(@NonNull String, @NonNull String, float);
method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static int getInt(@NonNull String, @NonNull String, int);
method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static long getLong(@NonNull String, @NonNull String, long);
+ method @NonNull @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getProperty(@NonNull String, @NonNull String);
method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getString(@NonNull String, @NonNull String, @Nullable String);
method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException;
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
field public static final String NAMESPACE_ANDROID = "android";
field public static final String NAMESPACE_AUTOFILL = "autofill";
@@ -2527,6 +2531,10 @@
field public static final String NAMESPACE_ROLLBACK_BOOT = "rollback_boot";
}
+ public static class DeviceConfig.BadConfigException extends java.lang.Exception {
+ ctor public DeviceConfig.BadConfigException();
+ }
+
public static interface DeviceConfig.OnPropertiesChangedListener {
method public void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties);
}
@@ -3137,6 +3145,15 @@
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);
@@ -4663,6 +4680,18 @@
package android.view.inputmethod {
+ public final class InlineSuggestion implements android.os.Parcelable {
+ method @NonNull public static android.view.inputmethod.InlineSuggestion newInlineSuggestion(@NonNull android.view.inputmethod.InlineSuggestionInfo);
+ }
+
+ public final class InlineSuggestionInfo implements android.os.Parcelable {
+ method @NonNull public static android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(@NonNull android.view.inline.InlinePresentationSpec, @NonNull String, @Nullable String[]);
+ }
+
+ public final class InlineSuggestionsResponse implements android.os.Parcelable {
+ method @NonNull public static android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(@NonNull java.util.List<android.view.inputmethod.InlineSuggestion>);
+ }
+
public final class InputMethodManager {
method public int getDisplayId();
method public boolean isInputMethodPickerShown();
diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt
index 6d1f291..603f7a2 100644
--- a/api/test-lint-baseline.txt
+++ b/api/test-lint-baseline.txt
@@ -400,7 +400,7 @@
GetterSetterNames: android.location.GnssClock#setTimeUncertaintyNanos(double):
GetterSetterNames: android.location.GnssMeasurement#setBasebandCn0DbHz(double):
-
+
GetterSetterNames: android.location.GnssMeasurement#setCarrierFrequencyHz(float):
GetterSetterNames: android.location.GnssMeasurement#setCodeType(String):
@@ -466,7 +466,7 @@
KotlinOperator: android.os.WorkSource#get(int):
KotlinOperator: android.util.SparseArrayMap#get(int, String):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+
ListenerInterface: android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener:
@@ -2059,6 +2059,18 @@
MissingNullability: android.view.KeyEvent#actionToString(int):
+MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #0:
+
+MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #1:
+
+MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #2:
+
+MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #0:
+
+MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #1:
+
+MissingNullability: android.view.SurfaceControlViewHost#relayout(android.view.WindowManager.LayoutParams) parameter #0:
+
MissingNullability: android.view.View#getTooltipView():
MissingNullability: android.view.View#isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable) parameter #0:
@@ -2079,18 +2091,6 @@
MissingNullability: android.view.WindowManager.LayoutParams#accessibilityTitle:
-MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #0:
-
-MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #1:
-
-MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #2:
-
-MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #0:
-
-MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #1:
-
-MissingNullability: android.view.SurfaceControlViewHost#relayout(android.view.WindowManager.LayoutParams) parameter #0:
-
MissingNullability: android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager) parameter #0:
MissingNullability: android.view.accessibility.AccessibilityNodeInfo#setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger) parameter #0:
@@ -2426,11 +2426,13 @@
ProtectedMember: android.view.ViewGroup#resetResolvedDrawables():
-PublicTypedef: android.os.HwParcel.Status: Don't expose @IntDef: @Status must be hidden.
-PublicTypedef: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability: Don't expose @IntDef: @MmTelCapability must be hidden.
-
-PublicTypedef: android.telephony.ims.feature.MmTelFeature.ProcessCallResult: Don't expose @IntDef: @ProcessCallResult must be hidden.
+PublicTypedef: android.os.HwParcel.Status:
+
+PublicTypedef: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability:
+
+PublicTypedef: android.telephony.ims.feature.MmTelFeature.ProcessCallResult:
+
RawAidl: android.telephony.mbms.vendor.MbmsDownloadServiceBase:
@@ -2513,6 +2515,8 @@
SamShouldBeLast: android.database.sqlite.SQLiteDirectCursorDriver#query(android.database.sqlite.SQLiteDatabase.CursorFactory, String[]):
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper):
+ SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
SamShouldBeLast: android.os.BugreportManager#startBugreport(android.os.ParcelFileDescriptor, android.os.ParcelFileDescriptor, android.os.BugreportParams, java.util.concurrent.Executor, android.os.BugreportManager.BugreportCallback):
@@ -2593,6 +2597,8 @@
+UserHandle: android.app.ActivityManager#switchUser(android.os.UserHandle):
+ When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
UserHandle: android.app.admin.DevicePolicyManager#getOwnerInstalledCaCerts(android.os.UserHandle):
UserHandle: android.app.role.RoleManager#addOnRoleHoldersChangedListenerAsUser(java.util.concurrent.Executor, android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle):
@@ -2607,8 +2613,12 @@
UserHandle: android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, android.os.UserHandle, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Boolean>):
-UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle):
+UserHandle: android.app.usage.StorageStatsManager#queryCratesForPackage(java.util.UUID, String, android.os.UserHandle):
When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
+UserHandle: android.app.usage.StorageStatsManager#queryCratesForUser(java.util.UUID, android.os.UserHandle):
+ When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
+UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle):
+
UserHandle: android.content.pm.PackageManager#getInstallReason(String, android.os.UserHandle):
UserHandle: android.content.pm.PackageManager#getPermissionFlags(String, String, android.os.UserHandle):
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index d7b6d69..64f4c66 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -1,3 +1,28 @@
+// 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.
+
+java_binary {
+ name: "incident-helper-cmd",
+ wrapper: "incident_helper_cmd",
+ srcs: [
+ "java/**/*.java",
+ ],
+ proto: {
+ plugin: "javastream",
+ },
+}
+
cc_defaults {
name: "incident_helper_defaults",
diff --git a/cmds/incident_helper/incident_helper_cmd b/cmds/incident_helper/incident_helper_cmd
new file mode 100644
index 0000000..d45f7df
--- /dev/null
+++ b/cmds/incident_helper/incident_helper_cmd
@@ -0,0 +1,6 @@
+#!/system/bin/sh
+# Script to start "incident_helper_cmd" on the device
+#
+base=/system
+export CLASSPATH=$base/framework/incident-helper-cmd.jar
+exec app_process $base/bin com.android.commands.incident.IncidentHelper "$@"
diff --git a/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java b/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java
new file mode 100644
index 0000000..d97b17e
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java
@@ -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.commands.incident;
+
+/**
+ * Thrown when there is an error executing a section.
+ */
+public class ExecutionException extends Exception {
+ /**
+ * Constructs a ExecutionException.
+ *
+ * @param msg the message
+ */
+ public ExecutionException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs a ExecutionException from another exception.
+ *
+ * @param e the exception
+ */
+ public ExecutionException(Exception e) {
+ super(e);
+ }
+}
diff --git a/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java b/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java
new file mode 100644
index 0000000..e5874e0
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java
@@ -0,0 +1,124 @@
+/*
+ * 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.commands.incident;
+
+import android.util.Log;
+
+import com.android.commands.incident.sections.PersistLogSection;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Helper command runner for incidentd to run customized command to gather data for a non-standard
+ * section.
+ */
+public class IncidentHelper {
+ private static final String TAG = "IncidentHelper";
+ private static boolean sLog = false;
+ private final List<String> mArgs;
+ private ListIterator<String> mArgsIterator;
+
+ private IncidentHelper(String[] args) {
+ mArgs = Collections.unmodifiableList(Arrays.asList(args));
+ mArgsIterator = mArgs.listIterator();
+ }
+
+ private static void showUsage(PrintStream out) {
+ out.println("This command is not designed to be run manually.");
+ out.println("Usage:");
+ out.println(" run [sectionName]");
+ }
+
+ private void run(String[] args) throws ExecutionException {
+ Section section = null;
+ List<String> sectionArgs = new ArrayList<>();
+ while (mArgsIterator.hasNext()) {
+ String arg = mArgsIterator.next();
+ if ("-l".equals(arg)) {
+ sLog = true;
+ Log.i(TAG, "Args: [" + String.join(",", args) + "]");
+ } else if ("run".equals(arg)) {
+ section = getSection(nextArgRequired());
+ mArgsIterator.forEachRemaining(sectionArgs::add);
+ break;
+ } else {
+ log(Log.WARN, TAG, "Error: Unknown argument: " + arg);
+ return;
+ }
+ }
+ section.run(System.in, System.out, sectionArgs);
+ }
+
+ private static Section getSection(String name) throws IllegalArgumentException {
+ if ("persisted_logs".equals(name)) {
+ return new PersistLogSection();
+ }
+ throw new IllegalArgumentException("Section not found: " + name);
+ }
+
+ private String nextArgRequired() {
+ if (!mArgsIterator.hasNext()) {
+ throw new IllegalArgumentException(
+ "Arg required after \"" + mArgs.get(mArgsIterator.previousIndex()) + "\"");
+ }
+ return mArgsIterator.next();
+ }
+
+ /**
+ * Print the given message to stderr, also log it if asked to (set by -l cmd arg).
+ */
+ public static void log(int priority, String tag, String msg) {
+ System.err.println(tag + ": " + msg);
+ if (sLog) {
+ Log.println(priority, tag, msg);
+ }
+ }
+
+ /**
+ * Command-line entry point.
+ *
+ * @param args The command-line arguments
+ */
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ showUsage(System.err);
+ System.exit(0);
+ }
+ IncidentHelper incidentHelper = new IncidentHelper(args);
+ try {
+ incidentHelper.run(args);
+ } catch (IllegalArgumentException e) {
+ showUsage(System.err);
+ System.err.println();
+ e.printStackTrace(System.err);
+ if (sLog) {
+ Log.e(TAG, "Error: ", e);
+ }
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ if (sLog) {
+ Log.e(TAG, "Error: ", e);
+ }
+ System.exit(1);
+ }
+ }
+}
diff --git a/cmds/incident_helper/java/com/android/commands/incident/Section.java b/cmds/incident_helper/java/com/android/commands/incident/Section.java
new file mode 100644
index 0000000..1c8c657
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/Section.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.incident;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/** Section interface used by {@link IncidentHelper}. */
+public interface Section {
+ /**
+ * Writes protobuf wire format to out, optionally reads data from in, with supplied args.
+ */
+ void run(InputStream in, OutputStream out, List<String> args) throws ExecutionException;
+}
diff --git a/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java b/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java
new file mode 100644
index 0000000..f9d2e79
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.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.commands.incident.sections;
+
+import android.util.Log;
+import android.util.PersistedLogProto;
+import android.util.TextLogEntry;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.commands.incident.ExecutionException;
+import com.android.commands.incident.IncidentHelper;
+import com.android.commands.incident.Section;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/** PersistLogSection reads persisted logs and parses them into a PersistedLogProto. */
+public class PersistLogSection implements Section {
+ private static final String TAG = "IH_PersistLog";
+ private static final String LOG_DIR = "/data/misc/logd/";
+ // Persist log files are named logcat, logcat.001, logcat.002, logcat.003, ...
+ private static final Pattern LOG_FILE_RE = Pattern.compile("logcat(\\.\\d+)?");
+ private static final Pattern BUFFER_BEGIN_RE =
+ Pattern.compile("--------- (?:beginning of|switch to) (.*)");
+ private static final Map<String, Long> SECTION_NAME_TO_ID = new HashMap<>();
+ private static final Map<Character, Integer> LOG_PRIORITY_MAP = new HashMap<>();
+ private static final String DEFAULT_BUFFER = "main";
+
+ static {
+ SECTION_NAME_TO_ID.put("main", PersistedLogProto.MAIN_LOGS);
+ SECTION_NAME_TO_ID.put("radio", PersistedLogProto.RADIO_LOGS);
+ SECTION_NAME_TO_ID.put("events", PersistedLogProto.EVENTS_LOGS);
+ SECTION_NAME_TO_ID.put("system", PersistedLogProto.SYSTEM_LOGS);
+ SECTION_NAME_TO_ID.put("crash", PersistedLogProto.CRASH_LOGS);
+ SECTION_NAME_TO_ID.put("kernel", PersistedLogProto.KERNEL_LOGS);
+ }
+
+ static {
+ LOG_PRIORITY_MAP.put('V', TextLogEntry.LOG_VERBOSE);
+ LOG_PRIORITY_MAP.put('D', TextLogEntry.LOG_DEBUG);
+ LOG_PRIORITY_MAP.put('I', TextLogEntry.LOG_INFO);
+ LOG_PRIORITY_MAP.put('W', TextLogEntry.LOG_WARN);
+ LOG_PRIORITY_MAP.put('E', TextLogEntry.LOG_ERROR);
+ LOG_PRIORITY_MAP.put('F', TextLogEntry.LOG_FATAL);
+ LOG_PRIORITY_MAP.put('S', TextLogEntry.LOG_SILENT);
+ }
+
+ /**
+ * Caches dates at 00:00:00 to epoch second elapsed conversion. There are only a few different
+ * dates in persisted logs in one device, and constructing DateTime object is relatively
+ * expensive.
+ */
+ private Map<Integer, Long> mEpochTimeCache = new HashMap<>();
+ private ProtoOutputStream mProto;
+ private long mCurrFieldId;
+ private long mMaxBytes = Long.MAX_VALUE;
+
+ @Override
+ public void run(InputStream in, OutputStream out, List<String> args) throws ExecutionException {
+ parseArgs(args);
+ Path logDirPath = Paths.get(LOG_DIR);
+ if (!Files.exists(logDirPath)) {
+ IncidentHelper.log(Log.WARN, TAG, "Skip dump. " + logDirPath + " does not exist.");
+ return;
+ }
+ if (!Files.isReadable(logDirPath)) {
+ IncidentHelper.log(Log.WARN, TAG, "Skip dump. " + logDirPath + " is not readable.");
+ return;
+ }
+ mProto = new ProtoOutputStream(out);
+ setCurrentSection(DEFAULT_BUFFER);
+ final Matcher logFileRe = LOG_FILE_RE.matcher("");
+ // Need to process older log files first and write logs to proto in chronological order
+ // But we want to process only the latest ones if there is a size limit
+ try (Stream<File> stream = Files.list(logDirPath).map(Path::toFile)
+ .filter(f -> !f.isDirectory() && match(logFileRe, f.getName()) != null)
+ .sorted(Comparator.comparingLong(File::lastModified).reversed())) {
+ Iterator<File> iter = stream.iterator();
+ List<File> filesToProcess = new ArrayList<>();
+ long sumBytes = 0;
+ while (iter.hasNext()) {
+ File file = iter.next();
+ sumBytes += file.length();
+ if (sumBytes > mMaxBytes) {
+ break;
+ }
+ filesToProcess.add(file);
+ }
+ IncidentHelper.log(Log.INFO, TAG, "Limit # log files to " + filesToProcess.size());
+ filesToProcess.stream()
+ .sorted(Comparator.comparingLong(File::lastModified))
+ .forEachOrdered(this::processFile);
+ } catch (IOException e) {
+ throw new ExecutionException(e);
+ } finally {
+ mProto.flush();
+ }
+ IncidentHelper.log(Log.DEBUG, TAG, "Bytes written: " + mProto.getBytes().length);
+ }
+
+ private void parseArgs(List<String> args) {
+ Iterator<String> iter = args.iterator();
+ while (iter.hasNext()) {
+ String arg = iter.next();
+ if ("--limit".equals(arg) && iter.hasNext()) {
+ String sizeStr = iter.next().toLowerCase();
+ if (sizeStr.endsWith("mb")) {
+ mMaxBytes = Long.parseLong(sizeStr.replace("mb", "")) * 1024 * 1024;
+ } else if (sizeStr.endsWith("kb")) {
+ mMaxBytes = Long.parseLong(sizeStr.replace("kb", "")) * 1024;
+ } else {
+ mMaxBytes = Long.parseLong(sizeStr);
+ }
+ } else {
+ throw new IllegalArgumentException("Unknown argument: " + arg);
+ }
+ }
+ }
+
+ private void processFile(File file) {
+ final Matcher bufferBeginRe = BUFFER_BEGIN_RE.matcher("");
+ try (BufferedReader reader = Files.newBufferedReader(file.toPath(),
+ StandardCharsets.UTF_8)) {
+ String line;
+ Matcher m;
+ while ((line = reader.readLine()) != null) {
+ if ((m = match(bufferBeginRe, line)) != null) {
+ setCurrentSection(m.group(1));
+ continue;
+ }
+ parseLine(line);
+ }
+ } catch (IOException e) {
+ // Non-fatal error. We can skip and still process other files.
+ IncidentHelper.log(Log.WARN, TAG, "Error reading \"" + file + "\": " + e.getMessage());
+ }
+ IncidentHelper.log(Log.DEBUG, TAG, "Finished reading " + file);
+ }
+
+ private void setCurrentSection(String sectionName) {
+ Long sectionId = SECTION_NAME_TO_ID.get(sectionName);
+ if (sectionId == null) {
+ IncidentHelper.log(Log.WARN, TAG, "Section does not exist: " + sectionName);
+ sectionId = SECTION_NAME_TO_ID.get(DEFAULT_BUFFER);
+ }
+ mCurrFieldId = sectionId;
+ }
+
+ /**
+ * Parse a log line in the following format:
+ * 01-01 15:01:47.723501 2738 2895 I Exp_TAG: example log line
+ *
+ * It does not use RegExp for performance reasons. Using this RegExp "(\\d{2})-(\\d{2})\\s
+ * (\\d{2}):(\\d{2}):(\\d{2}).(\\d{6})\\s+(\\d+)\\s+(\\d+)\\s+(.)\\s+(.*?):\\s(.*)" is twice as
+ * slow as the current approach.
+ */
+ private void parseLine(String line) {
+ long token = mProto.start(mCurrFieldId);
+ try {
+ mProto.write(TextLogEntry.SEC, getEpochSec(line));
+ // Nanosec is 15th to 20th digits of "10-01 02:57:27.710652" times 1000
+ mProto.write(TextLogEntry.NANOSEC, parseInt(line, 15, 21) * 1000L);
+
+ int start = nextNonBlank(line, 21);
+ int end = line.indexOf(' ', start + 1);
+ mProto.write(TextLogEntry.PID, parseInt(line, start, end));
+
+ start = nextNonBlank(line, end);
+ end = line.indexOf(' ', start + 1);
+ mProto.write(TextLogEntry.TID, parseInt(line, start, end));
+
+ start = nextNonBlank(line, end);
+ char priority = line.charAt(start);
+ mProto.write(TextLogEntry.PRIORITY,
+ LOG_PRIORITY_MAP.getOrDefault(priority, TextLogEntry.LOG_DEFAULT));
+
+ start = nextNonBlank(line, start + 1);
+ end = line.indexOf(": ", start);
+ mProto.write(TextLogEntry.TAG, line.substring(start, end).trim());
+ mProto.write(TextLogEntry.LOG, line.substring(Math.min(end + 2, line.length())));
+ } catch (RuntimeException e) {
+ // Error reporting is likely piped to /dev/null. Inserting it into the proto to make
+ // it more useful.
+ mProto.write(TextLogEntry.SEC, System.currentTimeMillis() / 1000);
+ mProto.write(TextLogEntry.PRIORITY, TextLogEntry.LOG_ERROR);
+ mProto.write(TextLogEntry.TAG, TAG);
+ mProto.write(TextLogEntry.LOG,
+ "Error parsing \"" + line + "\"" + ": " + e.getMessage());
+ }
+ mProto.end(token);
+ }
+
+ // ============== Below are util methods to parse log lines ==============
+
+ private static int nextNonBlank(String line, int start) {
+ for (int i = start; i < line.length(); i++) {
+ if (line.charAt(i) != ' ') {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Gets the epoch second from the line string. Line starts with a fixed-length timestamp like
+ * "10-01 02:57:27.710652"
+ */
+ private long getEpochSec(String line) {
+ int month = getDigit(line, 0) * 10 + getDigit(line, 1);
+ int day = getDigit(line, 3) * 10 + getDigit(line, 4);
+
+ int mmdd = month * 100 + day;
+ long epochSecBase = mEpochTimeCache.computeIfAbsent(mmdd, (key) -> {
+ final GregorianCalendar calendar = new GregorianCalendar();
+ calendar.set(Calendar.MONTH, (month + 12 - 1) % 12);
+ calendar.set(Calendar.DAY_OF_MONTH, day);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ // Date in log entries can never be in the future. If it happens, it means we are off
+ // by one year.
+ if (calendar.getTimeInMillis() > System.currentTimeMillis()) {
+ calendar.roll(Calendar.YEAR, /*amount=*/-1);
+ }
+ return calendar.getTimeInMillis() / 1000;
+ });
+
+ int hh = getDigit(line, 6) * 10 + getDigit(line, 7);
+ int mm = getDigit(line, 9) * 10 + getDigit(line, 10);
+ int ss = getDigit(line, 12) * 10 + getDigit(line, 13);
+ return epochSecBase + hh * 3600 + mm * 60 + ss;
+ }
+
+ private static int parseInt(String line, /*inclusive*/ int start, /*exclusive*/ int end) {
+ int num = 0;
+ for (int i = start; i < end; i++) {
+ num = num * 10 + getDigit(line, i);
+ }
+ return num;
+ }
+
+ private static int getDigit(String str, int pos) {
+ int digit = str.charAt(pos) - '0';
+ if (digit < 0 || digit > 9) {
+ throw new NumberFormatException("'" + str.charAt(pos) + "' is not a digit.");
+ }
+ return digit;
+ }
+
+ private static Matcher match(Matcher matcher, String text) {
+ matcher.reset(text);
+ return matcher.matches() ? matcher : null;
+ }
+}
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 1ca19c3..ada2f2d 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1312,6 +1312,13 @@
return Status::ok();
}
+Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) {
+ VLOG("StatsService::unregisterNativePullAtomCallback called.");
+ int32_t uid = IPCThreadState::self()->getCallingUid();
+ mPullerManager->UnregisterPullAtomCallback(uid, atomTag);
+ return Status::ok();
+}
+
Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn,
const int64_t trainVersionCodeIn,
const int options,
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index c9a9072..7990e5e 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -203,6 +203,11 @@
virtual Status unregisterPullAtomCallback(int32_t uid, int32_t atomTag) override;
/**
+ * Binder call to unregister any existing callback for the given atom and calling uid.
+ */
+ virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override;
+
+ /**
* Binder call to log BinaryPushStateChanged atom.
*/
virtual Status sendBinaryPushStateChangedAtom(
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index cbece78..4372e22 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -53,6 +53,7 @@
import "frameworks/base/core/proto/android/stats/mediaprovider/mediaprovider_enums.proto";
import "frameworks/base/core/proto/android/stats/storage/storage_enums.proto";
import "frameworks/base/core/proto/android/stats/style/style_enums.proto";
+import "frameworks/base/core/proto/android/stats/sysui/notification_enums.proto";
import "frameworks/base/core/proto/android/telecomm/enums.proto";
import "frameworks/base/core/proto/android/telephony/enums.proto";
import "frameworks/base/core/proto/android/view/enums.proto";
@@ -324,8 +325,6 @@
228 [(allow_from_any_uid) = true];
PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"];
VmsClientConnectionStateChanged vms_client_connection_state_changed = 230;
- GpsLocationStatusReported gps_location_status_reported = 231;
- GpsTimeToFirstFixReported gps_time_to_first_fix_reported = 232;
MediaProviderScanEvent media_provider_scan_event = 233 [(module) = "mediaprovider"];
MediaProviderDeletionEvent media_provider_deletion_event = 234 [(module) = "mediaprovider"];
MediaProviderPermissionEvent media_provider_permission_event =
@@ -339,10 +338,13 @@
BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
BootTimeEventErrorCode boot_time_event_error_code_reported = 242;
UserspaceRebootReported userspace_reboot_reported = 243;
+ NotificationReported notification_reported = 244;
+ NotificationPanelReported notification_panel_reported = 245;
+ NotificationChannelModified notification_panel_modified = 246;
}
// Pulled events will start at field 10000.
- // Next: 10070
+ // Next: 10071
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000;
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -399,7 +401,7 @@
ExternalStorageInfo external_storage_info = 10053;
GpuStatsGlobalInfo gpu_stats_global_info = 10054;
GpuStatsAppInfo gpu_stats_app_info = 10055;
- SystemIonHeapSize system_ion_heap_size = 10056;
+ SystemIonHeapSize system_ion_heap_size = 10056 [deprecated = true];
AppsOnExternalStorageInfo apps_on_external_storage_info = 10057;
FaceSettings face_settings = 10058;
CoolingDevice cooling_device = 10059;
@@ -413,6 +415,7 @@
DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067;
GraphicsStats graphics_stats = 10068;
RuntimeAppOpsAccess runtime_app_ops_access = 10069;
+ IonHeapSize ion_heap_size = 10070;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -737,27 +740,6 @@
optional android.server.location.GpsSignalQualityEnum level = 1;
}
-/**
- * Gps location status report
- *
- * Logged from:
- * /frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
- */
-message GpsLocationStatusReported {
- // Boolean stating if location was acquired
- optional bool location_success = 1;
-}
-
-/**
- * Gps log time to first fix report
- *
- * Logged from:
- * /frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
- */
-message GpsTimeToFirstFixReported {
- // int32 reporting the time to first fix in milliseconds
- optional int32 time_to_first_fix_millis = 1;
-}
/**
* Logs when a sync manager sync state changes.
@@ -3309,6 +3291,10 @@
* this button" or "this dialog was displayed".
* Keep the UI event stream clean: don't use for system or background events.
* Log using the UiEventLogger wrapper - don't write with the StatsLog API directly.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/
+ * frameworks/base/packages/SystemUI/src/com/android/systemui/
*/
message UiEventReported {
// The event_id.
@@ -3320,6 +3306,122 @@
}
/**
+ * Reports a notification was created or updated.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/notification/
+ */
+message NotificationReported {
+ // The event_id (as for UiEventReported).
+ optional int32 event_id = 1;
+ // The notifying app's uid and package.
+ optional int32 uid = 2 [(is_uid) = true];
+ optional string package_name = 3;
+ // A small system-assigned identifier for the notification.
+ // Locally probably-unique, but expect collisions across users and/or days.
+ optional int32 instance_id = 4;
+ // The app-assigned notification ID and tag
+ optional int32 notification_id = 5;
+ optional string notification_tag = 6;
+ optional string channel_id = 7; // App-assigned channel ID
+
+ // Grouping information
+ optional string group_id = 8; // Group the notification currently belongs to
+ optional int32 group_instance_id = 9; // Instance_id of the group-summary notification
+ optional bool is_group_summary = 10; // Tags the group-summary notification
+
+ // Attributes
+ optional string category = 11; // App-assigned notification category (API-defined strings)
+ optional int32 style = 12; // App-assigned notification style
+ optional int32 num_people = 13; // Number of Person records attached to the notification
+
+ // Ordering, importance and interruptiveness
+
+ optional int32 position = 14; // Position in NotificationManager's list
+
+ optional android.stats.sysui.NotificationImportance importance = 15;
+ optional int32 alerting = 16; // Bitfield, 1=buzz 2=beep 4=blink
+
+ enum NotificationImportanceExplanation {
+ IMPORTANCE_EXPLANATION_UNKNOWN = 0;
+ IMPORTANCE_EXPLANATION_APP = 1; // App-specified channel importance.
+ IMPORTANCE_EXPLANATION_USER = 2; // User-specified channel importance.
+ IMPORTANCE_EXPLANATION_ASST = 3; // Notification Assistant override.
+ IMPORTANCE_EXPLANATION_SYSTEM = 4; // System override.
+ // Like _APP, but based on pre-channels priority signal.
+ IMPORTANCE_EXPLANATION_APP_PRE_CHANNELS = 5;
+ }
+
+ optional NotificationImportanceExplanation importance_source = 17;
+ optional android.stats.sysui.NotificationImportance importance_initial = 18;
+ optional NotificationImportanceExplanation importance_initial_source = 19;
+ optional android.stats.sysui.NotificationImportance importance_asst = 20;
+ optional int32 assistant_hash = 21;
+ optional float assistant_ranking_score = 22;
+}
+
+message Notification {
+ // The notifying app's uid and package.
+ optional int32 uid = 1 [(is_uid) = true];
+ optional string package_name = 2;
+ // A small system-assigned identifier for the notification.
+ optional int32 instance_id = 3;
+
+ // Grouping information.
+ optional int32 group_instance_id = 4;
+ optional bool is_group_summary = 5;
+
+ // The section of the shade that the notification is in.
+ // See NotificationSectionsManager.PriorityBucket.
+ enum NotificationSection {
+ SECTION_UNKNOWN = 0;
+ SECTION_PEOPLE = 1;
+ SECTION_ALERTING = 2;
+ SECTION_SILENT = 3;
+ }
+ optional NotificationSection section = 6;
+}
+
+message NotificationList {
+ repeated Notification notifications = 1; // An ordered sequence of notifications.
+}
+
+/**
+ * Reports a notification panel was displayed, e.g. from the lockscreen or status bar.
+ *
+ * Logged from:
+ * frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/
+ */
+message NotificationPanelReported {
+ // The event_id (as for UiEventReported).
+ optional int32 event_id = 1;
+ optional int32 num_notifications = 2;
+ // The notifications in the panel, in the order that they appear there.
+ optional NotificationList notifications = 3 [(log_mode) = MODE_BYTES];
+}
+
+/**
+ * Reports a notification channel, or channel group, was created, updated, or deleted.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/notification/
+ */
+message NotificationChannelModified {
+ // The event_id (as for UiEventReported).
+ optional int32 event_id = 1;
+ // The notifying app's uid and package.
+ optional int32 uid = 2 [(is_uid) = true];
+ optional string package_name = 3;
+ // App-assigned notification channel ID or channel-group ID
+ optional string channel_id = 4;
+ // Previous importance setting, if applicable
+ optional android.stats.sysui.NotificationImportance old_importance = 5;
+ // New importance setting
+ optional android.stats.sysui.NotificationImportance importance = 6;
+}
+
+
+/**
* Logs when a biometric acquire event occurs.
*
* Logged from:
@@ -3507,12 +3609,14 @@
INSTALL_FAILURE_DOWNLOAD = 23;
INSTALL_FAILURE_STATE_MISMATCH = 24;
INSTALL_FAILURE_COMMIT = 25;
+ REBOOT_TRIGGERED = 26;
}
optional State state = 6;
// Possible experiment ids for monitoring this push.
optional TrainExperimentIds experiment_ids = 7 [(log_mode) = MODE_BYTES];
// user id
optional int32 user_id = 8;
+ optional int32 reason = 9;
}
/* Test atom, is not logged anywhere */
@@ -6804,6 +6908,7 @@
INSTALL_FAILURE_DOWNLOAD = 23;
INSTALL_FAILURE_STATE_MISMATCH = 24;
INSTALL_FAILURE_COMMIT = 25;
+ REBOOT_TRIGGERED = 26;
}
optional Status status = 4;
}
@@ -6946,11 +7051,27 @@
* Pulled from StatsCompanionService.
*/
message SystemIonHeapSize {
+ // Deprecated due to limited support of ion stats in debugfs.
+ // Use `IonHeapSize` instead.
+ option deprecated = true;
+
// Size of the system ion heap in bytes.
+ // Read from debugfs.
optional int64 size_in_bytes = 1;
}
/*
+ * Logs the total size of the ion heap.
+ *
+ * Pulled from StatsCompanionService.
+ */
+message IonHeapSize {
+ // Total size of all ion heaps in kilobytes.
+ // Read from: /sys/kernel/ion/total_heaps_kb.
+ optional int32 total_size_kb = 1;
+}
+
+/*
* Logs the per-process size of the system ion heap.
*
* Pulled from StatsCompanionService.
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp
index 0e6b677..e5a83a2 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -42,7 +42,7 @@
}
bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
- VLOG("StatsCallbackPuller called for tag %d", mTagId)
+ VLOG("StatsCallbackPuller called for tag %d", mTagId);
if(mCallback == nullptr) {
ALOGW("No callback registered");
return false;
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index b5920cb..591d727 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -68,14 +68,6 @@
{{.atomTag = android::util::ON_DEVICE_POWER_MEASUREMENT},
{.puller = new PowerStatsPuller()}},
- // 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)}},
-
// system_elapsed_realtime
{{.atomTag = android::util::SYSTEM_ELAPSED_REALTIME},
{.coolDownNs = NS_PER_SEC,
@@ -193,20 +185,6 @@
{.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},
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 070a4f8..d952be5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2539,7 +2539,8 @@
mCalled = true;
if (mAutoFillResetNeeded) {
- getAutofillManager().onInvisibleForAutofill();
+ // If stopped without changing the configurations, the response should expire.
+ getAutofillManager().onInvisibleForAutofill(!mChangingConfigurations);
} else if (mIntent != null
&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)
&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY)) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c3b07c8..2010cc9 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4064,6 +4064,7 @@
* @hide
*/
@SystemApi
+ @TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public boolean switchUser(@NonNull UserHandle user) {
if (user == null) {
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 70262b0..0c5e67c 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -464,6 +464,9 @@
|| appDir.equals(instrumentedAppDir)) {
outZipPaths.clear();
outZipPaths.add(instrumentationAppDir);
+ if (!instrumentationAppDir.equals(instrumentedAppDir)) {
+ outZipPaths.add(instrumentedAppDir);
+ }
// Only add splits if the app did not request isolated split loading.
if (!aInfo.requestsIsolatedSplitLoading()) {
@@ -472,7 +475,6 @@
}
if (!instrumentationAppDir.equals(instrumentedAppDir)) {
- outZipPaths.add(instrumentedAppDir);
if (instrumentedSplitAppDirs != null) {
Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 6a43465..5a4622e 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -107,6 +107,7 @@
private static final String ATT_ORIG_IMP = "orig_imp";
private static final String ATT_PARENT_CHANNEL = "parent";
private static final String ATT_CONVERSATION_ID = "conv_id";
+ private static final String ATT_DEMOTE = "dem";
private static final String DELIMITER = ",";
/**
@@ -196,6 +197,7 @@
private boolean mImportanceLockedDefaultApp;
private String mParentId = null;
private String mConversationId = null;
+ private boolean mDemoted = false;
/**
* Creates a notification channel.
@@ -262,6 +264,7 @@
mOriginalImportance = in.readInt();
mParentId = in.readString();
mConversationId = in.readString();
+ mDemoted = in.readBoolean();
}
@Override
@@ -319,6 +322,7 @@
dest.writeInt(mOriginalImportance);
dest.writeString(mParentId);
dest.writeString(mConversationId);
+ dest.writeBoolean(mDemoted);
}
/**
@@ -392,8 +396,6 @@
return input;
}
- // Modifiable by apps on channel creation.
-
/**
* @hide
*/
@@ -401,6 +403,8 @@
mId = id;
}
+ // Modifiable by apps on channel creation.
+
/**
* Sets what group this channel belongs to.
*
@@ -773,6 +777,20 @@
}
/**
+ * @hide
+ */
+ public void setDemoted(boolean demoted) {
+ mDemoted = demoted;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isDemoted() {
+ return mDemoted;
+ }
+
+ /**
* Returns whether the user has chosen the importance of this channel, either to affirm the
* initial selection from the app, or changed it to be higher or lower.
* @see #getImportance()
@@ -835,6 +853,7 @@
setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL),
parser.getAttributeValue(null, ATT_CONVERSATION_ID));
+ setDemoted(safeBool(parser, ATT_DEMOTE, false));
}
@Nullable
@@ -965,6 +984,9 @@
if (getConversationId() != null) {
out.attribute(null, ATT_CONVERSATION_ID, getConversationId());
}
+ if (isDemoted()) {
+ out.attribute(null, ATT_DEMOTE, Boolean.toString(isDemoted()));
+ }
// mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of
// truth and so aren't written to this xml file
@@ -1124,7 +1146,8 @@
&& mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp
&& mOriginalImportance == that.mOriginalImportance
&& Objects.equals(getParentChannelId(), that.getParentChannelId())
- && Objects.equals(getConversationId(), that.getConversationId());
+ && Objects.equals(getConversationId(), that.getConversationId())
+ && isDemoted() == that.isDemoted();
}
@Override
@@ -1135,7 +1158,7 @@
isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance,
- mParentId, mConversationId);
+ mParentId, mConversationId, mDemoted);
result = 31 * result + Arrays.hashCode(mVibration);
return result;
}
@@ -1182,7 +1205,8 @@
+ ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
+ ", mOriginalImp=" + mOriginalImportance
+ ", mParent=" + mParentId
- + ", mConversationId=" + mConversationId;
+ + ", mConversationId=" + mConversationId
+ + ", mDemoted=" + mDemoted;
}
/** @hide */
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c584575..be8e1d6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6850,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) {
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/content/Context.java b/core/java/android/content/Context.java
index 3860508..679de8a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1827,6 +1827,7 @@
*/
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
@SystemApi
+ @TestApi
public void startActivityAsUser(@RequiresPermission @NonNull Intent intent,
@NonNull UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c8f587f..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
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 5ca50c2..1f502a1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3379,6 +3379,10 @@
* etc. This library is versioned and backwards compatible. Clients
* should check its version via {@link android.ext.services.Version
* #getVersionCode()} and avoid calling APIs added in later versions.
+ * <p>
+ * This shared library no longer exists since Android R.
+ *
+ * @see #getServicesSystemSharedLibraryPackageName()
*
* @hide
*/
@@ -4745,6 +4749,9 @@
/**
* Get the name of the package hosting the services shared library.
+ * <p>
+ * Note that this package is no longer a shared library since Android R. It is now a package
+ * that hosts for a bunch of updatable services that the system binds to.
*
* @return The library host package.
*
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index a001ada..38d3137 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -65,6 +65,7 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
+import android.os.ext.SdkExtensions;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1615,11 +1616,72 @@
);
}
+ int type;
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (parser.getName().equals("extension-sdk")) {
+ final ParseResult result =
+ parseExtensionSdk(parseInput, parsingPackage, res, parser);
+ if (!result.isSuccess()) {
+ return result;
+ }
+ } else {
+ Slog.w(TAG, "Unknown element under <uses-sdk>: " + parser.getName()
+ + " at " + parsingPackage.getBaseCodePath() + " "
+ + parser.getPositionDescription());
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+
parsingPackage.setMinSdkVersion(minSdkVersion)
.setTargetSdkVersion(targetSdkVersion);
}
+ return parseInput.success(parsingPackage);
+ }
- XmlUtils.skipCurrentTag(parser);
+ private static ParseResult parseExtensionSdk(
+ ParseInput parseInput,
+ ParsingPackage parsingPackage,
+ Resources res,
+ XmlResourceParser parser
+ ) throws IOException, XmlPullParserException {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestExtensionSdk);
+ int sdkVersion = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestExtensionSdk_sdkVersion, -1);
+ int minVersion = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestExtensionSdk_minExtensionVersion,
+ -1);
+ sa.recycle();
+
+ if (sdkVersion < 0) {
+ return parseInput.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "<extension-sdk> must specify an sdkVersion >= 0");
+ }
+ if (minVersion < 0) {
+ return parseInput.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "<extension-sdk> must specify minExtensionVersion >= 0");
+ }
+
+ try {
+ int version = SdkExtensions.getExtensionVersion(sdkVersion);
+ if (version < minVersion) {
+ return parseInput.error(
+ PackageManager.INSTALL_FAILED_OLDER_SDK,
+ "Package requires " + sdkVersion + " extension version " + minVersion
+ + " which exceeds device version " + version);
+ }
+ } catch (RuntimeException e) {
+ return parseInput.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Specified sdkVersion " + sdkVersion + " is not valid");
+ }
return parseInput.success(parsingPackage);
}
diff --git a/core/java/android/content/pm/parsing/ComponentParseUtils.java b/core/java/android/content/pm/parsing/ComponentParseUtils.java
index 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/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a45648f..7bddc1d 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1539,10 +1539,15 @@
* <p><code>p' = Rp</code></p>
* <p>where <code>p</code> is in the device sensor coordinate system, and
* <code>p'</code> is in the camera-oriented coordinate system.</p>
+ * <p>If {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, the quaternion rotation cannot
+ * be accurately represented by the camera device, and will be represented by
+ * default values matching its default facing.</p>
* <p><b>Units</b>:
* Quaternion coefficients</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_REFERENCE
*/
@PublicKey
@NonNull
@@ -1577,6 +1582,8 @@
* <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is GYROSCOPE, then this position is relative to
* the center of the primary gyroscope on the device. The axis definitions are the same as
* with PRIMARY_CAMERA.</p>
+ * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, this position cannot be accurately
+ * represented by the camera device, and will be represented as <code>(0, 0, 0)</code>.</p>
* <p><b>Units</b>: Meters</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
@@ -1714,20 +1721,24 @@
new Key<float[]>("android.lens.radialDistortion", float[].class);
/**
- * <p>The origin for {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}.</p>
+ * <p>The origin for {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, and the accuracy of
+ * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} and {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p>
* <p>Different calibration methods and use cases can produce better or worse results
* depending on the selected coordinate origin.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #LENS_POSE_REFERENCE_PRIMARY_CAMERA PRIMARY_CAMERA}</li>
* <li>{@link #LENS_POSE_REFERENCE_GYROSCOPE GYROSCOPE}</li>
+ * <li>{@link #LENS_POSE_REFERENCE_UNDEFINED UNDEFINED}</li>
* </ul></p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
*
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
* @see CameraCharacteristics#LENS_POSE_TRANSLATION
* @see #LENS_POSE_REFERENCE_PRIMARY_CAMERA
* @see #LENS_POSE_REFERENCE_GYROSCOPE
+ * @see #LENS_POSE_REFERENCE_UNDEFINED
*/
@PublicKey
@NonNull
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index ec13a36..2377ccd 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -366,6 +366,20 @@
*/
public static final int LENS_POSE_REFERENCE_GYROSCOPE = 1;
+ /**
+ * <p>The camera device cannot represent the values of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}
+ * and {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} accurately enough. One such example is a camera device
+ * on the cover of a foldable phone: in order to measure the pose translation and rotation,
+ * some kind of hinge position sensor would be needed.</p>
+ * <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} must be all zeros, and
+ * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} must be values matching its default facing.</p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
+ * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+ * @see CameraCharacteristics#LENS_POSE_REFERENCE
+ */
+ public static final int LENS_POSE_REFERENCE_UNDEFINED = 2;
+
//
// Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
//
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 9b305b32..6f0d135 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3027,10 +3027,15 @@
* <p><code>p' = Rp</code></p>
* <p>where <code>p</code> is in the device sensor coordinate system, and
* <code>p'</code> is in the camera-oriented coordinate system.</p>
+ * <p>If {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, the quaternion rotation cannot
+ * be accurately represented by the camera device, and will be represented by
+ * default values matching its default facing.</p>
* <p><b>Units</b>:
* Quaternion coefficients</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_REFERENCE
*/
@PublicKey
@NonNull
@@ -3065,6 +3070,8 @@
* <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is GYROSCOPE, then this position is relative to
* the center of the primary gyroscope on the device. The axis definitions are the same as
* with PRIMARY_CAMERA.</p>
+ * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, this position cannot be accurately
+ * represented by the camera device, and will be represented as <code>(0, 0, 0)</code>.</p>
* <p><b>Units</b>: Meters</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 456ebf2..1932f46 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -34,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;
@@ -41,6 +42,7 @@
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;
@@ -1676,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
@@ -1697,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 03d29a3..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;
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/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 921f0f2..5cb3361 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -256,9 +256,13 @@
mService.send(msg);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to get status from installation service");
- mExecutor.execute(() -> {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e);
+ });
+ } else {
mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e);
- });
+ }
}
}
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index 4c92c28..cbf531c 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -106,9 +106,9 @@
* @return true if the call succeeds
*/
@RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
- public boolean startInstallation() {
+ public boolean startInstallation(String dsuSlot) {
try {
- return mService.startInstallation();
+ return mService.startInstallation(dsuSlot);
} catch (RemoteException e) {
throw new RuntimeException(e.toString());
}
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index 69cbab2..cc32f99 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -22,9 +22,10 @@
{
/**
* Start DynamicSystem installation.
+ * @param dsuSlot Name used to identify this installation
* @return true if the call succeeds
*/
- boolean startInstallation();
+ boolean startInstallation(@utf8InCpp String dsuSlot);
/**
* Create a DSU partition. This call may take 60~90 seconds. The caller
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 2583292..5a1ba7f 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -160,6 +160,7 @@
* Grant default permissions to currently active LUI app
* @param packageName The package name for the LUI app
* @param user The user handle
+ * @param executor The executor for the callback
* @param callback The callback provided by caller to be notified when grant completes
* @hide
*/
@@ -181,6 +182,7 @@
* Revoke default permissions to currently active LUI app
* @param packageNames The package names for the LUI apps
* @param user The user handle
+ * @param executor The executor for the callback
* @param callback The callback provided by caller to be notified when grant completes
* @hide
*/
@@ -198,6 +200,72 @@
}
}
+ /**
+ * Grant default permissions to currently active Ims services
+ * @param packageNames The package names for the Ims services
+ * @param user The user handle
+ * @param executor The executor for the callback
+ * @param callback The callback provided by caller to be notified when grant completes
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
+ public void grantDefaultPermissionsToEnabledImsServices(
+ @NonNull String[] packageNames, @NonNull UserHandle user,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ try {
+ mPermissionManager.grantDefaultPermissionsToEnabledImsServices(
+ packageNames, user.getIdentifier());
+ executor.execute(() -> callback.accept(true));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Grant default permissions to currently enabled telephony data services
+ * @param packageNames The package name for the services
+ * @param user The user handle
+ * @param executor The executor for the callback
+ * @param callback The callback provided by caller to be notified when grant completes
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
+ public void grantDefaultPermissionsToEnabledTelephonyDataServices(
+ @NonNull String[] packageNames, @NonNull UserHandle user,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ try {
+ mPermissionManager.grantDefaultPermissionsToEnabledTelephonyDataServices(
+ packageNames, user.getIdentifier());
+ executor.execute(() -> callback.accept(true));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Revoke default permissions to currently active telephony data services
+ * @param packageNames The package name for the services
+ * @param user The user handle
+ * @param executor The executor for the callback
+ * @param callback The callback provided by caller to be notified when revoke completes
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
+ public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(
+ @NonNull String[] packageNames, @NonNull UserHandle user,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ try {
+ mPermissionManager.revokeDefaultPermissionsFromDisabledTelephonyDataServices(
+ packageNames, user.getIdentifier());
+ executor.execute(() -> callback.accept(true));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
private List<SplitPermissionInfo> splitPermissionInfoListToNonParcelableList(
List<SplitPermissionInfoParcelable> parcelableList) {
final int size = parcelableList.size();
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 6650cf2..53f4615 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -424,6 +424,7 @@
* @hide
*/
@SystemApi
+ @TestApi
@NonNull
@RequiresPermission(READ_DEVICE_CONFIG)
public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) {
@@ -593,6 +594,7 @@
* @hide
*/
@SystemApi
+ @TestApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static boolean setProperties(@NonNull Properties properties) throws BadConfigException {
ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
@@ -817,6 +819,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static class BadConfigException extends Exception {}
/**
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index ef8a286..1453608 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -363,7 +363,7 @@
* <p>
* Type: INTEGER (int)
*
- * @see #FLAG_DIR_BLOCKS_TREE
+ * @see #FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
* @see #FLAG_DIR_PREFERS_GRID
* @see #FLAG_DIR_PREFERS_LAST_MODIFIED
* @see #FLAG_DIR_SUPPORTS_CREATE
@@ -567,7 +567,7 @@
* @see Intent#ACTION_OPEN_DOCUMENT_TREE
* @see #COLUMN_FLAGS
*/
- public static final int FLAG_DIR_BLOCKS_TREE = 1 << 15;
+ public static final int FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE = 1 << 15;
}
/**
diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java
index 298628e..8fc13b7 100644
--- a/core/java/android/provider/SearchIndexablesContract.java
+++ b/core/java/android/provider/SearchIndexablesContract.java
@@ -98,16 +98,14 @@
/**
- * Dynamic indexable raw data names.
- *
- * @hide
+ * The raw data name of dynamic index. This is used to compose the index path of provider
+ * for dynamic index.
*/
public static final String DYNAMIC_INDEXABLES_RAW = "dynamic_indexables_raw";
/**
- * ContentProvider path for dynamic indexable raw data.
- *
- * @hide
+ * ContentProvider path for dynamic index. This is used to get the raw data of dynamic index
+ * from provider.
*/
public static final String DYNAMIC_INDEXABLES_RAW_PATH =
SETTINGS + "/" + DYNAMIC_INDEXABLES_RAW;
diff --git a/core/java/android/provider/SearchIndexablesProvider.java b/core/java/android/provider/SearchIndexablesProvider.java
index 68284b4..f4d0cb4 100644
--- a/core/java/android/provider/SearchIndexablesProvider.java
+++ b/core/java/android/provider/SearchIndexablesProvider.java
@@ -204,11 +204,9 @@
* @param projection list of {@link android.provider.SearchIndexablesContract.RawData} columns
* to put into the cursor. If {@code null} all supported columns should be
* included.
- *
- * @hide
*/
@Nullable
- public Cursor queryDynamicRawData(String[] projection) {
+ public Cursor queryDynamicRawData(@Nullable String[] projection) {
// By default no-op;
return null;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e2b33e0..f663320 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -95,6 +95,7 @@
* The Settings provider contains global system-level device preferences.
*/
public final class Settings {
+ private static final boolean DEFAULT_OVERRIDEABLE_BY_RESTORE = false;
// Intent actions for Settings
@@ -2149,6 +2150,11 @@
*/
public static final String CALL_METHOD_FLAGS_KEY = "_flags";
+ /**
+ * @hide - String argument extra to the fast-path call()-based requests
+ */
+ public static final String CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY = "_overrideable_by_restore";
+
/** @hide - Private call() method to write to 'system' table */
public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
@@ -2517,7 +2523,8 @@
}
public boolean putStringForUser(ContentResolver cr, String name, String value,
- String tag, boolean makeDefault, final int userHandle) {
+ String tag, boolean makeDefault, final int userHandle,
+ boolean overrideableByRestore) {
try {
Bundle arg = new Bundle();
arg.putString(Settings.NameValueTable.VALUE, value);
@@ -2528,6 +2535,9 @@
if (makeDefault) {
arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
+ if (overrideableByRestore) {
+ arg.putBoolean(CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true);
+ }
IContentProvider cp = mProviderHolder.getProvider(cr);
cp.call(cr.getPackageName(), cr.getFeatureId(),
mProviderHolder.mUri.getAuthority(), mCallSetCommand, name, arg);
@@ -2736,6 +2746,8 @@
public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
List<String> names) {
+ String namespace = prefix.substring(0, prefix.length() - 1);
+ DeviceConfig.enforceReadPermission(ActivityThread.currentApplication(), namespace);
ArrayMap<String, String> keyValues = new ArrayMap<>();
int currentGeneration = -1;
@@ -3078,10 +3090,36 @@
return putStringForUser(resolver, name, value, resolver.getUserId());
}
+ /**
+ * Store a name/value pair into the database. Values written by this method will be
+ * overridden if a restore happens in the future.
+ *
+ * @param resolver to access the database with
+ * @param name to store
+ * @param value to associate with the name
+ *
+ * @return true if the value was set, false on database errors
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE)
+ @SystemApi
+ public static boolean putString(@NonNull ContentResolver resolver,
+ @NonNull String name, @Nullable String value, boolean overrideableByRestore) {
+ return putStringForUser(resolver, name, value, resolver.getUserId(),
+ overrideableByRestore);
+ }
+
/** @hide */
@UnsupportedAppUsage
public static boolean putStringForUser(ContentResolver resolver, String name, String value,
int userHandle) {
+ return putStringForUser(resolver, name, value, userHandle,
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
+ }
+
+ private static boolean putStringForUser(ContentResolver resolver, String name, String value,
+ int userHandle, boolean overrideableByRestore) {
if (MOVED_TO_SECURE.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ " to android.provider.Settings.Secure, value is unchanged.");
@@ -3092,7 +3130,8 @@
+ " to android.provider.Settings.Global, value is unchanged.");
return false;
}
- return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle);
+ return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle,
+ overrideableByRestore);
}
/**
@@ -3416,7 +3455,7 @@
// need to store the adjusted configuration as the initial settings.
Settings.System.putStringForUser(
cr, SYSTEM_LOCALES, outConfig.getLocales().toLanguageTags(),
- userHandle);
+ userHandle, DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
}
}
@@ -3449,7 +3488,8 @@
int userHandle) {
return Settings.System.putFloatForUser(cr, FONT_SCALE, config.fontScale, userHandle) &&
Settings.System.putStringForUser(
- cr, SYSTEM_LOCALES, config.getLocales().toLanguageTags(), userHandle);
+ cr, SYSTEM_LOCALES, config.getLocales().toLanguageTags(), userHandle,
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/** @hide */
@@ -5137,7 +5177,6 @@
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_P2P_DEVICE_NAME);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SAVED_STATE);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS);
- MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_ENHANCED_AUTO_JOIN);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NETWORK_SHOW_RSSI);
@@ -5252,6 +5291,24 @@
}
/**
+ * Store a name/value pair into the database. Values written by this method will be
+ * overridden if a restore happens in the future.
+ *
+ * @param resolver to access the database with
+ * @param name to store
+ * @param value to associate with the name
+ * @return true if the value was set, false on database errors
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE)
+ public static boolean putString(ContentResolver resolver, String name,
+ String value, boolean overrideableByRestore) {
+ return putStringForUser(resolver, name, value, /* tag */ null, /* makeDefault */ false,
+ resolver.getUserId(), overrideableByRestore);
+ }
+
+ /**
* Store a name/value pair into the database.
* @param resolver to access the database with
* @param name to store
@@ -5266,22 +5323,23 @@
@UnsupportedAppUsage
public static boolean putStringForUser(ContentResolver resolver, String name, String value,
int userHandle) {
- return putStringForUser(resolver, name, value, null, false, userHandle);
+ return putStringForUser(resolver, name, value, null, false, userHandle,
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/** @hide */
@UnsupportedAppUsage
public static boolean putStringForUser(@NonNull ContentResolver resolver,
@NonNull String name, @Nullable String value, @Nullable String tag,
- boolean makeDefault, @UserIdInt int userHandle) {
+ boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore) {
if (MOVED_TO_GLOBAL.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
+ " to android.provider.Settings.Global");
return Global.putStringForUser(resolver, name, value,
- tag, makeDefault, userHandle);
+ tag, makeDefault, userHandle, DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
return sNameValueCache.putStringForUser(resolver, name, value, tag,
- makeDefault, userHandle);
+ makeDefault, userHandle, overrideableByRestore);
}
/**
@@ -5330,7 +5388,7 @@
@NonNull String name, @Nullable String value, @Nullable String tag,
boolean makeDefault) {
return putStringForUser(resolver, name, value, tag, makeDefault,
- resolver.getUserId());
+ resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/**
@@ -8820,9 +8878,9 @@
* added to both AIRPLANE_MODE_RADIOS and AIRPLANE_MODE_TOGGLEABLE_RADIOS, then Wifi
* will be turned off when entering airplane mode, but the user will be able to reenable
* Wifi in the Settings app.
- *
- * {@hide}
+ * @hide
*/
+ @SystemApi
public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
/**
@@ -9994,24 +10052,17 @@
* Setting to allow scans to be enabled even wifi is turned off for connectivity.
* @hide
*/
+ @SystemApi
public static final String WIFI_SCAN_ALWAYS_AVAILABLE =
"wifi_scan_always_enabled";
/**
- * The interval in milliseconds at which wifi rtt ranging requests will be throttled when
- * they are coming from the background.
- *
- * @hide
- */
- public static final String WIFI_RTT_BACKGROUND_EXEC_GAP_MS =
- "wifi_rtt_background_exec_gap_ms";
-
- /**
* Indicate whether factory reset request is pending.
*
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String WIFI_P2P_PENDING_FACTORY_RESET =
"wifi_p2p_pending_factory_reset";
@@ -10021,6 +10072,7 @@
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
/**
@@ -10064,10 +10116,10 @@
* enabled state.
* @hide
*/
+ @SystemApi
public static final String NETWORK_RECOMMENDATIONS_ENABLED =
"network_recommendations_enabled";
-
/**
* Which package name to use for network recommendations. If null, network recommendations
* will neither be requested nor accepted.
@@ -10092,17 +10144,6 @@
public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
/**
- * The number of milliseconds the {@link com.android.server.NetworkScoreService}
- * will give a recommendation request to complete before returning a default response.
- *
- * Type: long
- * @hide
- * @deprecated to be removed
- */
- public static final String NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS =
- "network_recommendation_request_timeout_ms";
-
- /**
* The expiration time in milliseconds for the {@link android.net.WifiKey} request cache in
* {@link com.android.server.wifi.RecommendedNetworkEvaluator}.
*
@@ -10120,6 +10161,7 @@
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
/**
@@ -10228,18 +10270,11 @@
"wifi_watchdog_poor_network_test_enabled";
/**
- * Setting to turn on suspend optimizations at screen off on Wi-Fi. Enabled by default and
- * needs to be set to 0 to disable it.
- * @hide
- */
- public static final String WIFI_SUSPEND_OPTIMIZATIONS_ENABLED =
- "wifi_suspend_optimizations_enabled";
-
- /**
* Setting to enable verbose logging in Wi-Fi; disabled by default, and setting to 1
* will enable it. In the future, additional values may be supported.
* @hide
*/
+ @SystemApi
public static final String WIFI_VERBOSE_LOGGING_ENABLED =
"wifi_verbose_logging_enabled";
@@ -10265,69 +10300,10 @@
* Errors in the parameters will cause the entire setting to be ignored.
* @hide
*/
+ @SystemApi
public static final String WIFI_SCORE_PARAMS =
"wifi_score_params";
- /**
- * Setting to enable logging WifiIsUnusableEvent in metrics
- * which gets triggered when wifi becomes unusable.
- * Disabled by default, and setting it to 1 will enable it.
- * @hide
- */
- public static final String WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED =
- "wifi_is_unusable_event_metrics_enabled";
-
- /**
- * The minimum number of txBad the framework has to observe
- * to trigger a wifi data stall.
- * @hide
- */
- public static final String WIFI_DATA_STALL_MIN_TX_BAD =
- "wifi_data_stall_min_tx_bad";
-
- /**
- * The minimum number of txSuccess the framework has to observe
- * to trigger a wifi data stall when rxSuccess is 0.
- * @hide
- */
- public static final String WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX =
- "wifi_data_stall_min_tx_success_without_rx";
-
- /**
- * Setting to enable logging Wifi LinkSpeedCounts in metrics.
- * Disabled by default, and setting it to 1 will enable it.
- * @hide
- */
- public static final String WIFI_LINK_SPEED_METRICS_ENABLED =
- "wifi_link_speed_metrics_enabled";
-
- /**
- * Setting to enable the PNO frequency culling optimization.
- * Disabled by default, and setting it to 1 will enable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_PNO_FREQUENCY_CULLING_ENABLED =
- "wifi_pno_frequency_culling_enabled";
-
- /**
- * Setting to enable including recency information when determining pno network priorities.
- * Disabled by default, and setting it to 1 will enable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_PNO_RECENCY_SORTING_ENABLED =
- "wifi_pno_recency_sorting_enabled";
-
- /**
- * Setting to enable the Wi-Fi link probing.
- * Enabled by default, and setting it to 0 will disable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_LINK_PROBING_ENABLED =
- "wifi_link_probing_enabled";
-
/**
* The maximum number of times we will retry a connection to an access
* point for which we have failed in acquiring an IP address from DHCP.
@@ -10367,6 +10343,7 @@
* The Wi-Fi peer-to-peer device name
* @hide
*/
+ @SystemApi
public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
/**
@@ -13022,7 +12999,29 @@
*/
public static boolean putString(ContentResolver resolver,
String name, String value) {
- return putStringForUser(resolver, name, value, null, false, resolver.getUserId());
+ return putStringForUser(resolver, name, value, null, false, resolver.getUserId(),
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
+ }
+
+ /**
+ * Store a name/value pair into the database.
+ *
+ * @param resolver to access the database with
+ * @param name to store
+ * @param value to associate with the name
+ * @param tag to associated with the setting.
+ * @param makeDefault whether to make the value the default one.
+ * @param overrideableByRestore whether restore can override this value
+ * @return true if the value was set, false on database errors
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE)
+ public static boolean putString(@NonNull ContentResolver resolver,
+ @NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault, boolean overrideableByRestore) {
+ return putStringForUser(resolver, name, value, tag, makeDefault,
+ resolver.getUserId(), overrideableByRestore);
}
/**
@@ -13071,7 +13070,7 @@
@NonNull String name, @Nullable String value, @Nullable String tag,
boolean makeDefault) {
return putStringForUser(resolver, name, value, tag, makeDefault,
- resolver.getUserId());
+ resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/**
@@ -13133,13 +13132,14 @@
@UnsupportedAppUsage
public static boolean putStringForUser(ContentResolver resolver,
String name, String value, int userHandle) {
- return putStringForUser(resolver, name, value, null, false, userHandle);
+ return putStringForUser(resolver, name, value, null, false, userHandle,
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/** @hide */
public static boolean putStringForUser(@NonNull ContentResolver resolver,
@NonNull String name, @Nullable String value, @Nullable String tag,
- boolean makeDefault, @UserIdInt int userHandle) {
+ boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore) {
if (LOCAL_LOGV) {
Log.v(TAG, "Global.putString(name=" + name + ", value=" + value
+ " for " + userHandle);
@@ -13149,10 +13149,10 @@
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global"
+ " to android.provider.Settings.Secure, value is unchanged.");
return Secure.putStringForUser(resolver, name, value, tag,
- makeDefault, userHandle);
+ makeDefault, userHandle, overrideableByRestore);
}
return sNameValueCache.putStringForUser(resolver, name, value, tag,
- makeDefault, userHandle);
+ makeDefault, userHandle, overrideableByRestore);
}
/**
@@ -14019,7 +14019,8 @@
static boolean putString(@NonNull ContentResolver resolver, @NonNull String namespace,
@NonNull String name, @Nullable String value, boolean makeDefault) {
return sNameValueCache.putStringForUser(resolver, createCompositeName(namespace, name),
- value, null, makeDefault, resolver.getUserId());
+ value, null, makeDefault, resolver.getUserId(),
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/**
diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
index 619c507..9950143 100644
--- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
@@ -24,6 +24,7 @@
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -48,7 +49,8 @@
* <p>To extend this class, you must declare the service in your manifest file with the
* {@link android.Manifest.permission#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} permission,
* and include an intent filter with the {@link #SERVICE_INTERFACE} action. In adddition,
- * your implementation must live in {@link PackageManger#SYSTEM_SHARED_LIBRARY_SERVICES}.
+ * your implementation must live in
+ * {@link PackageManager#getServicesSystemSharedLibraryPackageName()}.
* For example:</p>
* <pre>
* <service android:name=".FooExplicitHealthCheckService"
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e08a06a..a4fe6aa 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
@@ -974,7 +985,21 @@
* TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
*/
public void onRegistrationFailed(@NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
- @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) {
+ int domain, int causeCode, int additionalCauseCode) {
+ // default implementation empty
+ }
+
+ /**
+ * Report updated barring information for the current camped/registered cell.
+ *
+ * <p>Barring info is provided for all services applicable to the current camped/registered
+ * cell, for the registered PLMN and current access class/access category.
+ *
+ * @param barringInfo for all services on the current cell.
+ *
+ * @see android.telephony.BarringInfo
+ */
+ public void onBarringInfoChanged(@NonNull BarringInfo barringInfo) {
// default implementation empty
}
@@ -1252,7 +1277,7 @@
}
public void onRegistrationFailed(@NonNull CellIdentity cellIdentity,
- @NonNull String chosenPlmn, @NetworkRegistrationInfo.Domain int domain,
+ @NonNull String chosenPlmn, int domain,
int causeCode, int additionalCauseCode) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
@@ -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..e25826c 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -701,11 +701,28 @@
*/
public void notifyRegistrationFailed(int slotIndex, int subId,
@NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
- @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) {
+ int domain, int causeCode, int additionalCauseCode) {
try {
sRegistry.notifyRegistrationFailed(slotIndex, subId, cellIdentity,
chosenPlmn, domain, causeCode, additionalCauseCode);
} catch (RemoteException ex) {
}
}
+
+ /**
+ * Notify {@link BarringInfo} has changed for a specific subscription.
+ *
+ * @param slotIndex for the phone object that got updated barring info.
+ * @param subId for which the BarringInfo changed.
+ * @param barringInfo updated BarringInfo.
+ */
+ public void notifyBarringInfoChanged(
+ int slotIndex, int subId, @NonNull BarringInfo barringInfo) {
+ try {
+ sRegistry.notifyBarringInfoChanged(slotIndex, subId, barringInfo);
+ } catch (RemoteException ex) {
+ // system server crash
+ }
+ }
+
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 9d22d30..eb4af1c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -59,10 +59,9 @@
DEFAULT_FLAGS.put("settings_wifi_details_datausage_header", "false");
DEFAULT_FLAGS.put("settings_skip_direction_mutable", "true");
DEFAULT_FLAGS.put(SETTINGS_WIFITRACKER2, "false");
- DEFAULT_FLAGS.put("settings_work_profile", "true");
DEFAULT_FLAGS.put("settings_controller_loading_enhancement", "false");
DEFAULT_FLAGS.put("settings_conditionals", "false");
- DEFAULT_FLAGS.put(NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false");
+ DEFAULT_FLAGS.put(NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true");
}
/**
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index f324113..9921bf0 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.DeadSystemException;
@@ -400,7 +401,7 @@
* @param message The message you would like logged.
* @hide
*/
- // @SystemApi(client= SystemApi.Client.MODULE_LIBRARIES) // TODO Uncomment once http://ag/9956147 is in.
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static int logToRadioBuffer(@Level int priority, @Nullable String tag,
@Nullable String message) {
return println_native(LOG_ID_RADIO, priority, tag, message);
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/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
new file mode 100644
index 0000000..5c494c1
--- /dev/null
+++ b/core/java/android/view/ImeFocusController.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+
+/**
+ * Responsible for IME focus handling inside {@link ViewRootImpl}.
+ * @hide
+ */
+public final class ImeFocusController {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "ImeFocusController";
+
+ private final ViewRootImpl mViewRootImpl;
+ private boolean mHasImeFocus = false;
+ private View mServedView;
+ private View mNextServedView;
+
+ @UiThread
+ ImeFocusController(@NonNull ViewRootImpl viewRootImpl) {
+ mViewRootImpl = viewRootImpl;
+ }
+
+ private InputMethodManagerDelegate getImmDelegate() {
+ return mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate();
+ }
+
+ @UiThread
+ void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
+ final boolean hasImeFocus = updateImeFocusable(windowAttribute, false /* force */);
+ if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) {
+ return;
+ }
+ if (hasImeFocus == mHasImeFocus) {
+ return;
+ }
+ mHasImeFocus = hasImeFocus;
+ if (mHasImeFocus) {
+ onPreWindowFocus(true /* hasWindowFocus */, windowAttribute);
+ onPostWindowFocus(mViewRootImpl.mView.findFocus(), true /* hasWindowFocus */,
+ windowAttribute);
+ }
+ }
+
+ @UiThread
+ void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
+ if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+ return;
+ }
+ if (hasWindowFocus) {
+ getImmDelegate().setCurrentRootView(mViewRootImpl);
+ }
+ }
+
+ @UiThread
+ boolean updateImeFocusable(WindowManager.LayoutParams windowAttribute, boolean force) {
+ final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
+ windowAttribute.flags);
+ if (force) {
+ mHasImeFocus = hasImeFocus;
+ }
+ return hasImeFocus;
+ }
+
+ @UiThread
+ void onPostWindowFocus(View focusedView, boolean hasWindowFocus,
+ WindowManager.LayoutParams windowAttribute) {
+ if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "onWindowFocus: " + focusedView
+ + " softInputMode=" + InputMethodDebug.softInputModeToString(
+ windowAttribute.softInputMode));
+ }
+
+ boolean forceFocus = false;
+ if (getImmDelegate().isRestartOnNextWindowFocus(true /* reset */)) {
+ if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true");
+ forceFocus = true;
+ }
+ // Update mNextServedView when focusedView changed.
+ final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
+ onViewFocusChanged(viewForWindowFocus, true);
+
+ getImmDelegate().startInputAsyncOnWindowFocusGain(viewForWindowFocus,
+ windowAttribute.softInputMode, windowAttribute.flags, forceFocus);
+ }
+
+ public boolean checkFocus(boolean forceNewFocus, boolean startInput) {
+ if (!getImmDelegate().isCurrentRootView(mViewRootImpl)
+ || (mServedView == mNextServedView && !forceNewFocus)) {
+ return false;
+ }
+ if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
+ + " next=" + mNextServedView
+ + " force=" + forceNewFocus
+ + " package="
+ + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));
+
+ // Close the connection when no next served view coming.
+ if (mNextServedView == null) {
+ getImmDelegate().finishInput();
+ getImmDelegate().closeCurrentIme();
+ return false;
+ }
+ mServedView = mNextServedView;
+ getImmDelegate().finishComposingText();
+
+ if (startInput) {
+ getImmDelegate().startInput(StartInputReason.CHECK_FOCUS, null, 0, 0, 0);
+ }
+ return true;
+ }
+
+ @UiThread
+ void onViewFocusChanged(View view, boolean hasFocus) {
+ if (view == null || view.isTemporarilyDetached()) {
+ return;
+ }
+ if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
+ return;
+ }
+ if (mServedView == view || !view.hasImeFocus() || !view.hasWindowFocus()) {
+ return;
+ }
+ mNextServedView = hasFocus ? view : null;
+ mViewRootImpl.dispatchCheckFocus();
+ }
+
+ @UiThread
+ void onViewDetachedFromWindow(View view) {
+ if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
+ return;
+ }
+ if (mServedView == view) {
+ mNextServedView = null;
+ mViewRootImpl.dispatchCheckFocus();
+ }
+ }
+
+ @UiThread
+ void onWindowDismissed() {
+ if (!getImmDelegate().isCurrentRootView(mViewRootImpl)) {
+ return;
+ }
+ if (mServedView != null) {
+ getImmDelegate().finishInput();
+ }
+ getImmDelegate().setCurrentRootView(null);
+ mHasImeFocus = false;
+ }
+
+ /**
+ * @param windowAttribute {@link WindowManager.LayoutParams} to be checked.
+ * @return Whether the window is in local focus mode or not.
+ */
+ @AnyThread
+ private static boolean isInLocalFocusMode(WindowManager.LayoutParams windowAttribute) {
+ return (windowAttribute.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
+ }
+
+ int onProcessImeInputStage(Object token, InputEvent event,
+ WindowManager.LayoutParams windowAttribute,
+ InputMethodManager.FinishedInputEventCallback callback) {
+ if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+ return InputMethodManager.DISPATCH_NOT_HANDLED;
+ }
+ final InputMethodManager imm =
+ mViewRootImpl.mContext.getSystemService(InputMethodManager.class);
+ if (imm == null) {
+ return InputMethodManager.DISPATCH_NOT_HANDLED;
+ }
+ return imm.dispatchInputEvent(event, token, callback, mViewRootImpl.mHandler);
+ }
+
+ /**
+ * A delegate implementing some basic {@link InputMethodManager} APIs.
+ * @hide
+ */
+ public interface InputMethodManagerDelegate {
+ boolean startInput(@StartInputReason int startInputReason, View focusedView,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags);
+ void startInputAsyncOnWindowFocusGain(View rootView,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
+ boolean forceNewFocus);
+ void finishInput();
+ void closeCurrentIme();
+ void finishComposingText();
+ void setCurrentRootView(ViewRootImpl rootView);
+ boolean isCurrentRootView(ViewRootImpl rootView);
+ boolean isRestartOnNextWindowFocus(boolean reset);
+ }
+
+ public View getServedView() {
+ return mServedView;
+ }
+
+ public View getNextServedView() {
+ return mNextServedView;
+ }
+
+ public void setServedView(View view) {
+ mServedView = view;
+ }
+
+ public void setNextServedView(View view) {
+ mNextServedView = view;
+ }
+}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 6589e75..69d0105 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -34,6 +34,7 @@
import android.util.SparseSetArray;
import android.view.InsetsController.LayoutInsetsDuringAnimation;
import android.view.InsetsState.InternalInsetsSide;
+import android.view.InsetsState.InternalInsetsType;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimationCallback.AnimationBounds;
@@ -92,6 +93,7 @@
mController = controller;
mInitialInsetsState = new InsetsState(state, true /* copySources */);
mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
+ mPendingInsets = mCurrentInsets;
mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */,
null /* typeSideMap */);
mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
@@ -131,6 +133,10 @@
return mTypes;
}
+ boolean controlsInternalType(@InternalInsetsType int type) {
+ return InsetsState.toInternalType(mTypes).contains(type);
+ }
+
@Override
public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
if (mFinished) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 775490c..e2739c4 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -28,6 +28,7 @@
import android.annotation.NonNull;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.net.InvalidPacketException.ErrorCode;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Log;
@@ -61,15 +62,9 @@
private static final int ANIMATION_DURATION_SHOW_MS = 275;
private static final int ANIMATION_DURATION_HIDE_MS = 340;
- private static final int DIRECTION_NONE = 0;
- private static final int DIRECTION_SHOW = 1;
- private static final int DIRECTION_HIDE = 2;
static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
- @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
- private @interface AnimationDirection{}
-
/**
* Layout mode during insets animation: The views should be laid out as if the changing inset
* types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will
@@ -101,6 +96,28 @@
@interface LayoutInsetsDuringAnimation {
}
+ /** Not running an animation. */
+ @VisibleForTesting
+ public static final int ANIMATION_TYPE_NONE = -1;
+
+ /** Running animation will show insets */
+ @VisibleForTesting
+ public static final int ANIMATION_TYPE_SHOW = 0;
+
+ /** Running animation will hide insets */
+ @VisibleForTesting
+ public static final int ANIMATION_TYPE_HIDE = 1;
+
+ /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
+ @VisibleForTesting
+ public static final int ANIMATION_TYPE_USER = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE,
+ ANIMATION_TYPE_USER})
+ @interface AnimationType {
+ }
+
/**
* Translation animation evaluator.
*/
@@ -145,7 +162,6 @@
public void onReady(WindowInsetsAnimationController controller, int types) {
mController = controller;
- mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE;
mAnimator = ObjectAnimator.ofObject(
controller,
new InsetsProperty(),
@@ -176,7 +192,6 @@
}
private void onAnimationFinish() {
- mAnimationDirection = DIRECTION_NONE;
mController.finish(mShow);
}
@@ -193,6 +208,20 @@
}
}
+ /**
+ * Represents a running animation
+ */
+ private static class RunningAnimation {
+
+ RunningAnimation(InsetsAnimationControlImpl control, int type) {
+ this.control = control;
+ this.type = type;
+ }
+
+ final InsetsAnimationControlImpl control;
+ final @AnimationType int type;
+ }
+
private final String TAG = "InsetsControllerImpl";
private final InsetsState mState = new InsetsState();
@@ -203,7 +232,7 @@
private final ViewRootImpl mViewRoot;
private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
- private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>();
+ private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>();
private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>();
private WindowInsets mLastInsets;
@@ -213,7 +242,6 @@
private final Rect mLastLegacyContentInsets = new Rect();
private final Rect mLastLegacyStableInsets = new Rect();
- private @AnimationDirection int mAnimationDirection;
private int mPendingTypesToShow;
@@ -226,7 +254,7 @@
mViewRoot = viewRoot;
mAnimCallback = () -> {
mAnimCallbackScheduled = false;
- if (mAnimationControls.isEmpty()) {
+ if (mRunningAnimations.isEmpty()) {
return;
}
if (mViewRoot.mView == null) {
@@ -236,9 +264,9 @@
mTmpFinishedControls.clear();
InsetsState state = new InsetsState(mState, true /* copySources */);
- for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
- InsetsAnimationControlImpl control = mAnimationControls.get(i);
- if (mAnimationControls.get(i).applyChangeInsets(state)) {
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
+ if (control.applyChangeInsets(state)) {
mTmpFinishedControls.add(control);
}
}
@@ -349,18 +377,13 @@
int typesReady = 0;
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
- InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
- if (mAnimationDirection == DIRECTION_HIDE) {
- // Only one animator (with multiple InsetsType) can run at a time.
- // previous one should be cancelled for simplicity.
- cancelExistingAnimation();
- } else if (consumer.isRequestedVisible()
- && (mAnimationDirection == DIRECTION_NONE
- || mAnimationDirection == DIRECTION_HIDE)) {
+ @InternalInsetsType int internalType = internalTypes.valueAt(i);
+ @AnimationType int animationType = getAnimationType(internalType);
+ InsetsSourceConsumer consumer = getSourceConsumer(internalType);
+ if (mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE
+ || animationType == ANIMATION_TYPE_SHOW) {
// no-op: already shown or animating in (because window visibility is
// applied before starting animation).
- // TODO: When we have more than one types: handle specific case when
- // show animation is going on, but the current type is not becoming visible.
continue;
}
typesReady |= InsetsState.toPublicType(consumer.getType());
@@ -377,12 +400,11 @@
int typesReady = 0;
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
- InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
- if (mAnimationDirection == DIRECTION_SHOW) {
- cancelExistingAnimation();
- } else if (!consumer.isRequestedVisible()
- && (mAnimationDirection == DIRECTION_NONE
- || mAnimationDirection == DIRECTION_HIDE)) {
+ @InternalInsetsType int internalType = internalTypes.valueAt(i);
+ @AnimationType int animationType = getAnimationType(internalType);
+ InsetsSourceConsumer consumer = getSourceConsumer(internalType);
+ if (!mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE
+ || animationType == ANIMATION_TYPE_HIDE) {
// no-op: already hidden or animating out.
continue;
}
@@ -394,11 +416,13 @@
@Override
public void controlWindowInsetsAnimation(@InsetsType int types, long durationMs,
WindowInsetsAnimationControlListener listener) {
- controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs);
+ controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs,
+ ANIMATION_TYPE_USER);
}
private void controlWindowInsetsAnimation(@InsetsType int types,
- WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs) {
+ WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs,
+ @AnimationType int animationType) {
// If the frame of our window doesn't span the entire display, the control API makes very
// little sense, as we don't deal with negative insets. So just cancel immediately.
if (!mState.getDisplayFrame().equals(mFrame)) {
@@ -406,12 +430,12 @@
return;
}
controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */,
- getLayoutInsetsDuringAnimationMode(types));
+ animationType, getLayoutInsetsDuringAnimationMode(types));
}
private void controlAnimationUnchecked(@InsetsType int types,
WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
- long durationMs, boolean fade,
+ long durationMs, boolean fade, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
if (types == 0) {
// nothing to animate.
@@ -444,7 +468,7 @@
final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls,
frame, mState, listener, typesReady, this, durationMs, fade,
layoutInsetsDuringAnimation);
- mAnimationControls.add(controller);
+ mRunningAnimations.add(new RunningAnimation(controller, animationType));
}
/**
@@ -523,10 +547,10 @@
}
private void cancelExistingControllers(@InsetsType int types) {
- for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
- InsetsAnimationControlImpl control = mAnimationControls.get(i);
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
if ((control.getTypes() & types) != 0) {
- cancelAnimation(control);
+ cancelAnimation(control, true /* invokeCallback */);
}
}
}
@@ -534,7 +558,7 @@
@VisibleForTesting
@Override
public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) {
- mAnimationControls.remove(controller);
+ cancelAnimation(controller, false /* invokeCallback */);
if (shown) {
showDirectly(controller.getTypes());
} else {
@@ -554,17 +578,24 @@
}
void notifyControlRevoked(InsetsSourceConsumer consumer) {
- for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
- InsetsAnimationControlImpl control = mAnimationControls.get(i);
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
if ((control.getTypes() & toPublicType(consumer.getType())) != 0) {
- cancelAnimation(control);
+ cancelAnimation(control, true /* invokeCallback */);
}
}
}
- private void cancelAnimation(InsetsAnimationControlImpl control) {
- control.onCancelled();
- mAnimationControls.remove(control);
+ private void cancelAnimation(InsetsAnimationControlImpl control, boolean invokeCallback) {
+ if (invokeCallback) {
+ control.onCancelled();
+ }
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ if (mRunningAnimations.get(i).control == control) {
+ mRunningAnimations.remove(i);
+ break;
+ }
+ }
}
private void applyLocalVisibilityOverride() {
@@ -622,8 +653,15 @@
}
}
- boolean isAnimating() {
- return mAnimationDirection != DIRECTION_NONE;
+ @VisibleForTesting
+ public @AnimationType int getAnimationType(@InternalInsetsType int type) {
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
+ if (control.controlsInternalType(type)) {
+ return mRunningAnimations.get(i).type;
+ }
+ }
+ return ANIMATION_TYPE_NONE;
}
private InsetsSourceConsumer createConsumerOfType(int type) {
@@ -665,8 +703,8 @@
// and hidden state insets are correct.
controlAnimationUnchecked(
types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
- true /* fade */, show
- ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+ true /* fade */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
+ show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
: LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
}
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index b2a5d91..8a1b45a 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.view.InsetsState.InternalInsetsType;
@@ -172,7 +174,7 @@
private void applyHiddenToControl() {
if (mSourceControl == null || mSourceControl.getLeash() == null
- || mController.isAnimating()) {
+ || mController.getAnimationType(mType) != ANIMATION_TYPE_NONE) {
return;
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index ff8455a..cc4278b 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -122,6 +122,8 @@
private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
private static native void nativeSetFlags(long transactionObj, long nativeObject,
int flags, int mask);
+ private static native void nativeSetFrameRateSelectionPriority(long transactionObj,
+ long nativeObject, int priority);
private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
int l, int t, int r, int b);
private static native void nativeSetCornerRadius(long transactionObj, long nativeObject,
@@ -2245,6 +2247,19 @@
}
/**
+ * This information is passed to SurfaceFlinger to decide which window should have a
+ * priority when deciding about the refresh rate of the display. All windows have the
+ * lowest priority by default.
+ * @hide
+ */
+ @NonNull
+ public Transaction setFrameRateSelectionPriority(@NonNull SurfaceControl sc, int priority) {
+ sc.checkNotReleased();
+ nativeSetFrameRateSelectionPriority(mNativeObject, sc.mNativeObject, priority);
+ return this;
+ }
+
+ /**
* Request that a given surface and it's sub-tree be shown.
*
* @param sc The surface to show.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 13d609b..562ed0e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -130,7 +130,6 @@
import android.view.contentcapture.ContentCaptureSession;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
import android.view.inspector.InspectableProperty;
import android.view.inspector.InspectableProperty.EnumEntry;
import android.view.inspector.InspectableProperty.FlagEntry;
@@ -7942,12 +7941,12 @@
if (isPressed()) {
setPressed(false);
}
- if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
- notifyFocusChangeToInputMethodManager(false /* hasFocus */);
+ if (hasWindowFocus()) {
+ notifyFocusChangeToImeFocusController(false /* hasFocus */);
}
onFocusLost();
- } else if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
- notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+ } else if (hasWindowFocus()) {
+ notifyFocusChangeToImeFocusController(true /* hasFocus */);
}
invalidate(true);
@@ -7964,23 +7963,15 @@
}
/**
- * Notify {@link InputMethodManager} about the focus change of the {@link View}.
- *
- * <p>Does nothing when {@link InputMethodManager} is not available.</p>
+ * Notify {@link ImeFocusController} about the focus change of the {@link View}.
*
* @param hasFocus {@code true} when the {@link View} is being focused.
*/
- private void notifyFocusChangeToInputMethodManager(boolean hasFocus) {
- final InputMethodManager imm =
- getContext().getSystemService(InputMethodManager.class);
- if (imm == null) {
+ private void notifyFocusChangeToImeFocusController(boolean hasFocus) {
+ if (mAttachInfo == null) {
return;
}
- if (hasFocus) {
- imm.focusIn(this);
- } else {
- imm.focusOut(this);
- }
+ mAttachInfo.mViewRootImpl.getImeFocusController().onViewFocusChanged(this, hasFocus);
}
/** @hide */
@@ -13918,7 +13909,7 @@
mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;
onFinishTemporaryDetach();
if (hasWindowFocus() && hasFocus()) {
- notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+ notifyFocusChangeToImeFocusController(true /* hasFocus */);
}
notifyEnterOrExitForAutoFillIfNeeded(true);
notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
@@ -14326,13 +14317,13 @@
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
- notifyFocusChangeToInputMethodManager(false /* hasFocus */);
+ notifyFocusChangeToImeFocusController(false /* hasFocus */);
}
removeLongPressCallback();
removeTapCallback();
onFocusLost();
} else if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
- notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+ notifyFocusChangeToImeFocusController(true /* hasFocus */);
}
refreshDrawableState();
@@ -14349,6 +14340,14 @@
}
/**
+ * @return {@code true} if this view is in a window that currently has IME focusable state.
+ * @hide
+ */
+ public boolean hasImeFocus() {
+ return mAttachInfo != null && mAttachInfo.mHasImeFocus;
+ }
+
+ /**
* Dispatch a view visibility change down the view hierarchy.
* ViewGroups should override to route to their children.
* @param changedView The view whose visibility changed. Could be 'this' or
@@ -19644,7 +19643,7 @@
rebuildOutline();
if (isFocused()) {
- notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+ notifyFocusChangeToImeFocusController(true /* hasFocus */);
}
}
@@ -20227,9 +20226,8 @@
onDetachedFromWindow();
onDetachedFromWindowInternal();
- InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
- if (imm != null) {
- imm.onViewDetachedFromWindow(this);
+ if (info != null) {
+ info.mViewRootImpl.getImeFocusController().onViewDetachedFromWindow(this);
}
ListenerInfo li = mListenerInfo;
@@ -28565,6 +28563,11 @@
boolean mHasWindowFocus;
/**
+ * Indicates whether the view's window has IME focused.
+ */
+ boolean mHasImeFocus;
+
+ /**
* The current visibility of the window.
*/
int mWindowVisibility;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2ef944f..17b945b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -453,7 +453,6 @@
boolean mReportNextDraw;
boolean mFullRedrawNeeded;
boolean mNewSurfaceNeeded;
- boolean mLastWasImTarget;
boolean mForceNextWindowRelayout;
CountDownLatch mWindowDrawCountDown;
@@ -619,6 +618,16 @@
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this, 0) : null;
+ private final ImeFocusController mImeFocusController;
+
+ /**
+ * @return {@link ImeFocusController} for this instance.
+ */
+ @NonNull
+ public ImeFocusController getImeFocusController() {
+ return mImeFocusController;
+ }
+
private final InsetsController mInsetsController = new InsetsController(this);
private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
@@ -704,6 +713,7 @@
}
loadSystemProperties();
+ mImeFocusController = new ImeFocusController(this);
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -1054,11 +1064,6 @@
}
}
- /** Whether the window is in local focus mode or not */
- private boolean isInLocalFocusMode() {
- return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
- }
-
@UnsupportedAppUsage
public int getWindowFlags() {
return mWindowAttributes.flags;
@@ -2892,19 +2897,7 @@
mViewVisibility = viewVisibility;
mHadWindowFocus = hasWindowFocus;
- if (hasWindowFocus && !isInLocalFocusMode()) {
- final boolean imTarget = WindowManager.LayoutParams
- .mayUseInputMethod(mWindowAttributes.flags);
- if (imTarget != mLastWasImTarget) {
- mLastWasImTarget = imTarget;
- InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
- if (imm != null && imTarget) {
- imm.onPreWindowFocus(mView, hasWindowFocus);
- imm.onPostWindowFocus(mView, mView.findFocus(),
- mWindowAttributes.softInputMode, mWindowAttributes.flags);
- }
- }
- }
+ mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
// Remember if we must report the next draw.
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
@@ -3072,14 +3065,10 @@
}
mAttachInfo.mHasWindowFocus = hasWindowFocus;
+ mAttachInfo.mHasImeFocus = mImeFocusController.updateImeFocusable(
+ mWindowAttributes, true /* force */);
+ mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes);
- mLastWasImTarget = WindowManager.LayoutParams
- .mayUseInputMethod(mWindowAttributes.flags);
-
- InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
- if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
- imm.onPreWindowFocus(mView, hasWindowFocus);
- }
if (mView != null) {
mAttachInfo.mKeyDispatchState.reset();
mView.dispatchWindowFocusChanged(hasWindowFocus);
@@ -3091,11 +3080,10 @@
// Note: must be done after the focus change callbacks,
// so all of the view state is set up correctly.
+ mImeFocusController.onPostWindowFocus(mView.findFocus(), hasWindowFocus,
+ mWindowAttributes);
+
if (hasWindowFocus) {
- if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
- imm.onPostWindowFocus(mView, mView.findFocus(),
- mWindowAttributes.softInputMode, mWindowAttributes.flags);
- }
// Clear the forward bit. We can just do this directly, since
// the window manager doesn't care about it.
mWindowAttributes.softInputMode &=
@@ -4891,10 +4879,7 @@
enqueueInputEvent(event, null, 0, true);
} break;
case MSG_CHECK_FOCUS: {
- InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
- if (imm != null) {
- imm.checkFocus();
- }
+ getImeFocusController().checkFocus(false, true);
} break;
case MSG_CLOSE_SYSTEM_DIALOGS: {
if (mView != null) {
@@ -5458,23 +5443,20 @@
@Override
protected int onProcess(QueuedInputEvent q) {
- if (mLastWasImTarget && !isInLocalFocusMode()) {
- InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
- if (imm != null) {
- final InputEvent event = q.mEvent;
- if (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " + event);
- int result = imm.dispatchInputEvent(event, q, this, mHandler);
- if (result == InputMethodManager.DISPATCH_HANDLED) {
- return FINISH_HANDLED;
- } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
- // The IME could not handle it, so skip along to the next InputStage
- return FORWARD;
- } else {
- return DEFER; // callback will be invoked later
- }
- }
+ final int result = mImeFocusController.onProcessImeInputStage(
+ q, q.mEvent, mWindowAttributes, this);
+ switch (result) {
+ case InputMethodManager.DISPATCH_IN_PROGRESS:
+ // callback will be invoked later
+ return DEFER;
+ case InputMethodManager.DISPATCH_NOT_HANDLED:
+ // The IME could not handle it, so skip along to the next InputStage
+ return FORWARD;
+ case InputMethodManager.DISPATCH_HANDLED:
+ return FINISH_HANDLED;
+ default:
+ throw new IllegalStateException("Unexpected result=" + result);
}
- return FORWARD;
}
@Override
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 7d5564e..ccfbd7e 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -487,11 +487,8 @@
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
- if (view != null) {
- InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
- if (imm != null) {
- imm.windowDismissed(mViews.get(index).getWindowToken());
- }
+ if (root != null) {
+ root.getImeFocusController().onWindowDismissed();
}
boolean deferred = root.die(immediate);
if (view != null) {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 9c04b39..c159f89 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -230,6 +230,7 @@
/** @hide */ public static final int ACTION_VIEW_ENTERED = 2;
/** @hide */ public static final int ACTION_VIEW_EXITED = 3;
/** @hide */ public static final int ACTION_VALUE_CHANGED = 4;
+ /** @hide */ public static final int ACTION_RESPONSE_EXPIRED = 5;
/** @hide */ public static final int NO_LOGGING = 0;
/** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1;
@@ -776,11 +777,19 @@
*
* @see AutofillClient#autofillClientIsVisibleForAutofill()
*
+ * @param isExpiredResponse The response has expired or not
+ *
* {@hide}
*/
- public void onInvisibleForAutofill() {
+ public void onInvisibleForAutofill(boolean isExpiredResponse) {
synchronized (mLock) {
mOnInvisibleCalled = true;
+
+ if (isExpiredResponse) {
+ // Notify service the response has expired.
+ updateSessionLocked(/* id= */ null, /* bounds= */ null, /* value= */ null,
+ ACTION_RESPONSE_EXPIRED, /* flags= */ 0);
+ }
}
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index ae2fb8e..d5d631a 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -758,8 +758,9 @@
Context context;
if (mTargetView != null) {
context = mTargetView.getContext();
- } else if (mIMM.mServedView != null) {
- context = mIMM.mServedView.getContext();
+ } else if (mIMM.mCurRootView != null) {
+ final View servedView = mIMM.mCurRootView.getImeFocusController().getServedView();
+ context = servedView != null ? servedView.getContext() : null;
} else {
context = null;
}
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index c10144e..a32ea4b 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -19,6 +19,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Parcelable;
@@ -61,6 +62,20 @@
private final @Nullable IInlineContentProvider mContentProvider;
/**
+ * Creates a new {@link InlineSuggestion}, for testing purpose.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static InlineSuggestion newInlineSuggestion(@NonNull InlineSuggestionInfo info) {
+ return new InlineSuggestion(info, null);
+ }
+
+
+
+
+ /**
* Inflates a view with the content of this suggestion at a specific size.
* The size must be between the {@link InlinePresentationSpec#getMinSize() min size}
* and the {@link InlinePresentationSpec#getMaxSize() max size} of the presentation
@@ -271,10 +286,10 @@
};
@DataClass.Generated(
- time = 1575933636929L,
+ time = 1578972138081L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
- inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+ inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
index 07fce31..195b63a 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.os.Parcelable;
import android.view.inline.InlinePresentationSpec;
@@ -53,6 +54,19 @@
/** Hints for the type of data being suggested. */
private final @Nullable String[] mAutofillHints;
+ /**
+ * Creates a new {@link InlineSuggestionInfo}, for testing purpose.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static InlineSuggestionInfo newInlineSuggestionInfo(
+ @NonNull InlinePresentationSpec presentationSpec,
+ @NonNull @Source String source,
+ @Nullable String[] autofillHints) {
+ return new InlineSuggestionInfo(presentationSpec, source, autofillHints);
+ }
@@ -247,10 +261,10 @@
};
@DataClass.Generated(
- time = 1574406074120L,
+ time = 1578972121865L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionInfo.java",
- inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mPresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+ inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mPresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.view.inline.InlinePresentationSpec,java.lang.String,java.lang.String[])\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
index 924a5ee..be833df 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.os.Parcelable;
import com.android.internal.util.DataClass;
@@ -33,6 +34,18 @@
public final class InlineSuggestionsResponse implements Parcelable {
private final @NonNull List<InlineSuggestion> mInlineSuggestions;
+ /**
+ * Creates a new {@link InlineSuggestionsResponse}, for testing purpose.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static InlineSuggestionsResponse newInlineSuggestionsResponse(
+ @NonNull List<InlineSuggestion> inlineSuggestions) {
+ return new InlineSuggestionsResponse(inlineSuggestions);
+ }
+
// Code below generated by codegen v1.0.14.
@@ -151,10 +164,10 @@
};
@DataClass.Generated(
- time = 1574406147911L,
+ time = 1578972149519L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsResponse.java",
- inputSignatures = "private final @android.annotation.NonNull java.util.List<android.view.inputmethod.InlineSuggestion> mInlineSuggestions\nclass InlineSuggestionsResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
+ inputSignatures = "private final @android.annotation.NonNull java.util.List<android.view.inputmethod.InlineSuggestion> mInlineSuggestions\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(java.util.List<android.view.inputmethod.InlineSuggestion>)\nclass InlineSuggestionsResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f3007a7..904e736 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -64,6 +64,7 @@
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.KeyEvent;
+import android.view.ImeFocusController;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -364,10 +365,10 @@
boolean mActive = false;
/**
- * {@code true} if next {@link #onPostWindowFocus(View, View, int, int)} needs to
+ * {@code true} if next {@link ImeFocusController#onPostWindowFocus} needs to
* restart input.
*/
- boolean mRestartOnNextWindowFocus = true;
+ private boolean mRestartOnNextWindowFocus = true;
/**
* As reported by IME through InputConnection.
@@ -380,22 +381,8 @@
* This is the root view of the overall window that currently has input
* method focus.
*/
- @UnsupportedAppUsage
- View mCurRootView;
- /**
- * This is the view that should currently be served by an input method,
- * regardless of the state of setting that up.
- */
- // See comment to mH field in regard to @UnsupportedAppUsage
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- View mServedView;
- /**
- * This is then next view that will be served by the input method, when
- * we get around to updating things.
- */
- // See comment to mH field in regard to @UnsupportedAppUsage
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- View mNextServedView;
+ @GuardedBy("mH")
+ ViewRootImpl mCurRootView;
/**
* This is set when we are in the process of connecting, to determine
* when we have actually finished.
@@ -489,6 +476,8 @@
final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
+ final DelegateImpl mDelegate = new DelegateImpl();
+
// -----------------------------------------------------------
static final int MSG_DUMP = 1;
@@ -564,6 +553,178 @@
return servedView.hasWindowFocus() || isAutofillUIShowing(servedView);
}
+ private final class DelegateImpl implements
+ ImeFocusController.InputMethodManagerDelegate {
+ /**
+ * Used by {@link ImeFocusController} to start input connection.
+ */
+ @Override
+ public boolean startInput(@StartInputReason int startInputReason, View focusedView,
+ @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
+ int windowFlags) {
+ synchronized (mH) {
+ mCurrentTextBoxAttribute = null;
+ mCompletions = null;
+ mServedConnecting = true;
+ if (getServedViewLocked() != null && !getServedViewLocked().onCheckIsTextEditor()) {
+ // servedView has changed and it's not editable.
+ maybeCallServedViewChangedLocked(null);
+ }
+ }
+ return startInputInner(startInputReason,
+ focusedView != null ? focusedView.getWindowToken() : null, startInputFlags,
+ softInputMode, windowFlags);
+ }
+
+ /**
+ * Used by {@link ImeFocusController} to finish input connection.
+ */
+ @Override
+ public void finishInput() {
+ synchronized (mH) {
+ finishInputLocked();
+ }
+ }
+
+ /**
+ * Used by {@link ImeFocusController} to hide current input method editor.
+ */
+ @Override
+ public void closeCurrentIme() {
+ closeCurrentInput();
+ }
+
+ /**
+ * For {@link ImeFocusController} to start input asynchronously when focus gain.
+ */
+ @Override
+ public void startInputAsyncOnWindowFocusGain(View focusedView,
+ @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) {
+ final boolean forceNewFocus1 = forceNewFocus;
+ final int startInputFlags = getStartInputFlags(focusedView, 0);
+
+ if (mWindowFocusGainFuture != null) {
+ mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
+ }
+ mWindowFocusGainFuture = mStartInputWorker.submit(() -> {
+ synchronized (mH) {
+ if (mCurRootView == null) {
+ return;
+ }
+ if (mCurRootView.getImeFocusController().checkFocus(forceNewFocus1, false)) {
+ // We need to restart input on the current focus view. This
+ // should be done in conjunction with telling the system service
+ // about the window gaining focus, to help make the transition
+ // smooth.
+ if (startInput(StartInputReason.WINDOW_FOCUS_GAIN,
+ focusedView, startInputFlags, softInputMode, windowFlags)) {
+ return;
+ }
+ }
+
+ // For some reason we didn't do a startInput + windowFocusGain, so
+ // we'll just do a window focus gain and call it a day.
+ try {
+ if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
+ mService.startInputOrWindowGainedFocus(
+ StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
+ focusedView.getWindowToken(), startInputFlags, softInputMode,
+ windowFlags,
+ null, null, 0 /* missingMethodFlags */,
+ mCurRootView.mContext.getApplicationInfo().targetSdkVersion);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ });
+ }
+
+ /**
+ * Used by {@link ImeFocusController} to finish current composing text.
+ */
+ @Override
+ public void finishComposingText() {
+ if (mServedInputConnectionWrapper != null) {
+ mServedInputConnectionWrapper.finishComposingText();
+ }
+ }
+
+ /**
+ * Used for {@link ImeFocusController} to set the current focused root view.
+ */
+ @Override
+ public void setCurrentRootView(ViewRootImpl rootView) {
+ // If the mCurRootView is losing window focus, release the strong reference to it
+ // so as not to prevent it from being garbage-collected.
+ if (mWindowFocusGainFuture != null) {
+ mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
+ mWindowFocusGainFuture = null;
+ }
+ synchronized (mH) {
+ mCurRootView = rootView;
+ }
+ }
+
+ /**
+ * Used for {@link ImeFocusController} to return if the root view from the
+ * controller is this {@link InputMethodManager} currently focused.
+ * TODO: Address event-order problem when get current root view in multi-threads.
+ */
+ @Override
+ public boolean isCurrentRootView(ViewRootImpl rootView) {
+ synchronized (mH) {
+ return mCurRootView == rootView;
+ }
+ }
+
+ /**
+ * For {@link ImeFocusController#checkFocus} if needed to force check new focus.
+ */
+ @Override
+ public boolean isRestartOnNextWindowFocus(boolean reset) {
+ final boolean result = mRestartOnNextWindowFocus;
+ if (reset) {
+ mRestartOnNextWindowFocus = false;
+ }
+ return result;
+ }
+ }
+
+ /** @hide */
+ public DelegateImpl getDelegate() {
+ return mDelegate;
+ }
+
+ private View getServedViewLocked() {
+ return mCurRootView != null ? mCurRootView.getImeFocusController().getServedView() : null;
+ }
+
+ private View getNextServedViewLocked() {
+ return mCurRootView != null ? mCurRootView.getImeFocusController().getNextServedView()
+ : null;
+ }
+
+ private void setServedViewLocked(View view) {
+ if (mCurRootView != null) {
+ mCurRootView.getImeFocusController().setServedView(view);
+ }
+ }
+
+ private void setNextServedViewLocked(View view) {
+ if (mCurRootView != null) {
+ mCurRootView.getImeFocusController().setNextServedView(view);
+ }
+ }
+
+ /**
+ * Returns {@code true} when the given view has been served by Input Method.
+ */
+ private boolean hasServedByInputMethodLocked(View view) {
+ final View servedView = getServedViewLocked();
+ return (servedView == view
+ || (servedView != null && servedView.checkInputConnectionProxy(view)));
+ }
+
class H extends Handler {
H(Looper looper) {
super(looper, null, true);
@@ -629,7 +790,8 @@
clearBindingLocked();
// If we were actively using the last input method, then
// we would like to re-connect to the next input method.
- if (mServedView != null && mServedView.isFocused()) {
+ final View servedView = getServedViewLocked();
+ if (servedView != null && servedView.isFocused()) {
mServedConnecting = true;
}
startInput = mActive;
@@ -664,11 +826,16 @@
}
// Check focus again in case that "onWindowFocus" is called before
// handling this message.
- if (mServedView != null && canStartInput(mServedView)) {
- if (checkFocusNoStartInput(mRestartOnNextWindowFocus)) {
+ final View servedView;
+ synchronized (mH) {
+ servedView = getServedViewLocked();
+ }
+ if (servedView != null && canStartInput(servedView)) {
+ if (mCurRootView != null && mCurRootView.getImeFocusController()
+ .checkFocus(mRestartOnNextWindowFocus, false)) {
final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS
: StartInputReason.DEACTIVATED_BY_IMMS;
- startInputInner(reason, null, 0, 0, 0);
+ mDelegate.startInput(reason, null, 0, 0, 0);
}
}
return;
@@ -1212,10 +1379,7 @@
checkFocus();
synchronized (mH) {
- return (mServedView == view
- || (mServedView != null
- && mServedView.checkInputConnectionProxy(view)))
- && mCurrentTextBoxAttribute != null;
+ return hasServedByInputMethodLocked(view) && mCurrentTextBoxAttribute != null;
}
}
@@ -1225,7 +1389,7 @@
public boolean isActive() {
checkFocus();
synchronized (mH) {
- return mServedView != null && mCurrentTextBoxAttribute != null;
+ return getServedViewLocked() != null && mCurrentTextBoxAttribute != null;
}
}
@@ -1286,11 +1450,14 @@
*/
@UnsupportedAppUsage
void finishInputLocked() {
- mNextServedView = null;
mActivityViewToScreenMatrix = null;
- if (mServedView != null) {
- if (DEBUG) Log.v(TAG, "FINISH INPUT: mServedView=" + dumpViewInfo(mServedView));
- mServedView = null;
+ setNextServedViewLocked(null);
+ if (getServedViewLocked() != null) {
+ if (DEBUG) {
+ Log.v(TAG, "FINISH INPUT: mServedView="
+ + dumpViewInfo(getServedViewLocked()));
+ }
+ setServedViewLocked(null);
mCompletions = null;
mServedConnecting = false;
clearConnectionLocked();
@@ -1307,8 +1474,7 @@
checkFocus();
synchronized (mH) {
- if (mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view))) {
+ if (!hasServedByInputMethodLocked(view)) {
return;
}
@@ -1332,8 +1498,7 @@
checkFocus();
synchronized (mH) {
- if (mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view))) {
+ if (!hasServedByInputMethodLocked(view)) {
return;
}
@@ -1447,8 +1612,7 @@
checkFocus();
synchronized (mH) {
- if (mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view))) {
+ if (!hasServedByInputMethodLocked(view)) {
return false;
}
@@ -1539,7 +1703,8 @@
ResultReceiver resultReceiver) {
checkFocus();
synchronized (mH) {
- if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+ final View servedView = getServedViewLocked();
+ if (servedView == null || servedView.getWindowToken() != windowToken) {
return false;
}
@@ -1566,7 +1731,8 @@
**/
public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
synchronized (mH) {
- if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+ final View servedView = getServedViewLocked();
+ if (servedView == null || servedView.getWindowToken() != windowToken) {
return;
}
if (mCurMethod != null) {
@@ -1617,8 +1783,7 @@
checkFocus();
synchronized (mH) {
- if (mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view))) {
+ if (!hasServedByInputMethodLocked(view)) {
return;
}
@@ -1645,7 +1810,7 @@
final View view;
synchronized (mH) {
- view = mServedView;
+ view = getServedViewLocked();
// Make sure we have a window token for the served view.
if (DEBUG) {
@@ -1664,10 +1829,7 @@
Log.e(TAG, "ABORT input: ServedView must be attached to a Window");
return false;
}
- startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
- if (view.onCheckIsTextEditor()) {
- startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
- }
+ startInputFlags = getStartInputFlags(view, startInputFlags);
softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode;
windowFlags = view.getViewRootImpl().mWindowAttributes.flags;
}
@@ -1690,7 +1852,7 @@
// The view is running on a different thread than our own, so
// we need to reschedule our work for over there.
if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
- vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0));
+ vh.post(() -> mDelegate.startInput(startInputReason, null, 0, 0, 0));
return false;
}
@@ -1709,11 +1871,12 @@
synchronized (mH) {
// Now that we are locked again, validate that our state hasn't
// changed.
- if (mServedView != view || !mServedConnecting) {
+ final View servedView = getServedViewLocked();
+ if (servedView != view || !mServedConnecting) {
// Something else happened, so abort.
if (DEBUG) Log.v(TAG,
"Starting input: finished by someone else. view=" + dumpViewInfo(view)
- + " mServedView=" + dumpViewInfo(mServedView)
+ + " servedView=" + dumpViewInfo(servedView)
+ " mServedConnecting=" + mServedConnecting);
return false;
}
@@ -1804,101 +1967,12 @@
return true;
}
- /**
- * When the focused window is dismissed, this method is called to finish the
- * input method started before.
- * @hide
- */
- @UnsupportedAppUsage
- public void windowDismissed(IBinder appWindowToken) {
- checkFocus();
- synchronized (mH) {
- if (mServedView != null &&
- mServedView.getWindowToken() == appWindowToken) {
- finishInputLocked();
- }
- if (mCurRootView != null &&
- mCurRootView.getWindowToken() == appWindowToken) {
- mCurRootView = null;
- }
+ private int getStartInputFlags(View focusedView, int startInputFlags) {
+ startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
+ if (focusedView.onCheckIsTextEditor()) {
+ startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
}
- }
-
- /**
- * Call this when a view receives focus.
- * @hide
- */
- @UnsupportedAppUsage
- public void focusIn(View view) {
- synchronized (mH) {
- focusInLocked(view);
- }
- }
-
- void focusInLocked(View view) {
- if (DEBUG) Log.v(TAG, "focusIn: " + dumpViewInfo(view));
-
- if (view != null && view.isTemporarilyDetached()) {
- // This is a request from a view that is temporarily detached from a window.
- if (DEBUG) Log.v(TAG, "Temporarily detached view, ignoring");
- return;
- }
-
- if (mCurRootView != view.getRootView()) {
- // This is a request from a window that isn't in the window with
- // IME focus, so ignore it.
- if (DEBUG) Log.v(TAG, "Not IME target window, ignoring");
- return;
- }
-
- mNextServedView = view;
- scheduleCheckFocusLocked(view);
- }
-
- /**
- * Call this when a view loses focus.
- * @hide
- */
- @UnsupportedAppUsage
- public void focusOut(View view) {
- synchronized (mH) {
- if (DEBUG) Log.v(TAG, "focusOut: view=" + dumpViewInfo(view)
- + " mServedView=" + dumpViewInfo(mServedView));
- if (mServedView != view) {
- // The following code would auto-hide the IME if we end up
- // with no more views with focus. This can happen, however,
- // whenever we go into touch mode, so it ends up hiding
- // at times when we don't really want it to. For now it
- // seems better to just turn it all off.
- // TODO: Check view.isTemporarilyDetached() when re-enable the following code.
- if (false && canStartInput(view)) {
- mNextServedView = null;
- scheduleCheckFocusLocked(view);
- }
- }
- }
- }
-
- /**
- * Call this when a view is being detached from a {@link android.view.Window}.
- * @hide
- */
- public void onViewDetachedFromWindow(View view) {
- synchronized (mH) {
- if (DEBUG) Log.v(TAG, "onViewDetachedFromWindow: view=" + dumpViewInfo(view)
- + " mServedView=" + dumpViewInfo(mServedView));
- if (mServedView == view) {
- mNextServedView = null;
- scheduleCheckFocusLocked(view);
- }
- }
- }
-
- static void scheduleCheckFocusLocked(View view) {
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
- if (viewRootImpl != null) {
- viewRootImpl.dispatchCheckFocus();
- }
+ return startInputFlags;
}
/**
@@ -1906,54 +1980,12 @@
*/
@UnsupportedAppUsage
public void checkFocus() {
- if (checkFocusNoStartInput(false)) {
- startInputInner(StartInputReason.CHECK_FOCUS, null, 0, 0, 0);
- }
- }
-
- private boolean checkFocusNoStartInput(boolean forceNewFocus) {
- // This is called a lot, so short-circuit before locking.
- if (mServedView == mNextServedView && !forceNewFocus) {
- return false;
- }
-
- final ControlledInputConnectionWrapper ic;
synchronized (mH) {
- if (mServedView == mNextServedView && !forceNewFocus) {
- return false;
- }
- if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
- + " next=" + mNextServedView
- + " forceNewFocus=" + forceNewFocus
- + " package="
- + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));
-
- if (mNextServedView == null) {
- finishInputLocked();
- // In this case, we used to have a focused view on the window,
- // but no longer do. We should make sure the input method is
- // no longer shown, since it serves no purpose.
- closeCurrentInput();
- return false;
- }
-
- ic = mServedInputConnectionWrapper;
-
- mServedView = mNextServedView;
- mCurrentTextBoxAttribute = null;
- mCompletions = null;
- mServedConnecting = true;
- // servedView has changed and it's not editable.
- if (!mServedView.onCheckIsTextEditor()) {
- maybeCallServedViewChangedLocked(null);
+ if (mCurRootView != null) {
+ mCurRootView.getImeFocusController().checkFocus(false /* forceNewFocus */,
+ true /* startInput */);
}
}
-
- if (ic != null) {
- ic.finishComposingText();
- }
-
- return true;
}
@UnsupportedAppUsage
@@ -1966,92 +1998,6 @@
}
/**
- * Called by ViewAncestor when its window gets input focus.
- * @hide
- */
- public void onPostWindowFocus(View rootView, View focusedView,
- @SoftInputModeFlags int softInputMode, int windowFlags) {
- boolean forceNewFocus = false;
- synchronized (mH) {
- if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
- + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
- + " flags=#" + Integer.toHexString(windowFlags));
- if (mRestartOnNextWindowFocus) {
- if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus");
- mRestartOnNextWindowFocus = false;
- forceNewFocus = true;
- }
- focusInLocked(focusedView != null ? focusedView : rootView);
- }
-
- int startInputFlags = 0;
- if (focusedView != null) {
- startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
- if (focusedView.onCheckIsTextEditor()) {
- startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
- }
- }
-
- final boolean forceNewFocus1 = forceNewFocus;
- final int startInputFlags1 = startInputFlags;
- if (mWindowFocusGainFuture != null) {
- mWindowFocusGainFuture.cancel(false/* mayInterruptIfRunning */);
- }
- mWindowFocusGainFuture = mStartInputWorker.submit(() -> {
- if (checkFocusNoStartInput(forceNewFocus1)) {
- // We need to restart input on the current focus view. This
- // should be done in conjunction with telling the system service
- // about the window gaining focus, to help make the transition
- // smooth.
- if (startInputInner(StartInputReason.WINDOW_FOCUS_GAIN, rootView.getWindowToken(),
- startInputFlags1, softInputMode, windowFlags)) {
- return;
- }
- }
-
- // For some reason we didn't do a startInput + windowFocusGain, so
- // we'll just do a window focus gain and call it a day.
- synchronized (mH) {
- try {
- if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
- mService.startInputOrWindowGainedFocus(
- StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
- rootView.getWindowToken(), startInputFlags1, softInputMode, windowFlags,
- null, null, 0 /* missingMethodFlags */,
- rootView.getContext().getApplicationInfo().targetSdkVersion);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- });
- }
-
- /** @hide */
- @UnsupportedAppUsage
- public void onPreWindowFocus(View rootView, boolean hasWindowFocus) {
- synchronized (mH) {
- if (rootView == null) {
- mCurRootView = null;
- } if (hasWindowFocus) {
- mCurRootView = rootView;
- } else if (rootView == mCurRootView) {
- // If the mCurRootView is losing window focus, release the strong reference to it
- // so as not to prevent it from being garbage-collected.
- mCurRootView = null;
- if (mWindowFocusGainFuture != null) {
- mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
- mWindowFocusGainFuture = null;
- }
- } else {
- if (DEBUG) {
- Log.v(TAG, "Ignoring onPreWindowFocus()."
- + " mCurRootView=" + mCurRootView + " rootView=" + rootView);
- }
- }
- }
- }
-
- /**
* Register for IME state callbacks and applying visibility in
* {@link android.view.ImeInsetsSourceConsumer}.
* @hide
@@ -2090,10 +2036,11 @@
*/
public boolean requestImeShow(ResultReceiver resultReceiver) {
synchronized (mH) {
- if (mServedView == null) {
+ final View servedView = getServedViewLocked();
+ if (servedView == null) {
return false;
}
- showSoftInput(mServedView, 0 /* flags */, resultReceiver);
+ showSoftInput(servedView, 0 /* flags */, resultReceiver);
return true;
}
}
@@ -2135,9 +2082,8 @@
checkFocus();
synchronized (mH) {
- if ((mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view)))
- || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
return;
}
@@ -2185,12 +2131,17 @@
return;
}
- final boolean focusChanged = mServedView != mNextServedView;
+ final View servedView;
+ final View nextServedView;
+ synchronized (mH) {
+ servedView = getServedViewLocked();
+ nextServedView = getNextServedViewLocked();
+ }
+ final boolean focusChanged = servedView != nextServedView;
checkFocus();
synchronized (mH) {
- if ((mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view)))
- || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
return;
}
try {
@@ -2258,9 +2209,8 @@
checkFocus();
synchronized (mH) {
- if ((mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view)))
- || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
return;
}
@@ -2296,9 +2246,8 @@
checkFocus();
synchronized (mH) {
- if ((mServedView != view &&
- (mServedView == null || !mServedView.checkInputConnectionProxy(view)))
- || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
return;
}
// If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
@@ -2354,9 +2303,8 @@
checkFocus();
synchronized (mH) {
- if ((mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view)))
- || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
return;
}
try {
@@ -2577,8 +2525,9 @@
synchronized (mH) {
ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
if (viewRootImpl == null) {
- if (mServedView != null) {
- viewRootImpl = mServedView.getViewRootImpl();
+ final View servedView = getServedViewLocked();
+ if (servedView != null) {
+ viewRootImpl = servedView.getViewRootImpl();
}
}
if (viewRootImpl != null) {
@@ -2759,6 +2708,7 @@
/**
* Show the settings for enabling subtypes of the specified input method.
+ *
* @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
* subtypes of all input methods will be shown.
*/
@@ -3057,8 +3007,8 @@
p.println(" mFullscreenMode=" + mFullscreenMode);
p.println(" mCurMethod=" + mCurMethod);
p.println(" mCurRootView=" + mCurRootView);
- p.println(" mServedView=" + mServedView);
- p.println(" mNextServedView=" + mNextServedView);
+ p.println(" mServedView=" + getServedViewLocked());
+ p.println(" mNextServedView=" + getNextServedViewLocked());
p.println(" mServedConnecting=" + mServedConnecting);
if (mCurrentTextBoxAttribute != null) {
p.println(" mCurrentTextBoxAttribute:");
@@ -3134,6 +3084,8 @@
sb.append(",window=" + view.getWindowToken());
sb.append(",displayId=" + view.getContext().getDisplayId());
sb.append(",temporaryDetach=" + view.isTemporarilyDetached());
+ sb.append(",hasImeFocus=" + view.hasImeFocus());
+
return sb.toString();
}
}
diff --git a/core/java/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 a9f0175..457c033 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -75,11 +75,14 @@
private static final char SERVICES_SEPARATOR = ':';
private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+ @UserShortcutType
private static final int ACCESSIBILITY_BUTTON_USER_TYPE = convertToUserType(
ACCESSIBILITY_BUTTON);
+ @UserShortcutType
private static final int ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE = convertToUserType(
ACCESSIBILITY_SHORTCUT_KEY);
+ @ShortcutType
private int mShortcutType;
private final List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
private AlertDialog mAlertDialog;
@@ -211,9 +214,14 @@
mShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
ACCESSIBILITY_BUTTON);
+ if ((mShortcutType != ACCESSIBILITY_BUTTON)
+ && (mShortcutType != ACCESSIBILITY_SHORTCUT_KEY)) {
+ throw new IllegalStateException("Unexpected shortcut type: " + mShortcutType);
+ }
+
mTargets.addAll(getServiceTargets(this, mShortcutType));
- mTargetAdapter = new TargetAdapter(mTargets);
+ mTargetAdapter = new TargetAdapter(mTargets, mShortcutType);
mAlertDialog = new AlertDialog.Builder(this)
.setAdapter(mTargetAdapter, /* listener= */ null)
.setPositiveButton(
@@ -357,16 +365,21 @@
private static class TargetAdapter extends BaseAdapter {
@ShortcutMenuMode
private int mShortcutMenuMode = ShortcutMenuMode.LAUNCH;
+ @ShortcutType
+ private int mShortcutButtonType;
private List<AccessibilityButtonTarget> mButtonTargets;
- TargetAdapter(List<AccessibilityButtonTarget> targets) {
+ TargetAdapter(List<AccessibilityButtonTarget> targets,
+ @ShortcutType int shortcutButtonType) {
this.mButtonTargets = targets;
+ this.mShortcutButtonType = shortcutButtonType;
}
- void setShortcutMenuMode(int shortcutMenuMode) {
+ void setShortcutMenuMode(@ShortcutMenuMode int shortcutMenuMode) {
mShortcutMenuMode = shortcutMenuMode;
}
+ @ShortcutMenuMode
int getShortcutMenuMode() {
return mShortcutMenuMode;
}
@@ -441,14 +454,16 @@
private void updateLegacyActionItemVisibility(@NonNull Context context,
@NonNull ViewHolder holder) {
- final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
+ final boolean isLaunchMenuMode = (mShortcutMenuMode == ShortcutMenuMode.LAUNCH);
+ final boolean isHardwareButtonTriggered =
+ (mShortcutButtonType == ACCESSIBILITY_SHORTCUT_KEY);
- holder.mLabelView.setEnabled(!isEditMenuMode);
- holder.mViewItem.setEnabled(!isEditMenuMode);
+ holder.mLabelView.setEnabled(isLaunchMenuMode || isHardwareButtonTriggered);
+ holder.mViewItem.setEnabled(isLaunchMenuMode || isHardwareButtonTriggered);
holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
holder.mViewItem.setVisibility(View.VISIBLE);
holder.mSwitchItem.setVisibility(View.GONE);
- holder.mItemContainer.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
+ holder.mItemContainer.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE);
}
private void updateInvisibleActionItemVisibility(@NonNull Context context,
@@ -574,8 +589,6 @@
ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
} else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
switchServiceState(target);
- } else {
- throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
}
}
@@ -611,13 +624,15 @@
ComponentName.unflattenFromString(target.getId());
switch (target.getFragmentType()) {
+ case AccessibilityServiceFragmentType.LEGACY:
+ onLegacyTargetDeleted(targetComponentName);
+ break;
case AccessibilityServiceFragmentType.INVISIBLE:
onInvisibleTargetDeleted(targetComponentName);
break;
case AccessibilityServiceFragmentType.INTUITIVE:
onIntuitiveTargetDeleted(targetComponentName);
break;
- case AccessibilityServiceFragmentType.LEGACY:
case AccessibilityServiceFragmentType.BOUNCE:
// Do nothing
break;
@@ -633,6 +648,12 @@
}
}
+ private void onLegacyTargetDeleted(ComponentName componentName) {
+ if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+ }
+ }
+
private void onInvisibleTargetDeleted(ComponentName componentName) {
if (mShortcutType == ACCESSIBILITY_BUTTON) {
optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
@@ -648,8 +669,6 @@
ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) {
disableService(componentName);
}
- } else {
- throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
}
}
@@ -658,8 +677,6 @@
optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
} else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
- } else {
- throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
}
}
@@ -772,7 +789,7 @@
* @param componentName The component name that need to be opted out from Settings.
*/
private void optOutValueFromSettings(
- Context context, int shortcutType, ComponentName componentName) {
+ Context context, @UserShortcutType int shortcutType, ComponentName componentName) {
final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
final String targetsKey = convertToKey(shortcutType);
final String targetsValue = Settings.Secure.getString(context.getContentResolver(),
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 9fbc1b7..de64573 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -357,6 +357,13 @@
*/
public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling";
+ // Flags related to Nav Bar
+
+ /**
+ * (boolean) Whether to force the Nav Bar handle to remain opaque.
+ */
+ public static final String NAV_BAR_HANDLE_FORCE_OPAQUE = "nav_bar_handle_force_opaque";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 221cd6d..ef9b3d10 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -561,7 +561,7 @@
flags |= Document.FLAG_SUPPORTS_MOVE;
if (shouldBlockFromTree(docId)) {
- flags |= Document.FLAG_DIR_BLOCKS_TREE;
+ flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
}
} else {
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 6fd271c..0f50596 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -16,6 +16,7 @@
package com.android.internal.telephony;
+import android.telephony.BarringInfo;
import android.telephony.CallAttributes;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
@@ -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/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b47080f..573f378 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -423,6 +423,14 @@
transaction->setFlags(ctrl, flags, mask);
}
+static void nativeSetFrameRateSelectionPriority(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jint priority) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+ transaction->setFrameRateSelectionPriority(ctrl, priority);
+}
+
static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jobject regionObj) {
SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
@@ -1362,6 +1370,8 @@
(void*)nativeSetColorSpaceAgnostic },
{"nativeSetFlags", "(JJII)V",
(void*)nativeSetFlags },
+ {"nativeSetFrameRateSelectionPriority", "(JJI)V",
+ (void*)nativeSetFrameRateSelectionPriority },
{"nativeSetWindowCrop", "(JJIIII)V",
(void*)nativeSetWindowCrop },
{"nativeSetCornerRadius", "(JJF)V",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 39ea45a..05b573a 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -174,6 +174,8 @@
static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000;
static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF;
+static constexpr const char* kCurProfileDirPath = "/data/misc/profiles/cur";
+
/**
* The maximum value that the gUSAPPoolSizeMax variable may take. This value
* is a mirror of ZygoteServer.USAP_POOL_SIZE_MAX_LIMIT
@@ -1382,6 +1384,49 @@
freecon(dataDataContext);
}
+/**
+ * Like isolateAppData(), isolate jit profile directories, so apps don't see what
+ * other apps are installed by reading content inside /data/misc/profiles/cur.
+ *
+ * The implementation is similar to isolateAppData(), it creates a tmpfs
+ * on /data/misc/profiles/cur, and bind mounts related package profiles to it.
+ */
+static void isolateJitProfile(JNIEnv* env, jobjectArray pkg_data_info_list,
+ uid_t uid, const char* process_name, jstring managed_nice_name,
+ fail_fn_t fail_fn) {
+
+ auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
+ const userid_t user_id = multiuser_get_user_id(uid);
+
+ int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
+ // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
+ if ((size % 3) != 0) {
+ fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size));
+ }
+
+ // Mount (namespace) tmpfs on profile directory, so apps no longer access
+ // the original profile directory anymore.
+ MountAppDataTmpFs(kCurProfileDirPath, fail_fn);
+
+ // Create profile directory for this user.
+ std::string actualCurUserProfile = StringPrintf("%s/%d", kCurProfileDirPath, user_id);
+ PrepareDir(actualCurUserProfile.c_str(), DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+
+ for (int i = 0; i < size; i += 3) {
+ jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
+ std::string packageName = extract_fn(package_str).value();
+
+ std::string actualCurPackageProfile = StringPrintf("%s/%s", actualCurUserProfile.c_str(),
+ packageName.c_str());
+ std::string mirrorCurPackageProfile = StringPrintf("/data_mirror/cur_profiles/%d/%s",
+ user_id, packageName.c_str());
+
+ PrepareDir(actualCurPackageProfile, DEFAULT_DATA_DIR_PERMISSION, uid, uid, fail_fn);
+ BindMount(mirrorCurPackageProfile, actualCurPackageProfile, fail_fn);
+ }
+}
+
// Utility routine to specialize a zygote child process.
static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
@@ -1432,8 +1477,8 @@
// so they can't even traverse CE / DE directories.
if (pkg_data_info_list != nullptr
&& GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
- isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name,
- fail_fn);
+ isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
+ isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
}
// If this zygote isn't root, it won't be able to create a process group,
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 082a289..1fcc8ac 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -39,6 +39,7 @@
"/apex/com.android.media/javalib/updatable-media.jar",
"/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar",
"/apex/com.android.os.statsd/javalib/framework-statsd.jar",
+ "/apex/com.android.permission/javalib/framework-permission.jar",
"/apex/com.android.sdkext/javalib/framework-sdkextensions.jar",
"/apex/com.android.wifi/javalib/framework-wifi.jar",
"/apex/com.android.tethering/javalib/framework-tethering.jar",
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 8f9c041..da8c944 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -165,6 +165,11 @@
(section).args = "security -L"
];
+ optional android.util.PersistedLogProto persisted_logs = 1116 [
+ (section).type = SECTION_COMMAND,
+ (section).args = "/system/bin/sh /system/bin/incident-helper-cmd -l run persisted_logs --limit 10MB"
+ ];
+
// Stack dumps
optional android.os.BackTraceProto native_traces = 1200 [
(section).type = SECTION_TOMBSTONE,
diff --git a/core/proto/android/stats/sysui/notification_enums.proto b/core/proto/android/stats/sysui/notification_enums.proto
new file mode 100644
index 0000000..0983702
--- /dev/null
+++ b/core/proto/android/stats/sysui/notification_enums.proto
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.stats.sysui;
+
+// Enum used in NotificationReported and NotificationChannelModified atoms
+enum NotificationImportance { // Constants from NotificationManager.java
+ IMPORTANCE_UNSPECIFIED = -1000; // Should not occur for real notifications.
+ IMPORTANCE_NONE = 0; // No importance: does not show in the shade.
+ IMPORTANCE_MIN = 1; // Minimum to show in the shade.
+ IMPORTANCE_LOW = 2; // Shows in shade, maybe status bar, no buzz/beep.
+ IMPORTANCE_DEFAULT = 3; // Shows everywhere, makes noise, no heads-up.
+ IMPORTANCE_HIGH = 4; // Shows everywhere, makes noise, heads-up, may full-screen.
+}
diff --git a/core/proto/android/util/log.proto b/core/proto/android/util/log.proto
index 09870ae..a214a1a 100644
--- a/core/proto/android/util/log.proto
+++ b/core/proto/android/util/log.proto
@@ -94,3 +94,16 @@
repeated BinaryLogEntry binary_logs = 2;
}
+message PersistedLogProto {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ repeated TextLogEntry main_logs = 1;
+ repeated TextLogEntry radio_logs = 2;
+ repeated TextLogEntry events_logs = 3;
+ repeated TextLogEntry system_logs = 4;
+ repeated TextLogEntry crash_logs = 5;
+ repeated TextLogEntry stats_logs = 6;
+ repeated TextLogEntry security_logs = 7;
+ repeated TextLogEntry kernel_logs = 8;
+}
+
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2665e8a..f8c5166 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -362,7 +362,6 @@
<protected-broadcast android:name="android.net.wifi.WIFI_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
- <protected-broadcast android:name="android.net.wifi.WIFI_SCAN_AVAILABLE" />
<protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" />
@@ -377,6 +376,7 @@
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION" />
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW" />
<protected-broadcast android:name="android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION" />
+ <protected-broadcast android:name="android.net.wifi.action.WIFI_SCAN_AVAILABLE" />
<protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
<protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" />
<protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" />
@@ -3980,6 +3980,13 @@
<permission android:name="android.permission.BACKUP"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to make modifications to device settings such that these
+ modifications will be overridden by settings restore..
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE"
+ android:protectionLevel="signature|setup" />
+
<!-- @SystemApi Allows application to manage
{@link android.security.keystore.recovery.RecoveryController}.
<p>Not for use by third-party applications.
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_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6435cdd..94f3b8a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2005,6 +2005,15 @@
<attr name="maxSdkVersion" />
</declare-styleable>
+ <!-- The <code>extension-sdk</code> tag is a child of the <uses-sdk> tag,
+ and specifies required extension sdk features. -->
+ <declare-styleable name="AndroidManifestExtensionSdk">
+ <!-- The extension SDK version that this tag refers to. -->
+ <attr name="sdkVersion" format="integer" />
+ <!-- The minimum version of the extension SDK this application requires.-->
+ <attr name="minExtensionVersion" format="integer" />
+ </declare-styleable>
+
<!-- The <code>library</code> tag declares that this apk is providing itself
as a shared library for other applications to use. It can only be used
with apks that are built in to the system image. Other apks can link to
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6f554f02..a78195b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4294,4 +4294,7 @@
<!-- Class name of the custom country detector to be used. -->
<string name="config_customCountryDetector" translatable="false">com.android.server.location.ComprehensiveCountryDetector</string>
+
+ <!-- Package name of the required service extension package. -->
+ <string name="config_servicesExtensionPackage" translatable="false">android.ext.services</string>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9860830..36dbcbd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3008,6 +3008,10 @@
<public name="supportsInlineSuggestions" />
<public name="crossProfile" />
<public name="canTakeScreenshot"/>
+ <!-- @hide @SystemApi -->
+ <public name="sdkVersion" />
+ <!-- @hide @SystemApi -->
+ <public name="minExtensionVersion" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f977ea8..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>
@@ -5219,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 30dbfc7..669b41e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3235,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"/>
@@ -3242,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" />
@@ -3649,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" />
@@ -3816,4 +3818,5 @@
<java-symbol type="string" name="capability_desc_canTakeScreenshot" />
<java-symbol type="string" name="capability_title_canTakeScreenshot" />
+ <java-symbol type="string" name="config_servicesExtensionPackage" />
</resources>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 2df6d1c..3836d6f 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -84,6 +84,11 @@
":FrameworksCoreTests_install_split_feature_a",
":FrameworksCoreTests_install_use_perm_good",
":FrameworksCoreTests_install_uses_feature",
+ ":FrameworksCoreTests_install_uses_sdk_0",
+ ":FrameworksCoreTests_install_uses_sdk_q0",
+ ":FrameworksCoreTests_install_uses_sdk_r",
+ ":FrameworksCoreTests_install_uses_sdk_r0",
+ ":FrameworksCoreTests_install_uses_sdk_r5",
":FrameworksCoreTests_install_verifier_bad",
":FrameworksCoreTests_install_verifier_good",
":FrameworksCoreTests_keyset_permdef_sa_unone",
diff --git a/core/tests/coretests/apks/install_uses_sdk/Android.bp b/core/tests/coretests/apks/install_uses_sdk/Android.bp
new file mode 100644
index 0000000..92b09ed
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/Android.bp
@@ -0,0 +1,39 @@
+android_test_helper_app {
+ name: "FrameworksCoreTests_install_uses_sdk_r0",
+ defaults: ["FrameworksCoreTests_apks_defaults"],
+ manifest: "AndroidManifest-r0.xml",
+
+ srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+ name: "FrameworksCoreTests_install_uses_sdk_r5",
+ defaults: ["FrameworksCoreTests_apks_defaults"],
+ manifest: "AndroidManifest-r5.xml",
+
+ srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+ name: "FrameworksCoreTests_install_uses_sdk_q0",
+ defaults: ["FrameworksCoreTests_apks_defaults"],
+ manifest: "AndroidManifest-q0.xml",
+
+ srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+ name: "FrameworksCoreTests_install_uses_sdk_r",
+ defaults: ["FrameworksCoreTests_apks_defaults"],
+ manifest: "AndroidManifest-r.xml",
+
+ srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+ name: "FrameworksCoreTests_install_uses_sdk_0",
+ defaults: ["FrameworksCoreTests_apks_defaults"],
+ manifest: "AndroidManifest-0.xml",
+
+ srcs: ["**/*.java"],
+}
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml
new file mode 100644
index 0000000..634228b
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_uses_sdk">
+
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+ <!-- This is invalid, because there is no sdk version specified -->
+ <extension-sdk android:minExtensionVersion="5" />
+ </uses-sdk>
+
+ <application>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml
new file mode 100644
index 0000000..8994966
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_uses_sdk">
+
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+ <!-- This fails because 29 doesn't have an extension sdk -->
+ <extension-sdk android:sdkVersion="29" android:minExtensionVersion="0" />
+ </uses-sdk>
+
+ <application>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml
new file mode 100644
index 0000000..0d0d8b9
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_uses_sdk">
+
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+ <!-- This is invalid, because there is no minimum extension version specified -->
+ <extension-sdk android:sdkVersion="10000" />
+ </uses-sdk>
+
+ <application>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml
new file mode 100644
index 0000000..a987afa
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_uses_sdk">
+
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+ <extension-sdk android:sdkVersion="10000" android:minExtensionVersion="0" />
+ </uses-sdk>
+
+ <application>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml
new file mode 100644
index 0000000..9860096
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_uses_sdk">
+
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+ <!-- This will fail to install, because minExtensionVersion is not met -->
+ <extension-sdk android:sdkVersion="10000" android:minExtensionVersion="5" />
+ </uses-sdk>
+
+ <application>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml b/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/src/android/app/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/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/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index 4402190..dfd762b 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.apex.ApexInfo;
import android.content.Context;
@@ -486,4 +487,34 @@
assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0);
assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
}
+
+ @Test
+ public void testUsesSdk() throws Exception {
+ parsePackage("install_uses_sdk.apk_r0", R.raw.install_uses_sdk_r0, x -> x);
+ try {
+ parsePackage("install_uses_sdk.apk_r5", R.raw.install_uses_sdk_r5, x -> x);
+ fail("Expected parsing exception due to incompatible extension SDK version");
+ } catch (PackageParser.PackageParserException expected) {
+ assertEquals(PackageManager.INSTALL_FAILED_OLDER_SDK, expected.error);
+ }
+ try {
+ parsePackage("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0, x -> x);
+ fail("Expected parsing exception due to non-existent extension SDK");
+ } catch (PackageParser.PackageParserException expected) {
+ assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
+ }
+ try {
+ parsePackage("install_uses_sdk.apk_r", R.raw.install_uses_sdk_r, x -> x);
+ fail("Expected parsing exception due to unspecified extension SDK version");
+ } catch (PackageParser.PackageParserException expected) {
+ assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
+ }
+ try {
+ parsePackage("install_uses_sdk.apk_0", R.raw.install_uses_sdk_0, x -> x);
+ fail("Expected parsing exception due to unspecified extension SDK");
+ } catch (PackageParser.PackageParserException expected) {
+ assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
+ }
+
+ }
}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 1db96b1..628f7ec 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -16,9 +16,13 @@
package android.view;
+import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
+import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -40,12 +44,15 @@
import android.view.WindowInsets.Type;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
+import android.view.test.InsetsModeSession;
import android.widget.TextView;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -69,6 +76,17 @@
private SurfaceSession mSession = new SurfaceSession();
private SurfaceControl mLeash;
private ViewRootImpl mViewRoot;
+ private static InsetsModeSession sInsetsModeSession;
+
+ @BeforeClass
+ public static void setupOnce() {
+ sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ sInsetsModeSession.close();
+ }
@Before
public void setup() {
@@ -86,6 +104,11 @@
}
mController = new InsetsController(mViewRoot);
final Rect rect = new Rect(5, 5, 5, 5);
+ mController.getState().getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 10));
+ mController.getState().getSource(ITYPE_NAVIGATION_BAR).setFrame(
+ new Rect(0, 90, 100, 100));
+ mController.getState().getSource(ITYPE_IME).setFrame(new Rect(0, 50, 100, 100));
+ mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100));
mController.calculateInsets(
false,
false,
@@ -93,7 +116,6 @@
Insets.of(10, 10, 10, 10), rect, rect, rect, rect),
rect, rect, SOFT_INPUT_ADJUST_RESIZE);
mController.onFrameChanged(new Rect(0, 0, 100, 100));
- mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -205,18 +227,24 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
int types = Type.navigationBars() | Type.systemBars();
- // test show select types.
- mController.show(types);
+ // test hide select types.
+ mController.hide(types);
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimation();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test hide all
- mController.hide(types);
+ mController.show(types);
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimation();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -271,30 +299,38 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// start two animations and see if previous is cancelled and final state is reached.
- mController.show(Type.navigationBars());
- mController.show(Type.systemBars());
- mController.cancelExistingAnimation();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
-
mController.hide(Type.navigationBars());
mController.hide(Type.systemBars());
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimation();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ mController.show(Type.navigationBars());
+ mController.show(Type.systemBars());
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
+ mController.cancelExistingAnimation();
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+
int types = Type.navigationBars() | Type.systemBars();
// show two at a time and hide one by one.
mController.show(types);
mController.hide(Type.navigationBars());
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimation();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.hide(Type.systemBars());
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimation();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/host/AndroidTest.xml
index 11eadf1a..087b731 100644
--- a/core/tests/overlaytests/remount/host/AndroidTest.xml
+++ b/core/tests/overlaytests/remount/host/AndroidTest.xml
@@ -19,9 +19,6 @@
<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" />
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
deleted file mode 100644
index 84af187..0000000
--- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java
+++ /dev/null
@@ -1,114 +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.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
index 4939e16..06b2ac8 100644
--- 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
@@ -16,17 +16,21 @@
package com.android.overlaytest.remounted;
+import static org.junit.Assert.assertTrue;
+
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
-import java.util.Collections;
-import java.util.List;
-
@RunWith(DeviceJUnit4ClassRunner.class)
-public class OverlaySharedLibraryTest extends OverlayHostTest {
+public class OverlaySharedLibraryTest extends BaseHostJUnit4Test {
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 =
@@ -38,6 +42,17 @@
private static final String SHARED_LIBRARY_OVERLAY_PACKAGE =
"com.android.overlaytest.remounted.shared_library.overlay";
+ public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+ public final SystemPreparer preparer = new SystemPreparer(temporaryFolder, this::getDevice);
+
+ @Rule
+ public final RuleChain ruleChain = RuleChain.outerRule(temporaryFolder).around(preparer);
+
+ @Before
+ public void startBefore() throws DeviceNotAvailableException {
+ getDevice().waitForDeviceAvailable();
+ }
+
@Test
public void testSharedLibrary() throws Exception {
final String targetResource = resourceName(TARGET_PACKAGE, "bool",
@@ -45,23 +60,20 @@
final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
"shared_library_overlaid");
- mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+ preparer.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");
+ assertResource(targetResource, "false");
+ assertResource(libraryResource, "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");
+ preparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
+ assertResource(targetResource, "true");
+ assertResource(libraryResource, "true");
}
@Test
@@ -71,20 +83,27 @@
final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
"shared_library_overlaid");
- mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+ preparer.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");
+ assertResource(targetResource, "true");
+ assertResource(libraryResource, "true");
}
- private void retrieveResource(List<String> requiredOverlayPaths, String... resourceNames)
+ /** 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);
+ }
+
+ void assertResource(String resourceName, String expectedValue)
throws DeviceNotAvailableException {
- retrieveResource(TARGET_PACKAGE, requiredOverlayPaths, resourceNames);
+ final String result = getDevice().executeShellCommand(
+ String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName));
+ assertTrue(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result),
+ result.equals(expectedValue + "\n") ||
+ result.endsWith("-> " + expectedValue + "\n"));
}
}
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
index 7028f2f..8696091 100644
--- 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
@@ -38,8 +38,7 @@
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;
+ private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000;
// The paths of the files pushed onto the device through this rule.
private ArrayList<String> mPushedFiles = new ArrayList<>();
@@ -59,6 +58,7 @@
SystemPreparer pushResourceFile(String resourcePath,
String outputPath) throws DeviceNotAvailableException, IOException {
final ITestDevice device = mDeviceProvider.getDevice();
+ device.executeAdbCommand("remount");
assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath));
mPushedFiles.add(outputPath);
return this;
@@ -77,7 +77,7 @@
/** Sets the enable state of an overlay pacakage. */
SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
- throws ExecutionException, TimeoutException {
+ throws ExecutionException, DeviceNotAvailableException {
final ITestDevice device = mDeviceProvider.getDevice();
// Wait for the overlay to change its enabled state.
@@ -86,8 +86,10 @@
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)) {
+ final String result = device.executeShellCommand("cmd overlay dump " + packageName);
+ final int startIndex = result.indexOf("mIsEnabled");
+ final int endIndex = result.indexOf('\n', startIndex);
+ if (result.substring(startIndex, endIndex).contains((enabled) ? "true" : "false")) {
return true;
}
}
@@ -98,6 +100,8 @@
try {
enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException ignored) {
+ } catch (TimeoutException e) {
+ throw new IllegalStateException(device.executeShellCommand("cmd overlay list"));
}
return this;
@@ -106,14 +110,7 @@
/** 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) {
- }
-
+ device.reboot();
return this;
}
@@ -141,12 +138,14 @@
protected void after() {
final ITestDevice device = mDeviceProvider.getDevice();
try {
+ device.executeAdbCommand("remount");
for (final String file : mPushedFiles) {
device.deleteFile(file);
}
for (final String packageName : mInstalledPackages) {
device.uninstallPackage(packageName);
}
+ device.reboot();
} catch (DeviceNotAvailableException e) {
Assert.fail(e.toString());
}
diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/target/AndroidManifest.xml
index 32fec43..dc07dca 100644
--- a/core/tests/overlaytests/remount/target/AndroidManifest.xml
+++ b/core/tests/overlaytests/remount/target/AndroidManifest.xml
@@ -23,8 +23,4 @@
<uses-library android:name="com.android.overlaytest.remounted.shared_library"
android:required="true" />
</application>
-
- <instrumentation android:name="com.android.overlaytest.remounted.target.ResourceRetrievalRunner"
- android:targetPackage="com.android.overlaytest.remounted.target"
- android:label="Remounted system RRO tests" />
</manifest>
diff --git a/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
deleted file mode 100644
index 2e4c211..0000000
--- a/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
+++ /dev/null
@@ -1,140 +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.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/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/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 1d735af..40de83a 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -22,7 +22,6 @@
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
<permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
- <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
<permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
<permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
<permission name="android.permission.CONTROL_VPN"/>
@@ -38,6 +37,7 @@
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
+ <permission name="android.permission.OBSERVE_NETWORK_POLICY"/>
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.READ_DREAM_STATE"/>
<permission name="android.permission.READ_FRAME_BUFFER"/>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 0574775..877ef26 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -180,7 +180,6 @@
<assign-permission name="android.permission.ACCESS_LOWPAN_STATE" uid="lowpan" />
<assign-permission name="android.permission.MANAGE_LOWPAN_INTERFACES" uid="lowpan" />
- <assign-permission name="android.permission.BATTERY_STATS" uid="statsd" />
<assign-permission name="android.permission.DUMP" uid="statsd" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="statsd" />
<assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
diff --git a/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/libs/hwui/Android.bp b/libs/hwui/Android.bp
index a7f8cc4..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: [
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 12681ae..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();
}
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index b93759f..c445885 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -108,10 +108,26 @@
}
}
+// FIXME: Share with the version in android_bitmap.cpp?
+// Skia's SkNamedGamut::kDCIP3 is based on a white point of D65. This gamut
+// matches the white point used by ColorSpace.Named.DCIP3.
+static constexpr skcms_Matrix3x3 kDCIP3 = {{
+ {0.486143, 0.323835, 0.154234},
+ {0.226676, 0.710327, 0.0629966},
+ {0.000800549, 0.0432385, 0.78275},
+}};
+
sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) {
if (dataspace == HAL_DATASPACE_UNKNOWN) {
return SkColorSpace::MakeSRGB();
}
+ if (dataspace == HAL_DATASPACE_DCI_P3) {
+ // This cannot be handled by the switch statements below because it
+ // needs to use the locally-defined kDCIP3 gamut, rather than the one in
+ // Skia (SkNamedGamut), which is used for other data spaces with
+ // HAL_DATASPACE_STANDARD_DCI_P3 (e.g. HAL_DATASPACE_DISPLAY_P3).
+ return SkColorSpace::MakeRGB({2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, kDCIP3);
+ }
skcms_Matrix3x3 gamut;
switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
@@ -152,10 +168,12 @@
return SkColorSpace::MakeRGB({2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
case HAL_DATASPACE_TRANSFER_GAMMA2_8:
return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
+ case HAL_DATASPACE_TRANSFER_ST2084:
+ return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
+ case HAL_DATASPACE_TRANSFER_SMPTE_170M:
+ return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut);
case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
return nullptr;
- case HAL_DATASPACE_TRANSFER_SMPTE_170M:
- case HAL_DATASPACE_TRANSFER_ST2084:
case HAL_DATASPACE_TRANSFER_HLG:
default:
ALOGV("Unsupported Gamma: %d", dataspace);
diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
index 751bb6a..127d00c 100644
--- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
@@ -110,7 +110,6 @@
* Logs the status of a location report received from the HAL
*/
public void logReceivedLocationStatus(boolean isSuccessful) {
- StatsLog.write(StatsLog.GPS_LOCATION_STATUS_REPORTED, isSuccessful);
if (!isSuccessful) {
mLocationFailureStatistics.addItem(1.0);
return;
@@ -127,7 +126,6 @@
DEFAULT_TIME_BETWEEN_FIXES_MILLISECS, desiredTimeBetweenFixesMilliSeconds)) - 1;
if (numReportMissed > 0) {
for (int i = 0; i < numReportMissed; i++) {
- StatsLog.write(StatsLog.GPS_LOCATION_STATUS_REPORTED, false);
mLocationFailureStatistics.addItem(1.0);
}
}
@@ -138,7 +136,6 @@
*/
public void logTimeToFirstFixMilliSecs(int timeToFirstFixMilliSeconds) {
mTimeToFirstFixSecStatistics.addItem((double) (timeToFirstFixMilliSeconds / 1000));
- StatsLog.write(StatsLog.GPS_TIME_TO_FIRST_FIX_REPORTED, timeToFirstFixMilliSeconds);
}
/**
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index ece5335..8cd3c6e 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -598,6 +598,11 @@
private boolean mMuteHapticChannels = true;
private HashSet<String> mTags = new HashSet<String>();
private Bundle mBundle;
+ private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
+
+ private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
+ private static final int PRIVACY_SENSITIVE_DISABLED = 0;
+ private static final int PRIVACY_SENSITIVE_ENABLED = 1;
/**
* Constructs a new Builder with the defaults.
@@ -638,11 +643,20 @@
if (mMuteHapticChannels) {
aa.mFlags |= FLAG_MUTE_HAPTIC;
}
- // capturing for camcorder of communication is private by default to
- // reflect legacy behavior
- if (aa.mSource == MediaRecorder.AudioSource.VOICE_COMMUNICATION
- || aa.mSource == MediaRecorder.AudioSource.CAMCORDER) {
+
+ if (mPrivacySensitive == PRIVACY_SENSITIVE_DEFAULT) {
+ // capturing for camcorder or communication is private by default to
+ // reflect legacy behavior
+ if (mSource == MediaRecorder.AudioSource.VOICE_COMMUNICATION
+ || mSource == MediaRecorder.AudioSource.CAMCORDER) {
+ aa.mFlags |= FLAG_CAPTURE_PRIVATE;
+ } else {
+ aa.mFlags &= ~FLAG_CAPTURE_PRIVATE;
+ }
+ } else if (mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED) {
aa.mFlags |= FLAG_CAPTURE_PRIVATE;
+ } else {
+ aa.mFlags &= ~FLAG_CAPTURE_PRIVATE;
}
aa.mTags = (HashSet<String>) mTags.clone();
aa.mFormattedTags = TextUtils.join(";", mTags);
@@ -967,6 +981,20 @@
mMuteHapticChannels = muted;
return this;
}
+
+ /**
+ * @hide
+ * Indicates if an AudioRecord build with this AudioAttributes is privacy sensitive or not.
+ * See {@link AudioRecord.Builder#setPrivacySensitive(boolean)}.
+ * @param privacySensitive True if capture must be marked as privacy sensitive,
+ * false otherwise.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setPrivacySensitive(boolean privacySensitive) {
+ mPrivacySensitive =
+ privacySensitive ? PRIVACY_SENSITIVE_ENABLED : PRIVACY_SENSITIVE_DISABLED;
+ return this;
+ }
};
@Override
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index fd3523d..4d26b8d 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -315,7 +315,7 @@
* @hide
* Class constructor with {@link AudioAttributes} and {@link AudioFormat}.
* @param attributes a non-null {@link AudioAttributes} instance. Use
- * {@link AudioAttributes.Builder#setAudioSource(int)} for configuring the audio
+ * {@link AudioAttributes.Builder#setCapturePreset(int)} for configuring the audio
* source for this instance.
* @param format a non-null {@link AudioFormat} instance describing the format of the data
* that will be recorded through this AudioRecord. See {@link AudioFormat.Builder} for
@@ -754,17 +754,10 @@
"Cannot request private capture with source: " + source);
}
- int flags = mAttributes.getAllFlags();
- if (mPrivacySensitive == PRIVACY_SENSITIVE_DISABLED) {
- flags &= ~AudioAttributes.FLAG_CAPTURE_PRIVATE;
- } else if (mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED) {
- flags |= AudioAttributes.FLAG_CAPTURE_PRIVATE;
- }
- if (flags != mAttributes.getAllFlags()) {
- mAttributes = new AudioAttributes.Builder(mAttributes)
- .replaceFlags(flags)
- .build();
- }
+ mAttributes = new AudioAttributes.Builder(mAttributes)
+ .setInternalCapturePreset(source)
+ .setPrivacySensitive(mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED)
+ .build();
}
try {
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 021e23e..239dfed 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -134,59 +134,204 @@
*/
public static final int DEVICE_TYPE_BLUETOOTH = 3;
- @NonNull
final String mId;
- @Nullable
- final String mProviderId;
- @NonNull
final CharSequence mName;
- @Nullable
- final CharSequence mDescription;
- @Nullable
- final @ConnectionState int mConnectionState;
- @Nullable
- final Uri mIconUri;
- @Nullable
- final String mClientPackageName;
- @NonNull
final List<String> mFeatures;
+ @DeviceType
+ final int mDeviceType;
+ final Uri mIconUri;
+ final CharSequence mDescription;
+ @ConnectionState
+ final int mConnectionState;
+ final String mClientPackageName;
final int mVolume;
final int mVolumeMax;
final int mVolumeHandling;
- final @DeviceType int mDeviceType;
- @Nullable
final Bundle mExtras;
+ final String mProviderId;
MediaRoute2Info(@NonNull Builder builder) {
mId = builder.mId;
- mProviderId = builder.mProviderId;
mName = builder.mName;
+ mFeatures = builder.mFeatures;
+ mDeviceType = builder.mDeviceType;
+ mIconUri = builder.mIconUri;
mDescription = builder.mDescription;
mConnectionState = builder.mConnectionState;
- mIconUri = builder.mIconUri;
mClientPackageName = builder.mClientPackageName;
- mFeatures = builder.mFeatures;
- mVolume = builder.mVolume;
- mVolumeMax = builder.mVolumeMax;
mVolumeHandling = builder.mVolumeHandling;
- mDeviceType = builder.mDeviceType;
+ mVolumeMax = builder.mVolumeMax;
+ mVolume = builder.mVolume;
mExtras = builder.mExtras;
+ mProviderId = builder.mProviderId;
}
MediaRoute2Info(@NonNull Parcel in) {
mId = in.readString();
- mProviderId = in.readString();
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mFeatures = in.createStringArrayList();
+ mDeviceType = in.readInt();
+ mIconUri = in.readParcelable(null);
mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mConnectionState = in.readInt();
- mIconUri = in.readParcelable(null);
mClientPackageName = in.readString();
- mFeatures = in.createStringArrayList();
- mVolume = in.readInt();
- mVolumeMax = in.readInt();
mVolumeHandling = in.readInt();
- mDeviceType = in.readInt();
+ mVolumeMax = in.readInt();
+ mVolume = in.readInt();
mExtras = in.readBundle();
+ mProviderId = in.readString();
+ }
+
+ /**
+ * Gets the id of the route. The routes which are given by {@link MediaRouter2} will have
+ * unique IDs.
+ * <p>
+ * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
+ * can be different from what was set in {@link MediaRoute2ProviderService}.
+ *
+ * @see Builder#Builder(String, CharSequence)
+ */
+ @NonNull
+ public String getId() {
+ if (mProviderId != null) {
+ return toUniqueId(mProviderId, mId);
+ } else {
+ return mId;
+ }
+ }
+
+ /**
+ * Gets the user-visible name of the route.
+ */
+ @NonNull
+ public CharSequence getName() {
+ return mName;
+ }
+
+ /**
+ * Gets the supported features of the route.
+ */
+ @NonNull
+ public List<String> getFeatures() {
+ return mFeatures;
+ }
+
+ /**
+ * Gets the type of the receiver device associated with this route.
+ *
+ * @return The type of the receiver device associated with this route:
+ * {@link #DEVICE_TYPE_REMOTE_TV}, {@link #DEVICE_TYPE_REMOTE_SPEAKER},
+ * {@link #DEVICE_TYPE_BLUETOOTH}.
+ */
+ @DeviceType
+ public int getDeviceType() {
+ return mDeviceType;
+ }
+
+ /**
+ * Gets the URI of the icon representing this route.
+ * <p>
+ * This icon will be used in picker UIs if available.
+ *
+ * @return The URI of the icon representing this route, or null if none.
+ */
+ @Nullable
+ public Uri getIconUri() {
+ return mIconUri;
+ }
+
+ /**
+ * Gets the user-visible description of the route.
+ */
+ @Nullable
+ public CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Gets the connection state of the route.
+ *
+ * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
+ * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
+ */
+ @ConnectionState
+ public int getConnectionState() {
+ return mConnectionState;
+ }
+
+ /**
+ * Gets the package name of the client that uses the route.
+ * Returns null if no clients use this route.
+ * @hide
+ */
+ @Nullable
+ public String getClientPackageName() {
+ return mClientPackageName;
+ }
+
+ /**
+ * Gets information about how volume is handled on the route.
+ *
+ * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
+ */
+ public int getVolumeHandling() {
+ return mVolumeHandling;
+ }
+
+ /**
+ * Gets the maximum volume of the route.
+ */
+ public int getVolumeMax() {
+ return mVolumeMax;
+ }
+
+ /**
+ * Gets the current volume of the route. This may be invalid if the route is not selected.
+ */
+ public int getVolume() {
+ return mVolume;
+ }
+
+ @Nullable
+ public Bundle getExtras() {
+ return mExtras == null ? null : new Bundle(mExtras);
+ }
+
+ /**
+ * Gets the original id set by {@link Builder#Builder(String, CharSequence)}.
+ * @hide
+ */
+ @NonNull
+ public String getOriginalId() {
+ return mId;
+ }
+
+ /**
+ * Gets the provider id of the route. It is assigned automatically by
+ * {@link com.android.server.media.MediaRouterService}.
+ *
+ * @return provider id of the route or null if it's not set.
+ * @hide
+ */
+ @Nullable
+ public String getProviderId() {
+ return mProviderId;
+ }
+
+ /**
+ * Returns if the route has at least one of the specified route features.
+ *
+ * @param features the list of route features to consider
+ * @return true if the route has at least one feature in the list
+ */
+ public boolean hasAnyFeatures(@NonNull Collection<String> features) {
+ Objects.requireNonNull(features, "features must not be null");
+ for (String feature : features) {
+ if (getFeatures().contains(feature)) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -213,172 +358,49 @@
return false;
}
MediaRoute2Info other = (MediaRoute2Info) obj;
+
+ // Note: mExtras is not included.
return Objects.equals(mId, other.mId)
- && Objects.equals(mProviderId, other.mProviderId)
&& Objects.equals(mName, other.mName)
+ && Objects.equals(mFeatures, other.mFeatures)
+ && (mDeviceType == other.mDeviceType)
+ && Objects.equals(mIconUri, other.mIconUri)
&& Objects.equals(mDescription, other.mDescription)
&& (mConnectionState == other.mConnectionState)
- && Objects.equals(mIconUri, other.mIconUri)
&& Objects.equals(mClientPackageName, other.mClientPackageName)
- && Objects.equals(mFeatures, other.mFeatures)
- && (mVolume == other.mVolume)
- && (mVolumeMax == other.mVolumeMax)
&& (mVolumeHandling == other.mVolumeHandling)
- && (mDeviceType == other.mDeviceType)
- //TODO: This will be evaluated as false in most cases. Try not to.
- && Objects.equals(mExtras, other.mExtras);
+ && (mVolumeMax == other.mVolumeMax)
+ && (mVolume == other.mVolume)
+ && Objects.equals(mProviderId, other.mProviderId);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri,
- mFeatures, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
+ // Note: mExtras is not included.
+ return Objects.hash(mId, mName, mFeatures, mDeviceType, mIconUri, mDescription,
+ mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume,
+ mProviderId);
}
- /**
- * Gets the id of the route. The routes which are given by {@link MediaRouter2} will have
- * unique IDs.
- * <p>
- * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
- * can be different from what was set in {@link MediaRoute2ProviderService}.
- *
- * @see Builder#Builder(String, CharSequence)
- */
- @NonNull
- public String getId() {
- if (mProviderId != null) {
- return toUniqueId(mProviderId, mId);
- } else {
- return mId;
- }
- }
-
- /**
- * Gets the original id set by {@link Builder#Builder(String, CharSequence)}.
- * @hide
- */
- @NonNull
- public String getOriginalId() {
- return mId;
- }
-
- /**
- * Gets the provider id of the route. It is assigned automatically by
- * {@link com.android.server.media.MediaRouterService}.
- *
- * @return provider id of the route or null if it's not set.
- * @hide
- */
- @Nullable
- public String getProviderId() {
- return mProviderId;
- }
-
- @NonNull
- public CharSequence getName() {
- return mName;
- }
-
- @Nullable
- public CharSequence getDescription() {
- return mDescription;
- }
-
- /**
- * Gets the connection state of the route.
- *
- * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
- * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
- */
- @ConnectionState
- public int getConnectionState() {
- return mConnectionState;
- }
-
- /**
- * Gets the URI of the icon representing this route.
- * <p>
- * This icon will be used in picker UIs if available.
- *
- * @return The URI of the icon representing this route, or null if none.
- */
- @Nullable
- public Uri getIconUri() {
- return mIconUri;
- }
-
- /**
- * Gets the package name of the client that uses the route.
- * Returns null if no clients use this.
- * @hide
- */
- @Nullable
- public String getClientPackageName() {
- return mClientPackageName;
- }
-
- /**
- * Gets the supported categories of the route.
- */
- @NonNull
- public List<String> getFeatures() {
- return mFeatures;
- }
-
- /**
- * Gets the type of the receiver device associated with this route.
- *
- * @return The type of the receiver device associated with this route:
- * {@link #DEVICE_TYPE_REMOTE_TV}, {@link #DEVICE_TYPE_REMOTE_SPEAKER},
- * {@link #DEVICE_TYPE_BLUETOOTH}.
- */
- @DeviceType
- public int getDeviceType() {
- return mDeviceType;
- }
-
- /**
- * Gets the current volume of the route. This may be invalid if the route is not selected.
- */
- public int getVolume() {
- return mVolume;
- }
-
- /**
- * Gets the maximum volume of the route.
- */
- public int getVolumeMax() {
- return mVolumeMax;
- }
-
- /**
- * Gets information about how volume is handled on the route.
- *
- * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
- */
- public int getVolumeHandling() {
- return mVolumeHandling;
- }
-
- @Nullable
- public Bundle getExtras() {
- return mExtras;
- }
-
- /**
- * Returns if the route has at least one of the specified route features.
- *
- * @param features the list of route features to consider
- * @return true if the route has at least one feature in the list
- */
- public boolean hasAnyFeatures(@NonNull Collection<String> features) {
- Objects.requireNonNull(features, "features must not be null");
- for (String feature : features) {
- if (getFeatures().contains(feature)) {
- return true;
- }
- }
- return false;
+ @Override
+ public String toString() {
+ // Note: mExtras is not printed here.
+ StringBuilder result = new StringBuilder()
+ .append("MediaRoute2Info{ ")
+ .append("id=").append(getId())
+ .append(", name=").append(getName())
+ .append(", features=").append(getFeatures())
+ .append(", deviceType=").append(getDeviceType())
+ .append(", iconUri=").append(getIconUri())
+ .append(", description=").append(getDescription())
+ .append(", connectionState=").append(getConnectionState())
+ .append(", clientPackageName=").append(getClientPackageName())
+ .append(", volumeHandling=").append(getVolumeHandling())
+ .append(", volumeMax=").append(getVolumeMax())
+ .append(", volume=").append(getVolume())
+ .append(", providerId=").append(getProviderId())
+ .append(" }");
+ return result.toString();
}
@Override
@@ -389,36 +411,18 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mId);
- dest.writeString(mProviderId);
TextUtils.writeToParcel(mName, dest, flags);
+ dest.writeStringList(mFeatures);
+ dest.writeInt(mDeviceType);
+ dest.writeParcelable(mIconUri, flags);
TextUtils.writeToParcel(mDescription, dest, flags);
dest.writeInt(mConnectionState);
- dest.writeParcelable(mIconUri, flags);
dest.writeString(mClientPackageName);
- dest.writeStringList(mFeatures);
- dest.writeInt(mVolume);
- dest.writeInt(mVolumeMax);
dest.writeInt(mVolumeHandling);
- dest.writeInt(mDeviceType);
+ dest.writeInt(mVolumeMax);
+ dest.writeInt(mVolume);
dest.writeBundle(mExtras);
- }
-
- @Override
- public String toString() {
- StringBuilder result = new StringBuilder()
- .append("MediaRouteInfo{ ")
- .append("id=").append(getId())
- .append(", name=").append(getName())
- .append(", description=").append(getDescription())
- .append(", connectionState=").append(getConnectionState())
- .append(", iconUri=").append(getIconUri())
- .append(", volume=").append(getVolume())
- .append(", volumeMax=").append(getVolumeMax())
- .append(", volumeHandling=").append(getVolumeHandling())
- .append(", deviceType=").append(getDeviceType())
- .append(", providerId=").append(getProviderId())
- .append(" }");
- return result.toString();
+ dest.writeString(mProviderId);
}
/**
@@ -426,20 +430,21 @@
*/
public static final class Builder {
final String mId;
- String mProviderId;
final CharSequence mName;
+ final List<String> mFeatures;
+
+ @DeviceType
+ int mDeviceType = DEVICE_TYPE_UNKNOWN;
+ Uri mIconUri;
CharSequence mDescription;
@ConnectionState
int mConnectionState;
- Uri mIconUri;
String mClientPackageName;
- List<String> mFeatures;
- int mVolume;
- int mVolumeMax;
int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
- @DeviceType
- int mDeviceType = DEVICE_TYPE_UNKNOWN;
+ int mVolumeMax;
+ int mVolume;
Bundle mExtras;
+ String mProviderId;
/**
* Constructor for builder to create {@link MediaRoute2Info}.
@@ -448,8 +453,8 @@
* obtained from {@link MediaRouter2} can be different from what was set in
* {@link MediaRoute2ProviderService}.
* </p>
- * @param id
- * @param name
+ * @param id The ID of the route. Must not be empty.
+ * @param name The user-visible name of the route.
*/
public Builder(@NonNull String id, @NonNull CharSequence name) {
if (TextUtils.isEmpty(id)) {
@@ -463,40 +468,91 @@
mFeatures = new ArrayList<>();
}
+ /**
+ * Constructor for builder to create {@link MediaRoute2Info} with
+ * existing {@link MediaRoute2Info} instance.
+ *
+ * @param routeInfo the existing instance to copy data from.
+ */
public Builder(@NonNull MediaRoute2Info routeInfo) {
- if (routeInfo == null) {
- throw new IllegalArgumentException("route info must not be null");
- }
+ Objects.requireNonNull(routeInfo, "routeInfo must not be null");
+
mId = routeInfo.mId;
mName = routeInfo.mName;
-
- if (!TextUtils.isEmpty(routeInfo.mProviderId)) {
- setProviderId(routeInfo.mProviderId);
- }
+ mFeatures = new ArrayList<>(routeInfo.mFeatures);
+ mDeviceType = routeInfo.mDeviceType;
+ mIconUri = routeInfo.mIconUri;
mDescription = routeInfo.mDescription;
mConnectionState = routeInfo.mConnectionState;
- mIconUri = routeInfo.mIconUri;
- setClientPackageName(routeInfo.mClientPackageName);
- mFeatures = new ArrayList<>(routeInfo.mFeatures);
- setVolume(routeInfo.mVolume);
- setVolumeMax(routeInfo.mVolumeMax);
- setVolumeHandling(routeInfo.mVolumeHandling);
- setDeviceType(routeInfo.mDeviceType);
+ mClientPackageName = routeInfo.mClientPackageName;
+ mVolumeHandling = routeInfo.mVolumeHandling;
+ mVolumeMax = routeInfo.mVolumeMax;
+ mVolume = routeInfo.mVolume;
if (routeInfo.mExtras != null) {
mExtras = new Bundle(routeInfo.mExtras);
}
+ mProviderId = routeInfo.mProviderId;
}
/**
- * Sets the provider id of the route.
- * @hide
+ * Adds a feature for the route.
*/
@NonNull
- public Builder setProviderId(@NonNull String providerId) {
- if (TextUtils.isEmpty(providerId)) {
- throw new IllegalArgumentException("providerId 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");
}
- mProviderId = providerId;
+ mFeatures.add(feature);
+ return this;
+ }
+
+ /**
+ * Adds features for the route. A route must support at least one route type.
+ */
+ @NonNull
+ public Builder addFeatures(@NonNull Collection<String> features) {
+ Objects.requireNonNull(features, "features must not be null");
+ for (String feature : features) {
+ addFeature(feature);
+ }
+ return this;
+ }
+
+ /**
+ * Clears the features of the route. A route must support at least one route type.
+ */
+ @NonNull
+ public Builder clearFeatures() {
+ mFeatures.clear();
+ return this;
+ }
+
+ /**
+ * Sets the route's device type.
+ */
+ @NonNull
+ public Builder setDeviceType(@DeviceType int deviceType) {
+ mDeviceType = deviceType;
+ return this;
+ }
+
+ /**
+ * Sets the URI of the icon representing this route.
+ * <p>
+ * This icon will be used in picker UIs if available.
+ * </p><p>
+ * The URI must be one of the following formats:
+ * <ul>
+ * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+ * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
+ * </li>
+ * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+ * </ul>
+ * </p>
+ */
+ @NonNull
+ public Builder setIconUri(@Nullable Uri iconUri) {
+ mIconUri = iconUri;
return this;
}
@@ -523,26 +579,6 @@
}
/**
- * Sets the URI of the icon representing this route.
- * <p>
- * This icon will be used in picker UIs if available.
- * </p><p>
- * The URI must be one of the following formats:
- * <ul>
- * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
- * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
- * </li>
- * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
- * </ul>
- * </p>
- */
- @NonNull
- public Builder setIconUri(@Nullable Uri iconUri) {
- mIconUri = iconUri;
- return this;
- }
-
- /**
* Sets the package name of the app using the route.
*/
@NonNull
@@ -552,57 +588,6 @@
}
/**
- * Clears the features of the route.
- */
- @NonNull
- public Builder clearFeatures() {
- mFeatures = new ArrayList<>();
- return this;
- }
-
- /**
- * Adds features for the route.
- */
- @NonNull
- public Builder addFeatures(@NonNull Collection<String> features) {
- Objects.requireNonNull(features, "features must not be null");
- for (String feature : features) {
- addFeature(feature);
- }
- return this;
- }
-
- /**
- * Adds a feature for the route.
- */
- @NonNull
- public Builder addFeature(@NonNull String feature) {
- if (TextUtils.isEmpty(feature)) {
- throw new IllegalArgumentException("feature must not be null or empty");
- }
- mFeatures.add(feature);
- return this;
- }
-
- /**
- * Sets the route's current volume, or 0 if unknown.
- */
- @NonNull
- public Builder setVolume(int volume) {
- mVolume = volume;
- return this;
- }
-
- /**
- * Sets the route's maximum volume, or 0 if unknown.
- */
- @NonNull
- public Builder setVolumeMax(int volumeMax) {
- mVolumeMax = volumeMax;
- return this;
- }
-
- /**
* Sets the route's volume handling.
*/
@NonNull
@@ -612,28 +597,61 @@
}
/**
- * Sets the route's device type.
+ * Sets the route's maximum volume, or 0 if unknown.
*/
@NonNull
- public Builder setDeviceType(@DeviceType int deviceType) {
- mDeviceType = deviceType;
+ public Builder setVolumeMax(int volumeMax) {
+ mVolumeMax = volumeMax;
+ return this;
+ }
+
+ /**
+ * Sets the route's current volume, or 0 if unknown.
+ */
+ @NonNull
+ public Builder setVolume(int volume) {
+ mVolume = volume;
return this;
}
/**
* Sets a bundle of extras for the route.
+ * <p>
+ * Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}.
*/
@NonNull
public Builder setExtras(@Nullable Bundle extras) {
+ if (extras == null) {
+ mExtras = null;
+ return this;
+ }
mExtras = new Bundle(extras);
return this;
}
/**
+ * Sets the provider id of the route.
+ * @hide
+ */
+ @NonNull
+ public Builder setProviderId(@NonNull String providerId) {
+ if (TextUtils.isEmpty(providerId)) {
+ throw new IllegalArgumentException("providerId must not be null or empty");
+ }
+ mProviderId = providerId;
+ return this;
+ }
+
+ /**
* Builds the {@link MediaRoute2Info media route info}.
+ *
+ * @throws IllegalArgumentException if no features are added.
*/
@NonNull
public MediaRoute2Info build() {
+ if (mFeatures.isEmpty()) {
+ throw new IllegalArgumentException("features must not be empty!");
+ }
return new MediaRoute2Info(this);
}
}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 6f5ba84..18969ae 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.BytesLong;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,6 +28,8 @@
import android.media.tv.tuner.TunerConstants.FrontendScanType;
import android.media.tv.tuner.TunerConstants.Result;
import android.media.tv.tuner.dvr.Dvr;
+import android.media.tv.tuner.dvr.DvrCallback;
+import android.media.tv.tuner.dvr.DvrSettings;
import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
import android.media.tv.tuner.filter.FilterEvent;
import android.media.tv.tuner.filter.TimeFilter;
@@ -88,6 +91,31 @@
nativeSetup();
}
+ /**
+ * Constructs a Tuner instance.
+ *
+ * @param context the context of the caller.
+ * @param tvInputSessionId the session ID of the TV input.
+ * @param useCase the use case of this Tuner instance.
+ *
+ * @hide
+ * TODO: replace the other constructor
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ public Tuner(@NonNull Context context, @NonNull String tvInputSessionId, int useCase) {
+ mContext = context;
+ }
+
+ /**
+ * Shares the frontend resource with another Tuner instance
+ *
+ * @param tuner the Tuner instance to share frontend resource with.
+ *
+ * @hide
+ */
+ public void shareFrontend(@NonNull Tuner tuner) { }
+
+
private long mNativeContext; // used by native jMediaTuner
/** @hide */
@@ -119,13 +147,13 @@
private native int nativeStopScan();
private native int nativeSetLnb(int lnbId);
private native int nativeSetLna(boolean enable);
- private native FrontendStatus[] nativeGetFrontendStatus(int[] statusTypes);
+ private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes);
private native int nativeGetAvSyncHwId(Filter filter);
private native long nativeGetAvSyncTime(int avSyncId);
private native int nativeConnectCiCam(int ciCamId);
private native int nativeDisconnectCiCam();
private native FrontendInfo nativeGetFrontendInfo(int id);
- private native Filter nativeOpenFilter(int type, int subType, int bufferSize);
+ private native Filter nativeOpenFilter(int type, int subType, long bufferSize);
private native TimeFilter nativeOpenTimeFilter();
private native List<Integer> nativeGetLnbIds();
@@ -133,7 +161,7 @@
private native Descrambler nativeOpenDescrambler();
- private native Dvr nativeOpenDvr(int type, int bufferSize);
+ private native Dvr nativeOpenDvr(int type, long bufferSize);
private static native DemuxCapabilities nativeGetDemuxCapabilities();
@@ -159,6 +187,21 @@
void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
}
+
+ /**
+ * Listener for resource lost.
+ *
+ * @hide
+ */
+ public interface OnResourceLostListener {
+ /**
+ * Invoked when resource lost.
+ *
+ * @param tuner the tuner instance whose resource is being reclaimed.
+ */
+ void onResourceLost(@NonNull Tuner tuner);
+ }
+
@Nullable
private EventHandler createEventHandler() {
Looper looper;
@@ -221,23 +264,32 @@
/**
* Stops a previous tuning.
*
- * If the method completes successfully the frontend is no longer tuned and no data
+ * <p>If the method completes successfully, the frontend is no longer tuned and no data
* will be sent to attached filters.
*
* @return result status of the operation.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
public int stopTune() {
+ TunerUtils.checkTunerPermission(mContext);
return nativeStopTune();
}
/**
* Scan channels.
+ *
+ * @param settings A {@link FrontendSettings} to configure the frontend.
+ * @param scanType The scan type.
+ *
* @hide
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType,
@NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
+ TunerUtils.checkTunerPermission(mContext);
mScanCallback = scanCallback;
mScanCallbackExecutor = executor;
return nativeScan(settings.getType(), settings, scanType);
@@ -255,6 +307,7 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
public int stopScan() {
TunerUtils.checkTunerPermission(mContext);
int retVal = nativeStopScan();
@@ -266,42 +319,49 @@
/**
* Sets Low-Noise Block downconverter (LNB) for satellite frontend.
*
- * This assigns a hardware LNB resource to the satellite tuner. It can be
+ * <p>This assigns a hardware LNB resource to the satellite tuner. It can be
* called multiple times to update LNB assignment.
*
* @param lnb the LNB instance.
*
* @return result status of the operation.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
public int setLnb(@NonNull Lnb lnb) {
+ TunerUtils.checkTunerPermission(mContext);
return nativeSetLnb(lnb.mId);
}
/**
* Enable or Disable Low Noise Amplifier (LNA).
*
- * @param enable true to activate LNA module; false to deactivate LNA
+ * @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA.
*
* @return result status of the operation.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
public int setLna(boolean enable) {
+ TunerUtils.checkTunerPermission(mContext);
return nativeSetLna(enable);
}
/**
* Gets the statuses of the frontend.
*
- * This retrieve the statuses of the frontend for given status types.
+ * <p>This retrieve the statuses of the frontend for given status types.
*
- * @param statusTypes an array of status type which the caller request.
- *
- * @return statuses an array of statuses which response the caller's
- * request.
+ * @param statusTypes an array of status types which the caller requests.
+ * @return statuses which response the caller's requests.
* @hide
*/
- public FrontendStatus[] getFrontendStatus(int[] statusTypes) {
+ @Nullable
+ public FrontendStatus getFrontendStatus(int[] statusTypes) {
return nativeGetFrontendStatus(statusTypes);
}
@@ -310,59 +370,77 @@
*
* @param filter the filter instance for the hardware sync ID.
* @return the id of hardware A/V sync.
+ *
* @hide
*/
- public int getAvSyncHwId(Filter filter) {
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ public int getAvSyncHwId(@NonNull Filter filter) {
+ TunerUtils.checkTunerPermission(mContext);
return nativeGetAvSyncHwId(filter);
}
+
/**
- * Gets the current timestamp for A/V sync
+ * Gets the current timestamp for Audio/Video sync
*
- * The timestamp is maintained by hardware. The timestamp based on 90KHz, and it's format is the
- * same as PTS (Presentation Time Stamp).
+ * <p>The timestamp is maintained by hardware. The timestamp based on 90KHz, and it's format is
+ * the same as PTS (Presentation Time Stamp).
*
* @param avSyncHwId the hardware id of A/V sync.
* @return the current timestamp of hardware A/V sync.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
public long getAvSyncTime(int avSyncHwId) {
+ TunerUtils.checkTunerPermission(mContext);
return nativeGetAvSyncTime(avSyncHwId);
}
-
/**
* Connects Conditional Access Modules (CAM) through Common Interface (CI)
*
- * The demux uses the output from the frontend as the input by default, and must change to use
- * the output from CI-CAM as the input after this call.
+ * <p>The demux uses the output from the frontend as the input by default, and must change to
+ * use the output from CI-CAM as the input after this call.
*
* @param ciCamId specify CI-CAM Id to connect.
* @return result status of the operation.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
@Result
public int connectCiCam(int ciCamId) {
+ TunerUtils.checkTunerPermission(mContext);
return nativeConnectCiCam(ciCamId);
}
/**
* Disconnects Conditional Access Modules (CAM)
*
- * The demux will use the output from the frontend as the input after this call.
+ * <p>The demux will use the output from the frontend as the input after this call.
*
* @return result status of the operation.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
@Result
public int disconnectCiCam() {
+ TunerUtils.checkTunerPermission(mContext);
return nativeDisconnectCiCam();
}
/**
- * Retrieve the frontend information.
+ * Gets the frontend information.
+ *
+ * @return The frontend information. {@code null} if the operation failed.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Nullable
public FrontendInfo getFrontendInfo() {
+ TunerUtils.checkTunerPermission(mContext);
if (mFrontend == null) {
throw new IllegalStateException("frontend is not initialized");
}
@@ -370,10 +448,13 @@
}
/**
- * Gets frontend ID.
+ * Gets the frontend ID.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
public int getFrontendId() {
+ TunerUtils.checkTunerPermission(mContext);
if (mFrontend == null) {
throw new IllegalStateException("frontend is not initialized");
}
@@ -382,6 +463,7 @@
/**
* Gets Demux capabilities.
+ *
* @hide
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
@@ -424,8 +506,26 @@
private Filter() {}
}
- private Filter openFilter(@FilterType int mainType, @FilterSubtype int subType, int bufferSize,
- FilterCallback cb) {
+ /**
+ * Opens a filter object based on the given types and buffer size.
+ *
+ * @param mainType the main type of the filter.
+ * @param subType the subtype of the filter.
+ * @param bufferSize the buffer size of the filter to be opened in bytes. The buffer holds the
+ * data output from the filter.
+ * @param cb the callback to receive notifications from filter.
+ * @param executor the executor on which callback will be invoked. The default event handler
+ * executor is used if it's {@code null}.
+ * @return the opened filter. {@code null} if the operation failed.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Nullable
+ public Filter openFilter(@FilterType int mainType, @FilterSubtype int subType,
+ @BytesLong long bufferSize, @Nullable FilterCallback cb,
+ @CallbackExecutor @Nullable Executor executor) {
+ TunerUtils.checkTunerPermission(mContext);
Filter filter = nativeOpenFilter(
mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
if (filter != null) {
@@ -437,6 +537,24 @@
return filter;
}
+ /**
+ * Opens an LNB (low-noise block downconverter) object.
+ *
+ * @param cb the callback to receive notifications from LNB.
+ * @param executor the executor on which callback will be invoked. The default event handler
+ * executor is used if it's {@code null}.
+ * @return the opened LNB object. {@code null} if the operation failed.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Nullable
+ public Lnb openLnb(LnbCallback cb, @CallbackExecutor @Nullable Executor executor) {
+ TunerUtils.checkTunerPermission(mContext);
+ // TODO: use resource manager to get LNB ID.
+ return new Lnb(0);
+ }
+
private List<Integer> getLnbIds() {
mLnbIds = nativeGetLnbIds();
return mLnbIds;
@@ -484,7 +602,24 @@
return nativeOpenDescrambler();
}
- private Dvr openDvr(int type, int bufferSize) {
+ /**
+ * Open a DVR (Digital Video Record) instance.
+ *
+ * @param type the DVR type to be opened.
+ * @param bufferSize the buffer size of the output in bytes. It's used to hold output data of
+ * the attached filters.
+ * @param cb the callback to receive notifications from DVR.
+ * @param executor the executor on which callback will be invoked. The default event handler
+ * executor is used if it's {@code null}.
+ * @return the opened DVR object. {@code null} if the operation failed.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Nullable
+ public Dvr openDvr(@DvrSettings.Type int type, @BytesLong long bufferSize, DvrCallback cb,
+ @CallbackExecutor @Nullable Executor executor) {
+ TunerUtils.checkTunerPermission(mContext);
Dvr dvr = nativeOpenDvr(type, bufferSize);
return dvr;
}
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index 4532122..fa8f550 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;
@@ -124,93 +129,29 @@
*/
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})
+
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
- public @interface TsIndex {}
+ @IntDef(prefix = "INDEX_TYPE_", value =
+ {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC})
+ public @interface ScIndexType {}
/**
- * TS index FIRST_PACKET.
+ * Start Code Index is not used.
* @hide
*/
- public static final int TS_INDEX_FIRST_PACKET = Constants.DemuxTsIndex.FIRST_PACKET;
+ public static final int INDEX_TYPE_NONE = Constants.DemuxRecordScIndexType.NONE;
/**
- * TS index PAYLOAD_UNIT_START_INDICATOR.
+ * Start Code index.
* @hide
*/
- public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR =
- Constants.DemuxTsIndex.PAYLOAD_UNIT_START_INDICATOR;
+ public static final int INDEX_TYPE_SC = Constants.DemuxRecordScIndexType.SC;
/**
- * TS index CHANGE_TO_NOT_SCRAMBLED.
+ * Start Code index for HEVC.
* @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;
+ public static final int INDEX_TYPE_SC_HEVC = Constants.DemuxRecordScIndexType.SC_HEVC;
+
/**
* Indexes can be tagged by Start Code in PES (Packetized Elementary Stream)
@@ -317,156 +258,6 @@
public static final int FRONTEND_SCAN_BLIND = Constants.FrontendScanType.SCAN_BLIND;
- /** @hide */
- @IntDef({FRONTEND_STATUS_TYPE_DEMOD_LOCK, FRONTEND_STATUS_TYPE_SNR, FRONTEND_STATUS_TYPE_BER,
- FRONTEND_STATUS_TYPE_PER, FRONTEND_STATUS_TYPE_PRE_BER,
- FRONTEND_STATUS_TYPE_SIGNAL_QUALITY, FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH,
- FRONTEND_STATUS_TYPE_SYMBOL_RATE, FRONTEND_STATUS_TYPE_FEC,
- FRONTEND_STATUS_TYPE_MODULATION, FRONTEND_STATUS_TYPE_SPECTRAL,
- FRONTEND_STATUS_TYPE_LNB_VOLTAGE, FRONTEND_STATUS_TYPE_PLP_ID,
- FRONTEND_STATUS_TYPE_EWBS, FRONTEND_STATUS_TYPE_AGC, FRONTEND_STATUS_TYPE_LNA,
- FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_VBER_CN,
- FRONTEND_STATUS_TYPE_LBER_CN, FRONTEND_STATUS_TYPE_XER_CN, FRONTEND_STATUS_TYPE_MER,
- FRONTEND_STATUS_TYPE_FREQ_OFFSET, FRONTEND_STATUS_TYPE_HIERARCHY,
- FRONTEND_STATUS_TYPE_RF_LOCK, FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO})
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendStatusType {}
-
- /**
- * Lock status for Demod.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK =
- Constants.FrontendStatusType.DEMOD_LOCK;
- /**
- * Signal to Noise Ratio.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_SNR = Constants.FrontendStatusType.SNR;
- /**
- * Bit Error Ratio.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_BER = Constants.FrontendStatusType.BER;
- /**
- * Packages Error Ratio.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_PER = Constants.FrontendStatusType.PER;
- /**
- * Bit Error Ratio before FEC.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_PRE_BER = Constants.FrontendStatusType.PRE_BER;
- /**
- * Signal Quality (0..100). Good data over total data in percent can be
- * used as a way to present Signal Quality.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY =
- Constants.FrontendStatusType.SIGNAL_QUALITY;
- /**
- * Signal Strength.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH =
- Constants.FrontendStatusType.SIGNAL_STRENGTH;
- /**
- * Symbol Rate.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE =
- Constants.FrontendStatusType.SYMBOL_RATE;
- /**
- * Forward Error Correction Type.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_FEC = Constants.FrontendStatusType.FEC;
- /**
- * Modulation Type.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_MODULATION =
- Constants.FrontendStatusType.MODULATION;
- /**
- * Spectral Inversion Type.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_SPECTRAL = Constants.FrontendStatusType.SPECTRAL;
- /**
- * LNB Voltage.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE =
- Constants.FrontendStatusType.LNB_VOLTAGE;
- /**
- * Physical Layer Pipe ID.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_PLP_ID = Constants.FrontendStatusType.PLP_ID;
- /**
- * Status for Emergency Warning Broadcasting System.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_EWBS = Constants.FrontendStatusType.EWBS;
- /**
- * Automatic Gain Control.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_AGC = Constants.FrontendStatusType.AGC;
- /**
- * Low Noise Amplifier.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_LNA = Constants.FrontendStatusType.LNA;
- /**
- * Error status by layer.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR =
- Constants.FrontendStatusType.LAYER_ERROR;
- /**
- * CN value by VBER.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_VBER_CN = Constants.FrontendStatusType.VBER_CN;
- /**
- * CN value by LBER.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_LBER_CN = Constants.FrontendStatusType.LBER_CN;
- /**
- * CN value by XER.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_XER_CN = Constants.FrontendStatusType.XER_CN;
- /**
- * Moduration Error Ratio.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_MER = Constants.FrontendStatusType.MER;
- /**
- * Difference between tuning frequency and actual locked frequency.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET =
- Constants.FrontendStatusType.FREQ_OFFSET;
- /**
- * Hierarchy for DVBT.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_HIERARCHY = Constants.FrontendStatusType.HIERARCHY;
- /**
- * Lock status for RF.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_RF_LOCK = Constants.FrontendStatusType.RF_LOCK;
- /**
- * PLP information in a frequency band for ATSC3.0 frontend.
- * @hide
- */
- public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO =
- Constants.FrontendStatusType.ATSC3_PLP_INFO;
/** @hide */
@LongDef({FEC_UNDEFINED, FEC_AUTO, FEC_1_2, FEC_1_3, FEC_1_4, FEC_1_5, FEC_2_3, FEC_2_5,
@@ -665,632 +456,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_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(prefix = "FRONTEND_DVBS_STANDARD",
- value = {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(prefix = "FRONTEND_DVBT_STANDARD",
- value = {FRONTEND_DVBT_STANDARD_AUTO, FRONTEND_DVBT_STANDARD_T,
- FRONTEND_DVBT_STANDARD_T2}
- )
- @Retention(RetentionPolicy.SOURCE)
- public @interface FrontendDvbtStandard {}
- /** @hide */
- public static final int FRONTEND_DVBT_STANDARD_AUTO = Constants.FrontendDvbtStandard.AUTO;
- /** @hide */
- public static final int FRONTEND_DVBT_STANDARD_T = Constants.FrontendDvbtStandard.T;
- /** @hide */
- public static final int FRONTEND_DVBT_STANDARD_T2 = Constants.FrontendDvbtStandard.T2;
-
- /** @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)
diff --git a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
index f0fe533..fcca6a1 100644
--- a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
@@ -16,20 +16,123 @@
package android.media.tv.tuner.filter;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Filter configuration for a ALP filter.
* @hide
*/
public class AlpFilterConfiguration extends FilterConfiguration {
- private int mPacketType;
- private int mLengthType;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "LENGTH_TYPE_", value =
+ {LENGTH_TYPE_UNDEFINED, LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER,
+ LENGTH_TYPE_WITH_ADDITIONAL_HEADER})
+ public @interface LengthType {}
- public AlpFilterConfiguration(Settings settings) {
+ /**
+ * Length type not defined.
+ */
+ public static final int LENGTH_TYPE_UNDEFINED = Constants.DemuxAlpLengthType.UNDEFINED;
+ /**
+ * Length does NOT include additional header.
+ */
+ public static final int LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER =
+ Constants.DemuxAlpLengthType.WITHOUT_ADDITIONAL_HEADER;
+ /**
+ * Length includes additional header.
+ */
+ public static final int LENGTH_TYPE_WITH_ADDITIONAL_HEADER =
+ Constants.DemuxAlpLengthType.WITH_ADDITIONAL_HEADER;
+
+
+ private final int mPacketType;
+ private final int mLengthType;
+
+ public AlpFilterConfiguration(Settings settings, int packetType, int lengthType) {
super(settings);
+ mPacketType = packetType;
+ mLengthType = lengthType;
}
@Override
public int getType() {
return FilterConfiguration.FILTER_TYPE_ALP;
}
+
+ /**
+ * Gets packet type.
+ */
+ @FilterConfiguration.PacketType
+ public int getPacketType() {
+ return mPacketType;
+ }
+ /**
+ * Gets length type.
+ */
+ @LengthType
+ public int getLengthType() {
+ return mLengthType;
+ }
+
+ /**
+ * Creates a builder for {@link AlpFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link AlpFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private int mPacketType;
+ private int mLengthType;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets packet type.
+ */
+ @NonNull
+ public Builder setPacketType(@FilterConfiguration.PacketType int packetType) {
+ mPacketType = packetType;
+ return this;
+ }
+ /**
+ * Sets length type.
+ */
+ @NonNull
+ public Builder setLengthType(@LengthType int lengthType) {
+ mLengthType = lengthType;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AlpFilterConfiguration} object.
+ */
+ @NonNull
+ public AlpFilterConfiguration build() {
+ return new AlpFilterConfiguration(mSettings, mPacketType, mLengthType);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java
index a7c49d5..940b5ae 100644
--- a/media/java/android/media/tv/tuner/filter/AvSettings.java
+++ b/media/java/android/media/tv/tuner/filter/AvSettings.java
@@ -16,21 +16,84 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
import android.media.tv.tuner.TunerConstants;
import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
/**
* Filter Settings for a Video and Audio.
+ *
* @hide
*/
public class AvSettings extends Settings {
- private boolean mIsPassthrough;
+ private final boolean mIsPassthrough;
- private AvSettings(int mainType, boolean isAudio) {
+ private AvSettings(int mainType, boolean isAudio, boolean isPassthrough) {
super(TunerUtils.getFilterSubtype(
mainType,
isAudio
? TunerConstants.FILTER_SUBTYPE_AUDIO
: TunerConstants.FILTER_SUBTYPE_VIDEO));
+ mIsPassthrough = isPassthrough;
+ }
+
+ /**
+ * Checks whether it's passthrough.
+ */
+ public boolean isPassthrough() {
+ return mIsPassthrough;
+ }
+
+ /**
+ * Creates a builder for {@link AvSettings}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ * @param isAudio {@code true} if it's audio settings; {@code false} if it's video settings.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(
+ @NonNull Context context, @FilterType int mainType, boolean isAudio) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType, isAudio);
+ }
+
+ /**
+ * Builder for {@link AvSettings}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private final boolean mIsAudio;
+ private boolean mIsPassthrough;
+
+ private Builder(int mainType, boolean isAudio) {
+ super(mainType);
+ mIsAudio = isAudio;
+ }
+
+ /**
+ * Sets whether it's passthrough.
+ */
+ @NonNull
+ public Builder setPassthrough(boolean isPassthrough) {
+ mIsPassthrough = isPassthrough;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AvSettings} object.
+ */
+ @NonNull
+ public AvSettings build() {
+ return new AvSettings(mMainType, mIsAudio, mIsPassthrough);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
}
diff --git a/media/java/android/media/tv/tuner/filter/DownloadSettings.java b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
index 0742b11..e3e1df0 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadSettings.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
@@ -16,17 +16,75 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
import android.media.tv.tuner.TunerConstants;
import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
/**
* Filter Settings for a Download.
* @hide
*/
public class DownloadSettings extends Settings {
- private int mDownloadId;
+ private final int mDownloadId;
- public DownloadSettings(int mainType) {
+ private DownloadSettings(int mainType, int downloadId) {
super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD));
+ mDownloadId = downloadId;
+ }
+
+ /**
+ * Gets download ID.
+ */
+ public int getDownloadId() {
+ return mDownloadId;
+ }
+
+ /**
+ * Creates a builder for {@link DownloadSettings}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link DownloadSettings}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private int mDownloadId;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets download ID.
+ */
+ @NonNull
+ public Builder setDownloadId(int downloadId) {
+ mDownloadId = downloadId;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DownloadSettings} object.
+ */
+ @NonNull
+ public DownloadSettings build() {
+ return new DownloadSettings(mMainType, mDownloadId);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
}
diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
index 6496627..68c722f 100644
--- a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
@@ -33,7 +33,8 @@
public abstract class FilterConfiguration {
/** @hide */
- @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
+ @IntDef(prefix = "FILTER_TYPE_", value =
+ {FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
@Retention(RetentionPolicy.SOURCE)
public @interface FilterType {}
@@ -58,6 +59,30 @@
*/
public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP;
+
+ /** @hide */
+ @IntDef(prefix = "PACKET_TYPE_", value =
+ {PACKET_TYPE_IPV4, PACKET_TYPE_COMPRESSED, PACKET_TYPE_SIGNALING})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PacketType {}
+
+ /**
+ * IP v4 packet type.
+ * @hide
+ */
+ public static final int PACKET_TYPE_IPV4 = 0;
+ /**
+ * Compressed packet type.
+ * @hide
+ */
+ public static final int PACKET_TYPE_COMPRESSED = 2;
+ /**
+ * Signaling packet type.
+ * @hide
+ */
+ public static final int PACKET_TYPE_SIGNALING = 4;
+
+
@Nullable
/* package */ final Settings mSettings;
@@ -77,4 +102,27 @@
public Settings getSettings() {
return mSettings;
}
+
+ /**
+ * Builder for {@link FilterConfiguration}.
+ *
+ * @param <T> The subclass to be built.
+ * @hide
+ */
+ public abstract static class Builder<T extends Builder<T>> {
+ /* package */ Settings mSettings;
+
+ /* package */ Builder() {
+ }
+
+ /**
+ * Sets filter settings.
+ */
+ @Nullable
+ public T setFrequency(Settings settings) {
+ mSettings = settings;
+ return self();
+ }
+ /* package */ abstract T self();
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
index c896368..98edf10 100644
--- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -16,23 +16,152 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.Size;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
/**
* Filter configuration for a IP filter.
* @hide
*/
public class IpFilterConfiguration extends FilterConfiguration {
- private byte[] mSrcIpAddress;
- private byte[] mDstIpAddress;
- private int mSrcPort;
- private int mDstPort;
- private boolean mPassthrough;
+ private final byte[] mSrcIpAddress;
+ private final byte[] mDstIpAddress;
+ private final int mSrcPort;
+ private final int mDstPort;
+ private final boolean mPassthrough;
- public IpFilterConfiguration(Settings settings) {
+ public IpFilterConfiguration(Settings settings, byte[] srcAddr, byte[] dstAddr, int srcPort,
+ int dstPort, boolean passthrough) {
super(settings);
+ mSrcIpAddress = srcAddr;
+ mDstIpAddress = dstAddr;
+ mSrcPort = srcPort;
+ mDstPort = dstPort;
+ mPassthrough = passthrough;
}
@Override
public int getType() {
return FilterConfiguration.FILTER_TYPE_IP;
}
+
+ /**
+ * Gets source IP address.
+ */
+ @Size(min = 4, max = 16)
+ public byte[] getSrcIpAddress() {
+ return mSrcIpAddress;
+ }
+ /**
+ * Gets destination IP address.
+ */
+ @Size(min = 4, max = 16)
+ public byte[] getDstIpAddress() {
+ return mDstIpAddress;
+ }
+ /**
+ * Gets source port.
+ */
+ public int getSrcPort() {
+ return mSrcPort;
+ }
+ /**
+ * Gets destination port.
+ */
+ public int getDstPort() {
+ return mDstPort;
+ }
+ /**
+ * Checks whether the filter is passthrough.
+ *
+ * @return {@code true} if the data from IP subtype go to next filter directly;
+ * {@code false} otherwise.
+ */
+ public boolean isPassthrough() {
+ return mPassthrough;
+ }
+
+ /**
+ * Creates a builder for {@link IpFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IpFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private byte[] mSrcIpAddress;
+ private byte[] mDstIpAddress;
+ private int mSrcPort;
+ private int mDstPort;
+ private boolean mPassthrough;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets source IP address.
+ */
+ @NonNull
+ public Builder setSrcIpAddress(byte[] srcIpAddress) {
+ mSrcIpAddress = srcIpAddress;
+ return this;
+ }
+ /**
+ * Sets destination IP address.
+ */
+ @NonNull
+ public Builder setDstIpAddress(byte[] dstIpAddress) {
+ mDstIpAddress = dstIpAddress;
+ return this;
+ }
+ /**
+ * Sets source port.
+ */
+ @NonNull
+ public Builder setSrcPort(int srcPort) {
+ mSrcPort = srcPort;
+ return this;
+ }
+ /**
+ * Sets destination port.
+ */
+ @NonNull
+ public Builder setDstPort(int dstPort) {
+ mDstPort = dstPort;
+ return this;
+ }
+ /**
+ * Sets passthrough.
+ */
+ @NonNull
+ public Builder setPassthrough(boolean passthrough) {
+ mPassthrough = passthrough;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IpFilterConfiguration} object.
+ */
+ @NonNull
+ public IpFilterConfiguration build() {
+ return new IpFilterConfiguration(
+ mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
index 9045ce6..83246e5 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
@@ -16,19 +16,78 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
/**
* Filter configuration for a MMTP filter.
* @hide
*/
public class MmtpFilterConfiguration extends FilterConfiguration {
- private int mMmtpPid;
+ private final int mMmtpPid;
- public MmtpFilterConfiguration(Settings settings) {
+ public MmtpFilterConfiguration(Settings settings, int mmtpPid) {
super(settings);
+ mMmtpPid = mmtpPid;
}
@Override
public int getType() {
return FilterConfiguration.FILTER_TYPE_MMTP;
}
+
+ /**
+ * Gets MMPT PID.
+ *
+ * <p>Packet ID is used to specify packets in MMTP.
+ */
+ public int getMmtpPid() {
+ return mMmtpPid;
+ }
+
+ /**
+ * Creates a builder for {@link IpFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IpFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private int mMmtpPid;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets MMPT PID.
+ */
+ @NonNull
+ public Builder setMmtpPid(int mmtpPid) {
+ mMmtpPid = mmtpPid;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IpFilterConfiguration} object.
+ */
+ @NonNull
+ public MmtpFilterConfiguration build() {
+ return new MmtpFilterConfiguration(mSettings, mMmtpPid);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
index 701868a..4833709 100644
--- a/media/java/android/media/tv/tuner/filter/RecordSettings.java
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -16,18 +16,231 @@
package android.media.tv.tuner.filter;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerConstants.ScIndexType;
import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* The Settings for the record in DVR.
* @hide
*/
public class RecordSettings extends Settings {
- private int mIndexType;
- private int mIndexMask;
+ /**
+ * Indexes can be tagged through TS (Transport Stream) header.
+ *
+ * @hide
+ */
+ @IntDef(flag = true,
+ prefix = "TS_INDEX_",
+ value = {TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR,
+ TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED,
+ TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR,
+ TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR,
+ TS_INDEX_PCR_FLAG, TS_INDEX_OPCR_FLAG, TS_INDEX_SPLICING_POINT_FLAG,
+ TS_INDEX_PRIVATE_DATA, TS_INDEX_ADAPTATION_EXTENSION_FLAG})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TsIndexMask {}
- public RecordSettings(int mainType) {
+ /**
+ * TS index FIRST_PACKET.
+ * @hide
+ */
+ public static final int TS_INDEX_FIRST_PACKET = Constants.DemuxTsIndex.FIRST_PACKET;
+ /**
+ * TS index PAYLOAD_UNIT_START_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR =
+ Constants.DemuxTsIndex.PAYLOAD_UNIT_START_INDICATOR;
+ /**
+ * TS index CHANGE_TO_NOT_SCRAMBLED.
+ * @hide
+ */
+ public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED =
+ Constants.DemuxTsIndex.CHANGE_TO_NOT_SCRAMBLED;
+ /**
+ * TS index CHANGE_TO_EVEN_SCRAMBLED.
+ * @hide
+ */
+ public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED =
+ Constants.DemuxTsIndex.CHANGE_TO_EVEN_SCRAMBLED;
+ /**
+ * TS index CHANGE_TO_ODD_SCRAMBLED.
+ * @hide
+ */
+ public static final int TS_INDEX_CHANGE_TO_ODD_SCRAMBLED =
+ Constants.DemuxTsIndex.CHANGE_TO_ODD_SCRAMBLED;
+ /**
+ * TS index DISCONTINUITY_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_DISCONTINUITY_INDICATOR =
+ Constants.DemuxTsIndex.DISCONTINUITY_INDICATOR;
+ /**
+ * TS index RANDOM_ACCESS_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_RANDOM_ACCESS_INDICATOR =
+ Constants.DemuxTsIndex.RANDOM_ACCESS_INDICATOR;
+ /**
+ * TS index PRIORITY_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_PRIORITY_INDICATOR = Constants.DemuxTsIndex.PRIORITY_INDICATOR;
+ /**
+ * TS index PCR_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_PCR_FLAG = Constants.DemuxTsIndex.PCR_FLAG;
+ /**
+ * TS index OPCR_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_OPCR_FLAG = Constants.DemuxTsIndex.OPCR_FLAG;
+ /**
+ * TS index SPLICING_POINT_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_SPLICING_POINT_FLAG =
+ Constants.DemuxTsIndex.SPLICING_POINT_FLAG;
+ /**
+ * TS index PRIVATE_DATA.
+ * @hide
+ */
+ public static final int TS_INDEX_PRIVATE_DATA = Constants.DemuxTsIndex.PRIVATE_DATA;
+ /**
+ * TS index ADAPTATION_EXTENSION_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG =
+ Constants.DemuxTsIndex.ADAPTATION_EXTENSION_FLAG;
+ /**
+ * @hide
+ */
+ @IntDef(flag = true,
+ prefix = "SC_",
+ value = {
+ TunerConstants.SC_INDEX_I_FRAME,
+ TunerConstants.SC_INDEX_P_FRAME,
+ TunerConstants.SC_INDEX_B_FRAME,
+ TunerConstants.SC_INDEX_SEQUENCE,
+ TunerConstants.SC_HEVC_INDEX_SPS,
+ TunerConstants.SC_HEVC_INDEX_AUD,
+ TunerConstants.SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+ TunerConstants.SC_HEVC_INDEX_SLICE_BLA_W_RADL,
+ TunerConstants.SC_HEVC_INDEX_SLICE_BLA_N_LP,
+ TunerConstants.SC_HEVC_INDEX_SLICE_IDR_W_RADL,
+ TunerConstants.SC_HEVC_INDEX_SLICE_IDR_N_LP,
+ TunerConstants.SC_HEVC_INDEX_SLICE_TRAIL_CRA,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScIndexMask {}
+
+
+ private final int mTsIndexMask;
+ private final int mScIndexType;
+ private final int mScIndexMask;
+
+ private RecordSettings(int mainType, int tsIndexType, int scIndexType, int scIndexMask) {
super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_RECORD));
+ mTsIndexMask = tsIndexType;
+ mScIndexType = scIndexType;
+ mScIndexMask = scIndexMask;
}
+
+ /**
+ * Gets TS index mask.
+ */
+ @TsIndexMask
+ public int getTsIndexMask() {
+ return mTsIndexMask;
+ }
+ /**
+ * Gets Start Code index type.
+ */
+ @ScIndexType
+ public int getScIndexType() {
+ return mScIndexType;
+ }
+ /**
+ * Gets Start Code index mask.
+ */
+ @ScIndexMask
+ public int getScIndexMask() {
+ return mScIndexMask;
+ }
+
+ /**
+ * Creates a builder for {@link RecordSettings}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link RecordSettings}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private int mTsIndexMask;
+ private int mScIndexType;
+ private int mScIndexMask;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets TS index mask.
+ */
+ @NonNull
+ public Builder setTsIndexMask(@TsIndexMask int indexMask) {
+ mTsIndexMask = indexMask;
+ return this;
+ }
+ /**
+ * Sets index type.
+ */
+ @NonNull
+ public Builder setScIndexType(@ScIndexType int indexType) {
+ mScIndexType = indexType;
+ return this;
+ }
+ /**
+ * Sets Start Code index mask.
+ */
+ @NonNull
+ public Builder setScIndexMask(@ScIndexMask int indexMask) {
+ mScIndexMask = indexMask;
+ return this;
+ }
+
+ /**
+ * Builds a {@link RecordSettings} object.
+ */
+ @NonNull
+ public RecordSettings build() {
+ return new RecordSettings(mMainType, mTsIndexMask, mScIndexType, mScIndexMask);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
}
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
index 414ea67..0fa982e 100644
--- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
@@ -16,18 +16,116 @@
package android.media.tv.tuner.filter;
-import java.util.List;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
/**
- * Bits Settings for Section Filter.
+ * Bits Settings for Section Filters.
* @hide
*/
public class SectionSettingsWithSectionBits extends SectionSettings {
- private List<Byte> mFilter;
- private List<Byte> mMask;
- private List<Byte> mMode;
+ private final byte[] mFilter;
+ private final byte[] mMask;
+ private final byte[] mMode;
- private SectionSettingsWithSectionBits(int mainType) {
+
+ private SectionSettingsWithSectionBits(int mainType, byte[] filter, byte[] mask, byte[] mode) {
super(mainType);
+ mFilter = filter;
+ mMask = mask;
+ mMode = mode;
+ }
+
+ /**
+ * Gets the bytes configured for Section Filter
+ */
+ public byte[] getFilterBytes() {
+ return mFilter;
+ }
+ /**
+ * Gets bit mask.
+ *
+ * <p>The bits in the bytes are used for filtering.
+ */
+ public byte[] getMask() {
+ return mMask;
+ }
+ /**
+ * Gets mode.
+ *
+ * <p>Do positive match at the bit position of the configured bytes when the bit at same
+ * position of the mode is 0.
+ * <p>Do negative match at the bit position of the configured bytes when the bit at same
+ * position of the mode is 1.
+ */
+ public byte[] getMode() {
+ return mMode;
+ }
+
+ /**
+ * Creates a builder for {@link SectionSettingsWithSectionBits}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link SectionSettingsWithSectionBits}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private byte[] mFilter;
+ private byte[] mMask;
+ private byte[] mMode;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets filter bytes.
+ */
+ @NonNull
+ public Builder setFilter(byte[] filter) {
+ mFilter = filter;
+ return this;
+ }
+ /**
+ * Sets bit mask.
+ */
+ @NonNull
+ public Builder setMask(byte[] mask) {
+ mMask = mask;
+ return this;
+ }
+ /**
+ * Sets mode.
+ */
+ @NonNull
+ public Builder setMode(byte[] mode) {
+ mMode = mode;
+ return this;
+ }
+
+ /**
+ * Builds a {@link SectionSettingsWithSectionBits} object.
+ */
+ @NonNull
+ public SectionSettingsWithSectionBits build() {
+ return new SectionSettingsWithSectionBits(mMainType, mFilter, mMask, mMode);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
}
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
index 0df1d73..6542b89 100644
--- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
@@ -16,15 +16,92 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+
/**
* Table information for Section Filter.
* @hide
*/
public class SectionSettingsWithTableInfo extends SectionSettings {
- private int mTableId;
- private int mVersion;
+ private final int mTableId;
+ private final int mVersion;
- private SectionSettingsWithTableInfo(int mainType) {
+ private SectionSettingsWithTableInfo(int mainType, int tableId, int version) {
super(mainType);
+ mTableId = tableId;
+ mVersion = version;
}
+
+ /**
+ * Gets table ID.
+ */
+ public int getTableId() {
+ return mTableId;
+ }
+ /**
+ * Gets version.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Creates a builder for {@link SectionSettingsWithTableInfo}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link SectionSettingsWithTableInfo}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private int mTableId;
+ private int mVersion;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets table ID.
+ */
+ @NonNull
+ public Builder setTableId(int tableId) {
+ mTableId = tableId;
+ return this;
+ }
+ /**
+ * Sets version.
+ */
+ @NonNull
+ public Builder setVersion(int version) {
+ mVersion = version;
+ return this;
+ }
+
+ /**
+ * Builds a {@link SectionSettingsWithTableInfo} object.
+ */
+ @NonNull
+ public SectionSettingsWithTableInfo build() {
+ return new SectionSettingsWithTableInfo(mMainType, mTableId, mVersion);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
}
diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java
index 9155926..d697280 100644
--- a/media/java/android/media/tv/tuner/filter/Settings.java
+++ b/media/java/android/media/tv/tuner/filter/Settings.java
@@ -39,4 +39,20 @@
public int getType() {
return mType;
}
+
+
+ /**
+ * Builder for {@link Settings}.
+ *
+ * @param <T> The subclass to be built.
+ * @hide
+ */
+ public abstract static class Builder<T extends Builder<T>> {
+ /* package */ final int mMainType;
+
+ /* package */ Builder(int mainType) {
+ mMainType = mainType;
+ }
+ /* package */ abstract T self();
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
index de8ee75..eb97fc0 100644
--- a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
@@ -16,21 +16,118 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
/**
* Filter configuration for a TLV filter.
* @hide
*/
public class TlvFilterConfiguration extends FilterConfiguration {
- private int mPacketType;
- private boolean mIsCompressedIpPacket;
- private boolean mPassthrough;
+ private final int mPacketType;
+ private final boolean mIsCompressedIpPacket;
+ private final boolean mPassthrough;
- public TlvFilterConfiguration(Settings settings) {
+ public TlvFilterConfiguration(Settings settings, int packetType, boolean isCompressed,
+ boolean passthrough) {
super(settings);
+ mPacketType = packetType;
+ mIsCompressedIpPacket = isCompressed;
+ mPassthrough = passthrough;
}
@Override
public int getType() {
return FilterConfiguration.FILTER_TYPE_TLV;
}
+
+ /**
+ * Gets packet type.
+ */
+ @FilterConfiguration.PacketType
+ public int getPacketType() {
+ return mPacketType;
+ }
+ /**
+ * Checks whether the data is compressed IP packet.
+ *
+ * @return {@code true} if the filtered data is compressed IP packet; {@code false} otherwise.
+ */
+ public boolean isCompressedIpPacket() {
+ return mIsCompressedIpPacket;
+ }
+ /**
+ * Checks whether it's passthrough.
+ *
+ * @return {@code true} if the data from TLV subtype go to next filter directly;
+ * {@code false} otherwise.
+ */
+ public boolean isPassthrough() {
+ return mPassthrough;
+ }
+
+ /**
+ * Creates a builder for {@link TlvFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link TlvFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private int mPacketType;
+ private boolean mIsCompressedIpPacket;
+ private boolean mPassthrough;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets packet type.
+ */
+ @NonNull
+ public Builder setPacketType(@FilterConfiguration.PacketType int packetType) {
+ mPacketType = packetType;
+ return this;
+ }
+ /**
+ * Sets whether the data is compressed IP packet.
+ */
+ @NonNull
+ public Builder setIsCompressedIpPacket(boolean isCompressedIpPacket) {
+ mIsCompressedIpPacket = isCompressedIpPacket;
+ return this;
+ }
+ /**
+ * Sets whether it's passthrough.
+ */
+ @NonNull
+ public Builder setPassthrough(boolean passthrough) {
+ mPassthrough = passthrough;
+ return this;
+ }
+
+ /**
+ * Builds a {@link TlvFilterConfiguration} object.
+ */
+ @NonNull
+ public TlvFilterConfiguration build() {
+ return new TlvFilterConfiguration(
+ mSettings, mPacketType, mIsCompressedIpPacket, mPassthrough);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
index fa4dd72..1b8485e 100644
--- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -16,12 +16,8 @@
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;
/**
* Filter event sent from {@link Filter} objects for TS record data.
@@ -29,47 +25,17 @@
* @hide
*/
public class TsRecordEvent extends FilterEvent {
- /**
- * @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 int mTsIndexMask;
+ private final int mScIndexMask;
private final long mByteNumber;
// This constructor is used by JNI code only
- private TsRecordEvent(int pid, int indexMask, long byteNumber) {
+ private TsRecordEvent(int pid, int tsIndexMask, int scIndexMask, long byteNumber) {
mPid = pid;
- mIndexMask = indexMask;
+ mTsIndexMask = tsIndexMask;
+ mScIndexMask = scIndexMask;
mByteNumber = byteNumber;
}
@@ -81,13 +47,20 @@
}
/**
- * Gets index mask.
- *
- * <p>The index type is one of TS, SC, and SC-HEVC, and is set when configuring the filter.
+ * Gets TS index mask.
*/
- @IndexMask
- public int getIndexMask() {
- return mIndexMask;
+ @RecordSettings.TsIndexMask
+ public int getTsIndexMask() {
+ return mTsIndexMask;
+ }
+ /**
+ * Gets SC index mask.
+ *
+ * <p>The index type is SC or SC-HEVC, and is set when configuring the filter.
+ */
+ @RecordSettings.ScIndexMask
+ public int getScIndexMask() {
+ return mScIndexMask;
}
/**
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
index 2962e98..aa64df5 100644
--- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
@@ -17,24 +17,33 @@
package android.media.tv.tuner.frontend;
/**
- * Analog Capabilities.
+ * Capabilities for analog tuners.
+ *
* @hide
*/
public class AnalogFrontendCapabilities extends FrontendCapabilities {
+ @AnalogFrontendSettings.SignalType
private final int mTypeCap;
+ @AnalogFrontendSettings.SifStandard
private final int mSifStandardCap;
- AnalogFrontendCapabilities(int typeCap, int sifStandardCap) {
+ // Called by JNI code.
+ private AnalogFrontendCapabilities(int typeCap, int sifStandardCap) {
mTypeCap = typeCap;
mSifStandardCap = sifStandardCap;
}
+
/**
- * Gets type capability.
+ * Gets analog signal type capability.
*/
- public int getTypeCapability() {
+ @AnalogFrontendSettings.SignalType
+ public int getSignalTypeCapability() {
return mTypeCap;
}
- /** Gets SIF standard capability. */
+ /**
+ * Gets Standard Interchange Format (SIF) capability.
+ */
+ @AnalogFrontendSettings.SifStandard
public int getSifStandardCapability() {
return mSifStandardCap;
}
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
index aec8ce8..a30ddc7 100644
--- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
@@ -30,8 +30,9 @@
*/
public class AnalogFrontendSettings extends FrontendSettings {
/** @hide */
- @IntDef(flag = true, value = {SIGNAL_TYPE_UNDEFINED, SIGNAL_TYPE_PAL, SIGNAL_TYPE_SECAM,
- SIGNAL_TYPE_NTSC})
+ @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 {}
@@ -54,7 +55,9 @@
/** @hide */
- @IntDef(flag = true, value = {SIF_UNDEFINED, SIF_BG, SIF_BG_A2, SIF_BG_NICAM, SIF_I, SIF_DK,
+ @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)
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 5b09e36..5e1ba72 100644
--- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
@@ -16,19 +16,357 @@
package android.media.tv.tuner.frontend;
-import java.util.List;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Frontend settings for ATSC-3.
* @hide
*/
public class Atsc3FrontendSettings extends FrontendSettings {
- 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
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 19e18d0..32901d8 100644
--- a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
@@ -16,15 +16,105 @@
package android.media.tv.tuner.frontend;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Frontend settings for ATSC.
* @hide
*/
public class AtscFrontendSettings extends FrontendSettings {
- 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
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 60618f6..3d212d3 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
@@ -16,20 +16,279 @@
package android.media.tv.tuner.frontend;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Frontend settings for DVBC.
* @hide
*/
public class DvbcFrontendSettings extends FrontendSettings {
- 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
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 586787f..5b3bffc4 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -16,21 +16,344 @@
package android.media.tv.tuner.frontend;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Frontend settings for DVBS.
* @hide
*/
public class DvbsFrontendSettings extends FrontendSettings {
- 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
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 6b350a7..f0469b7 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
@@ -16,27 +16,631 @@
package android.media.tv.tuner.frontend;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Frontend settings for DVBT.
* @hide
*/
public class DvbtFrontendSettings extends FrontendSettings {
- 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
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
index 7350bc0..e4f66b8 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
@@ -17,7 +17,8 @@
package android.media.tv.tuner.frontend;
/**
- * Frontend Capabilities.
+ * Frontend capabilities.
+ *
* @hide
*/
public abstract class FrontendCapabilities {
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
index 99e8dd2..360c84a 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -16,77 +16,98 @@
package android.media.tv.tuner.frontend;
+import android.annotation.NonNull;
import android.media.tv.tuner.frontend.FrontendSettings.Type;
+import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
+import android.util.Range;
/**
- * Frontend info.
+ * This class is used to specify meta information of a frontend.
+ *
* @hide
*/
public class FrontendInfo {
private final int mId;
private final int mType;
- private final int mMinFrequency;
- private final int mMaxFrequency;
- private final int mMinSymbolRate;
- private final int mMaxSymbolRate;
+ private final Range<Integer> mFrequencyRange;
+ private final Range<Integer> mSymbolRateRange;
private final int mAcquireRange;
private final int mExclusiveGroupId;
private final int[] mStatusCaps;
private final FrontendCapabilities mFrontendCap;
- FrontendInfo(int id, int type, int minFrequency, int maxFrequency, int minSymbolRate,
+ private FrontendInfo(int id, int type, int minFrequency, int maxFrequency, int minSymbolRate,
int maxSymbolRate, int acquireRange, int exclusiveGroupId, int[] statusCaps,
FrontendCapabilities frontendCap) {
mId = id;
mType = type;
- mMinFrequency = minFrequency;
- mMaxFrequency = maxFrequency;
- mMinSymbolRate = minSymbolRate;
- mMaxSymbolRate = maxSymbolRate;
+ mFrequencyRange = new Range<>(minFrequency, maxFrequency);
+ mSymbolRateRange = new Range<>(minSymbolRate, maxSymbolRate);
mAcquireRange = acquireRange;
mExclusiveGroupId = exclusiveGroupId;
mStatusCaps = statusCaps;
mFrontendCap = frontendCap;
}
- /** Gets frontend ID. */
+ /**
+ * Gets frontend ID.
+ */
public int getId() {
return mId;
}
- /** Gets frontend type. */
+ /**
+ * Gets frontend type.
+ */
@Type
public int getType() {
return mType;
}
- /** Gets min frequency. */
- public int getMinFrequency() {
- return mMinFrequency;
+
+ /**
+ * Gets supported frequency range in Hz.
+ */
+ @NonNull
+ public Range<Integer> getFrequencyRange() {
+ return mFrequencyRange;
}
- /** Gets max frequency. */
- public int getMaxFrequency() {
- return mMaxFrequency;
+
+ /**
+ * Gets symbol rate range in symbols per second.
+ */
+ @NonNull
+ public Range<Integer> getSymbolRateRange() {
+ return mSymbolRateRange;
}
- /** Gets min symbol rate. */
- public int getMinSymbolRate() {
- return mMinSymbolRate;
- }
- /** Gets max symbol rate. */
- public int getMaxSymbolRate() {
- return mMaxSymbolRate;
- }
- /** Gets acquire range. */
+
+ /**
+ * Gets acquire range in Hz.
+ *
+ * <p>The maximum frequency difference the frontend can detect.
+ */
public int getAcquireRange() {
return mAcquireRange;
}
- /** Gets exclusive group ID. */
+ /**
+ * Gets exclusive group ID.
+ *
+ * <p>Frontends with the same exclusive group ID indicates they can't function at same time. For
+ * instance, they share some hardware modules.
+ */
public int getExclusiveGroupId() {
return mExclusiveGroupId;
}
- /** Gets status capabilities. */
+ /**
+ * Gets status capabilities.
+ *
+ * @return An array of supported status types.
+ */
+ @FrontendStatusType
public int[] getStatusCapabilities() {
return mStatusCaps;
}
- /** Gets frontend capability. */
+ /**
+ * Gets frontend capabilities.
+ */
public FrontendCapabilities getFrontendCapability() {
return mFrontendCap;
}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
index 210aef4..617d608 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
@@ -17,6 +17,8 @@
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;
@@ -97,4 +99,26 @@
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 fb5d62a..088adff 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -16,13 +16,15 @@
package android.media.tv.tuner.frontend;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
import android.media.tv.tuner.Lnb;
-import android.media.tv.tuner.TunerConstants;
-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 java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Frontend status
@@ -31,204 +33,409 @@
*/
public class FrontendStatus {
- private final int mType;
- private final Object mValue;
+ /** @hide */
+ @IntDef({FRONTEND_STATUS_TYPE_DEMOD_LOCK, FRONTEND_STATUS_TYPE_SNR, FRONTEND_STATUS_TYPE_BER,
+ FRONTEND_STATUS_TYPE_PER, FRONTEND_STATUS_TYPE_PRE_BER,
+ FRONTEND_STATUS_TYPE_SIGNAL_QUALITY, FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH,
+ FRONTEND_STATUS_TYPE_SYMBOL_RATE, FRONTEND_STATUS_TYPE_FEC,
+ FRONTEND_STATUS_TYPE_MODULATION, FRONTEND_STATUS_TYPE_SPECTRAL,
+ FRONTEND_STATUS_TYPE_LNB_VOLTAGE, FRONTEND_STATUS_TYPE_PLP_ID,
+ FRONTEND_STATUS_TYPE_EWBS, FRONTEND_STATUS_TYPE_AGC, FRONTEND_STATUS_TYPE_LNA,
+ FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_VBER_CN,
+ FRONTEND_STATUS_TYPE_LBER_CN, FRONTEND_STATUS_TYPE_XER_CN, FRONTEND_STATUS_TYPE_MER,
+ FRONTEND_STATUS_TYPE_FREQ_OFFSET, FRONTEND_STATUS_TYPE_HIERARCHY,
+ FRONTEND_STATUS_TYPE_RF_LOCK, FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FrontendStatusType {}
- private FrontendStatus(int type, Object value) {
- mType = type;
- mValue = value;
+ /**
+ * Lock status for Demod.
+ */
+ public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK =
+ Constants.FrontendStatusType.DEMOD_LOCK;
+ /**
+ * Signal to Noise Ratio.
+ */
+ public static final int FRONTEND_STATUS_TYPE_SNR = Constants.FrontendStatusType.SNR;
+ /**
+ * Bit Error Ratio.
+ */
+ public static final int FRONTEND_STATUS_TYPE_BER = Constants.FrontendStatusType.BER;
+ /**
+ * Packages Error Ratio.
+ */
+ public static final int FRONTEND_STATUS_TYPE_PER = Constants.FrontendStatusType.PER;
+ /**
+ * Bit Error Ratio before FEC.
+ */
+ public static final int FRONTEND_STATUS_TYPE_PRE_BER = Constants.FrontendStatusType.PRE_BER;
+ /**
+ * Signal Quality (0..100). Good data over total data in percent can be
+ * used as a way to present Signal Quality.
+ */
+ public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY =
+ Constants.FrontendStatusType.SIGNAL_QUALITY;
+ /**
+ * Signal Strength.
+ */
+ public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH =
+ Constants.FrontendStatusType.SIGNAL_STRENGTH;
+ /**
+ * Symbol Rate in symbols per second.
+ */
+ public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE =
+ Constants.FrontendStatusType.SYMBOL_RATE;
+ /**
+ * Forward Error Correction Type.
+ */
+ public static final int FRONTEND_STATUS_TYPE_FEC = Constants.FrontendStatusType.FEC;
+ /**
+ * Modulation Type.
+ */
+ public static final int FRONTEND_STATUS_TYPE_MODULATION =
+ Constants.FrontendStatusType.MODULATION;
+ /**
+ * Spectral Inversion Type.
+ */
+ public static final int FRONTEND_STATUS_TYPE_SPECTRAL = Constants.FrontendStatusType.SPECTRAL;
+ /**
+ * LNB Voltage.
+ */
+ public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE =
+ Constants.FrontendStatusType.LNB_VOLTAGE;
+ /**
+ * Physical Layer Pipe ID.
+ */
+ public static final int FRONTEND_STATUS_TYPE_PLP_ID = Constants.FrontendStatusType.PLP_ID;
+ /**
+ * Status for Emergency Warning Broadcasting System.
+ */
+ public static final int FRONTEND_STATUS_TYPE_EWBS = Constants.FrontendStatusType.EWBS;
+ /**
+ * Automatic Gain Control.
+ */
+ public static final int FRONTEND_STATUS_TYPE_AGC = Constants.FrontendStatusType.AGC;
+ /**
+ * Low Noise Amplifier.
+ */
+ public static final int FRONTEND_STATUS_TYPE_LNA = Constants.FrontendStatusType.LNA;
+ /**
+ * Error status by layer.
+ */
+ public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR =
+ Constants.FrontendStatusType.LAYER_ERROR;
+ /**
+ * CN value by VBER.
+ */
+ public static final int FRONTEND_STATUS_TYPE_VBER_CN = Constants.FrontendStatusType.VBER_CN;
+ /**
+ * CN value by LBER.
+ */
+ public static final int FRONTEND_STATUS_TYPE_LBER_CN = Constants.FrontendStatusType.LBER_CN;
+ /**
+ * CN value by XER.
+ */
+ public static final int FRONTEND_STATUS_TYPE_XER_CN = Constants.FrontendStatusType.XER_CN;
+ /**
+ * Modulation Error Ratio.
+ */
+ public static final int FRONTEND_STATUS_TYPE_MER = Constants.FrontendStatusType.MER;
+ /**
+ * Difference between tuning frequency and actual locked frequency.
+ */
+ public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET =
+ Constants.FrontendStatusType.FREQ_OFFSET;
+ /**
+ * Hierarchy for DVBT.
+ */
+ public static final int FRONTEND_STATUS_TYPE_HIERARCHY = Constants.FrontendStatusType.HIERARCHY;
+ /**
+ * Lock status for RF.
+ */
+ public static final int FRONTEND_STATUS_TYPE_RF_LOCK = Constants.FrontendStatusType.RF_LOCK;
+ /**
+ * PLP information in a frequency band for ATSC-3.0 frontend.
+ */
+ public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO =
+ Constants.FrontendStatusType.ATSC3_PLP_INFO;
+
+
+ private Boolean mIsDemodLocked;
+ private Integer mSnr;
+ private Integer mBer;
+ private Integer mPer;
+ private Integer mPerBer;
+ private Integer mSignalQuality;
+ private Integer mSignalStrength;
+ private Integer mSymbolRate;
+ private Long mInnerFec;
+ private Integer mModulation;
+ private Integer mInversion;
+ private Integer mLnbVoltage;
+ private Integer mPlpId;
+ private Boolean mIsEwbs;
+ private Integer mAgc;
+ private Boolean mIsLnaOn;
+ private boolean[] mIsLayerErrors;
+ private Integer mVberCn;
+ private Integer mLberCn;
+ private Integer mXerCn;
+ private Integer mMer;
+ private Integer mFreqOffset;
+ private Integer mHierarchy;
+ private Boolean mIsRfLocked;
+ private Atsc3PlpInfo[] mPlpInfo;
+
+ // Constructed and fields set by JNI code.
+ private FrontendStatus() {
}
- /** Gets frontend status type. */
- @FrontendStatusType
- public int getStatusType() {
- return mType;
- }
- /** Lock status for Demod in True/False. */
- public boolean getIsDemodLocked() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_DEMOD_LOCK) {
+ /**
+ * Lock status for Demod.
+ */
+ public boolean isDemodLocked() {
+ if (mIsDemodLocked == null) {
throw new IllegalStateException();
}
- return (Boolean) mValue;
- }
- /** SNR value measured by 0.001 dB. */
- public int getSnr() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SNR) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** The number of error bit per 1 billion bits. */
- public int getBer() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_BER) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** The number of error package per 1 billion packages. */
- public int getPer() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PER) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** The number of error bit per 1 billion bits before FEC. */
- public int getPerBer() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PRE_BER) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** Signal Quality in percent. */
- public int getSignalQuality() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SIGNAL_QUALITY) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** Signal Strength measured by 0.001 dBm. */
- public int getSignalStrength() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** Symbols per second. */
- public int getSymbolRate() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SYMBOL_RATE) {
- throw new IllegalStateException();
- }
- return (int) mValue;
+ return mIsDemodLocked;
}
/**
- * Inner Forward Error Correction type as specified in ETSI EN 300 468 V1.15.1
+ * Gets Signal to Noise Ratio in thousandths of a deciBel (0.001dB).
+ */
+ public int getSnr() {
+ if (mSnr == null) {
+ throw new IllegalStateException();
+ }
+ return mSnr;
+ }
+ /**
+ * Gets Bit Error Ratio.
+ *
+ * <p>The number of error bit per 1 billion bits.
+ */
+ public int getBer() {
+ if (mBer == null) {
+ throw new IllegalStateException();
+ }
+ return mBer;
+ }
+
+ /**
+ * Gets Packages Error Ratio.
+ *
+ * <p>The number of error package per 1 billion packages.
+ */
+ public int getPer() {
+ if (mPer == null) {
+ throw new IllegalStateException();
+ }
+ return mPer;
+ }
+ /**
+ * Gets Bit Error Ratio before Forward Error Correction (FEC).
+ *
+ * <p>The number of error bit per 1 billion bits before FEC.
+ */
+ public int getPerBer() {
+ if (mPerBer == null) {
+ throw new IllegalStateException();
+ }
+ return mPerBer;
+ }
+ /**
+ * Gets Signal Quality in percent.
+ */
+ public int getSignalQuality() {
+ if (mSignalQuality == null) {
+ throw new IllegalStateException();
+ }
+ return mSignalQuality;
+ }
+ /**
+ * Gets Signal Strength in thousandths of a dBm (0.001dBm).
+ */
+ public int getSignalStrength() {
+ if (mSignalStrength == null) {
+ throw new IllegalStateException();
+ }
+ return mSignalStrength;
+ }
+ /**
+ * Gets symbol rate in symbols per second.
+ */
+ public int getSymbolRate() {
+ if (mSymbolRate == null) {
+ throw new IllegalStateException();
+ }
+ return mSymbolRate;
+ }
+ /**
+ * Gets Inner Forward Error Correction type as specified in ETSI EN 300 468 V1.15.1
* and ETSI EN 302 307-2 V1.1.1.
*/
@FrontendInnerFec
public long getFec() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_FEC) {
+ if (mInnerFec == null) {
throw new IllegalStateException();
}
- return (long) mValue;
+ return mInnerFec;
}
- /** Modulation */
+ /**
+ * Gets modulation.
+ */
@FrontendModulation
public int getModulation() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_MODULATION) {
+ if (mModulation == null) {
throw new IllegalStateException();
}
- return (int) mValue;
+ return mModulation;
}
- /** Spectral Inversion for DVBC. */
- @FrontendDvbcSpectralInversion
+ /**
+ * Gets Spectral Inversion for DVBC.
+ */
+ @DvbcFrontendSettings.SpectralInversion
public int getSpectralInversion() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SPECTRAL) {
+ if (mInversion == null) {
throw new IllegalStateException();
}
- return (int) mValue;
+ return mInversion;
}
- /** Power Voltage Type for LNB. */
+ /**
+ * Gets Power Voltage Type for LNB.
+ */
@Lnb.Voltage
public int getLnbVoltage() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNB_VOLTAGE) {
+ if (mLnbVoltage == null) {
throw new IllegalStateException();
}
- return (int) mValue;
+ return mLnbVoltage;
}
- /** PLP ID */
- public byte getPlpId() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PLP_ID) {
+ /**
+ * Gets Physical Layer Pipe ID.
+ */
+ public int getPlpId() {
+ if (mPlpId == null) {
throw new IllegalStateException();
}
- return (byte) mValue;
+ return mPlpId;
}
- /** Emergency Warning Broadcasting System */
- public boolean getIsEwbs() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_EWBS) {
+ /**
+ * Checks whether it's Emergency Warning Broadcasting System
+ */
+ public boolean isEwbs() {
+ if (mIsEwbs == null) {
throw new IllegalStateException();
}
- return (Boolean) mValue;
+ return mIsEwbs;
}
- /** AGC value is normalized from 0 to 255. */
- public byte getAgc() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_AGC) {
+ /**
+ * Gets Automatic Gain Control value which is normalized from 0 to 255.
+ */
+ public int getAgc() {
+ if (mAgc == null) {
throw new IllegalStateException();
}
- return (byte) mValue;
+ return mAgc;
}
- /** LNA(Low Noise Amplifier) is on or not. */
- public boolean getLnaOn() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNA) {
+ /**
+ * Checks LNA (Low Noise Amplifier) is on or not.
+ */
+ public boolean isLnaOn() {
+ if (mIsLnaOn == null) {
throw new IllegalStateException();
}
- return (Boolean) mValue;
+ return mIsLnaOn;
}
- /** Error status by layer. */
- public boolean[] getIsLayerError() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LAYER_ERROR) {
+ /**
+ * Gets Error status by layer.
+ */
+ @NonNull
+ public boolean[] getLayerErrors() {
+ if (mIsLayerErrors == null) {
throw new IllegalStateException();
}
- return (boolean[]) mValue;
+ return mIsLayerErrors;
}
- /** CN value by VBER measured by 0.001 dB. */
+ /**
+ * Gets CN value by VBER in thousandths of a deciBel (0.001dB).
+ */
public int getVberCn() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_VBER_CN) {
+ if (mVberCn == null) {
throw new IllegalStateException();
}
- return (int) mValue;
+ return mVberCn;
}
- /** CN value by LBER measured by 0.001 dB. */
+ /**
+ * Gets CN value by LBER in thousandths of a deciBel (0.001dB).
+ */
public int getLberCn() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LBER_CN) {
+ if (mLberCn == null) {
throw new IllegalStateException();
}
- return (int) mValue;
+ return mLberCn;
}
- /** CN value by XER measured by 0.001 dB. */
+ /**
+ * Gets CN value by XER in thousandths of a deciBel (0.001dB).
+ */
public int getXerCn() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_XER_CN) {
+ if (mXerCn == null) {
throw new IllegalStateException();
}
- return (int) mValue;
+ return mXerCn;
}
- /** MER value measured by 0.001 dB. */
+ /**
+ * Gets Modulation Error Ratio in thousandths of a deciBel (0.001dB).
+ */
public int getMer() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_MER) {
+ if (mMer == null) {
throw new IllegalStateException();
}
- return (int) mValue;
+ return mMer;
}
- /** Frequency difference in Hertz. */
+ /**
+ * Gets frequency difference in Hz.
+ *
+ * <p>Difference between tuning frequency and actual locked frequency.
+ */
public int getFreqOffset() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_FREQ_OFFSET) {
+ if (mFreqOffset == null) {
throw new IllegalStateException();
}
- return (int) mValue;
+ return mFreqOffset;
}
- /** Hierarchy Type for DVBT. */
- @FrontendDvbtHierarchy
+ /**
+ * Gets hierarchy Type for DVBT.
+ */
+ @DvbtFrontendSettings.Hierarchy
public int getHierarchy() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_HIERARCHY) {
+ if (mHierarchy == null) {
throw new IllegalStateException();
}
- return (int) mValue;
+ return mHierarchy;
}
- /** Lock status for RF. */
- public boolean getIsRfLock() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_RF_LOCK) {
+ /**
+ * Gets lock status for RF.
+ */
+ public boolean isRfLock() {
+ if (mIsRfLocked == null) {
throw new IllegalStateException();
}
- return (Boolean) mValue;
+ return mIsRfLocked;
}
- /** A list of PLP status for tuned PLPs for ATSC3 frontend. */
+ /**
+ * Gets an array of PLP status for tuned PLPs for ATSC3 frontend.
+ */
+ @NonNull
public Atsc3PlpInfo[] getAtsc3PlpInfo() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO) {
+ if (mPlpInfo == null) {
throw new IllegalStateException();
}
- return (Atsc3PlpInfo[]) mValue;
+ return mPlpInfo;
}
- /** Status for each tuning PLPs. */
+ /**
+ * Status for each tuning Physical Layer Pipes.
+ */
public static class Atsc3PlpInfo {
private final int mPlpId;
private final boolean mIsLock;
@@ -240,17 +447,20 @@
mUec = uec;
}
- /** Gets PLP IDs. */
+ /**
+ * Gets Physical Layer Pipe ID.
+ */
public int getPlpId() {
return mPlpId;
}
- /** Gets Demod Lock/Unlock status of this particular PLP. */
- public boolean getIsLock() {
+ /**
+ * Gets Demod Lock/Unlock status of this particular PLP.
+ */
+ public boolean isLock() {
return mIsLock;
}
/**
- * Gets Uncorrectable Error Counts (UEC) of this particular PLP since last tune
- * operation.
+ * Gets Uncorrectable Error Counts (UEC) of this particular PLP since last tune operation.
*/
public int getUec() {
return mUec;
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
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 45932a7..7e6f188 100644
--- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
@@ -16,20 +16,284 @@
package android.media.tv.tuner.frontend;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Frontend settings for ISDBS-3.
* @hide
*/
public class Isdbs3FrontendSettings extends FrontendSettings {
- 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
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 e726a9a..fe100f8 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
@@ -16,20 +16,269 @@
package android.media.tv.tuner.frontend;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Frontend settings for ISDBS.
* @hide
*/
public class IsdbsFrontendSettings extends FrontendSettings {
- 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
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 f2b7d24..1510193 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -16,19 +16,243 @@
package android.media.tv.tuner.frontend;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Frontend settings for ISDBT.
* @hide
*/
public class IsdbtFrontendSettings extends FrontendSettings {
- 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
diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
index 8118fcc..5e7d218 100644
--- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java
+++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
@@ -16,8 +16,6 @@
package android.media.tv.tuner.frontend;
-import android.media.tv.tuner.TunerConstants;
-
/**
* Scan callback.
*
@@ -49,10 +47,10 @@
void onInputStreamIds(int[] inputStreamIds);
/** Locked signal standard. */
- void onDvbsStandard(@TunerConstants.FrontendDvbsStandard int dvbsStandandard);
+ void onDvbsStandard(@DvbsFrontendSettings.Standard int dvbsStandandard);
/** Locked signal standard. */
- void onDvbtStandard(@TunerConstants.FrontendDvbtStandard int dvbtStandard);
+ void onDvbtStandard(@DvbtFrontendSettings.Standard int dvbtStandard);
/** PLP status in a tuned frequency band for ATSC3 frontend. */
void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos);
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java
new file mode 100644
index 0000000..c46966f
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediaroutertest;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.media.MediaRoute2Info;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests {@link MediaRoute2Info} and its {@link MediaRoute2Info.Builder builder}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaRoute2InfoTest {
+
+ public static final String TEST_ID = "test_id";
+ public static final String TEST_NAME = "test_name";
+ public static final String TEST_ROUTE_TYPE_0 = "test_route_type_0";
+ public static final String TEST_ROUTE_TYPE_1 = "test_route_type_1";
+ public static final int TEST_DEVICE_TYPE = MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
+ public static final Uri TEST_ICON_URI = Uri.parse("https://developer.android.com");
+ public static final String TEST_DESCRIPTION = "test_description";
+ public static final int TEST_CONNECTION_STATE = MediaRoute2Info.CONNECTION_STATE_CONNECTING;
+ public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
+ public static final int TEST_VOLUME_HANDLING = MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+ public static final int TEST_VOLUME_MAX = 100;
+ public static final int TEST_VOLUME = 65;
+
+ public static final String TEST_KEY = "test_key";
+ public static final String TEST_VALUE = "test_value";
+
+ @Test
+ public void testBuilderConstructorWithInvalidValues() {
+ final String nullId = null;
+ final String nullName = null;
+ final String emptyId = "";
+ final String emptyName = "";
+ final String validId = "valid_id";
+ final String validName = "valid_name";
+
+ // ID is invalid
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(nullId, validName));
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(emptyId, validName));
+
+ // name is invalid
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(validId, nullName));
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(validId, emptyName));
+
+ // Both are invalid
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(nullId, nullName));
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(nullId, emptyName));
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(emptyId, nullName));
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(emptyId, emptyName));
+
+
+ // Null RouteInfo (1-argument constructor)
+ final MediaRoute2Info nullRouteInfo = null;
+ assertThrows(NullPointerException.class,
+ () -> new MediaRoute2Info.Builder(nullRouteInfo));
+ }
+
+ @Test
+ public void testBuilderBuildWithEmptyRouteTypesShouldThrowIAE() {
+ MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME);
+ assertThrows(IllegalArgumentException.class, () -> builder.build());
+ }
+
+ @Test
+ public void testBuilderAndGettersOfMediaRoute2Info() {
+ Bundle extras = new Bundle();
+ extras.putString(TEST_KEY, TEST_VALUE);
+
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ assertEquals(TEST_ID, routeInfo.getId());
+ assertEquals(TEST_NAME, routeInfo.getName());
+
+ assertEquals(2, routeInfo.getFeatures().size());
+ assertEquals(TEST_ROUTE_TYPE_0, routeInfo.getFeatures().get(0));
+ assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(1));
+
+ assertEquals(TEST_DEVICE_TYPE, routeInfo.getDeviceType());
+ assertEquals(TEST_ICON_URI, routeInfo.getIconUri());
+ assertEquals(TEST_DESCRIPTION, routeInfo.getDescription());
+ assertEquals(TEST_CONNECTION_STATE, routeInfo.getConnectionState());
+ assertEquals(TEST_CLIENT_PACKAGE_NAME, routeInfo.getClientPackageName());
+ assertEquals(TEST_VOLUME_HANDLING, routeInfo.getVolumeHandling());
+ assertEquals(TEST_VOLUME_MAX, routeInfo.getVolumeMax());
+ assertEquals(TEST_VOLUME, routeInfo.getVolume());
+
+ Bundle extrasOut = routeInfo.getExtras();
+ assertNotNull(extrasOut);
+ assertTrue(extrasOut.containsKey(TEST_KEY));
+ assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
+ }
+
+ @Test
+ public void testBuilderSetExtrasWithNull() {
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .setExtras(null)
+ .build();
+
+ assertNull(routeInfo.getExtras());
+ }
+
+ @Test
+ public void testBuilderaddFeatures() {
+ List<String> routeTypes = new ArrayList<>();
+ routeTypes.add(TEST_ROUTE_TYPE_0);
+ routeTypes.add(TEST_ROUTE_TYPE_1);
+
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeatures(routeTypes)
+ .build();
+
+ assertEquals(routeTypes, routeInfo.getFeatures());
+ }
+
+ @Test
+ public void testBuilderclearFeatures() {
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ // clearFeatures should clear the route types.
+ .clearFeatures()
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .build();
+
+ assertEquals(1, routeInfo.getFeatures().size());
+ assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(0));
+ }
+
+ @Test
+ public void testhasAnyFeaturesReturnsFalse() {
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .build();
+
+ List<String> testRouteTypes = new ArrayList<>();
+ testRouteTypes.add("non_matching_route_type_1");
+ testRouteTypes.add("non_matching_route_type_2");
+ testRouteTypes.add("non_matching_route_type_3");
+ testRouteTypes.add("non_matching_route_type_4");
+
+ assertFalse(routeInfo.hasAnyFeatures(testRouteTypes));
+ }
+
+ @Test
+ public void testhasAnyFeaturesReturnsTrue() {
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .build();
+
+ List<String> testRouteTypes = new ArrayList<>();
+ testRouteTypes.add("non_matching_route_type_1");
+ testRouteTypes.add("non_matching_route_type_2");
+ testRouteTypes.add("non_matching_route_type_3");
+ testRouteTypes.add(TEST_ROUTE_TYPE_1);
+
+ assertTrue(routeInfo.hasAnyFeatures(testRouteTypes));
+ }
+
+ @Test
+ public void testEqualsCreatedWithSameArguments() {
+ Bundle extras = new Bundle();
+ extras.putString(TEST_KEY, TEST_VALUE);
+
+ MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ assertEquals(routeInfo1, routeInfo2);
+ assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode());
+ }
+
+ @Test
+ public void testEqualsCreatedWithBuilderCopyConstructor() {
+ Bundle extras = new Bundle();
+ extras.putString(TEST_KEY, TEST_VALUE);
+
+ MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(routeInfo1).build();
+
+ assertEquals(routeInfo1, routeInfo2);
+ assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode());
+ }
+
+ @Test
+ public void testEqualsReturnFalse() {
+ Bundle extras = new Bundle();
+ extras.putString(TEST_KEY, TEST_VALUE);
+
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ // Now, we will use copy constructor
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .addFeature("randomRouteType")
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setDeviceType(TEST_DEVICE_TYPE + 1)
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setIconUri(Uri.parse("randomUri"))
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setDescription("randomDescription")
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setConnectionState(TEST_CONNECTION_STATE + 1)
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setClientPackageName("randomPackageName")
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setVolumeHandling(TEST_VOLUME_HANDLING + 1)
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setVolumeMax(TEST_VOLUME_MAX + 100)
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setVolume(TEST_VOLUME + 10)
+ .build());
+ // Note: Extras will not affect the equals.
+ }
+
+ @Test
+ public void testParcelingAndUnParceling() {
+ Bundle extras = new Bundle();
+ extras.putString(TEST_KEY, TEST_VALUE);
+
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ routeInfo.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ MediaRoute2Info routeInfoFromParcel = MediaRoute2Info.CREATOR.createFromParcel(parcel);
+ assertEquals(routeInfo, routeInfoFromParcel);
+ assertEquals(routeInfo.hashCode(), routeInfoFromParcel.hashCode());
+
+ // Check extras
+ Bundle extrasOut = routeInfoFromParcel.getExtras();
+ assertNotNull(extrasOut);
+ assertTrue(extrasOut.containsKey(TEST_KEY));
+ assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
+ }
+}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 007229a..59e1122 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -16,17 +16,13 @@
package com.android.mediaroutertest;
-import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTED;
import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTING;
import static android.media.MediaRoute2Info.DEVICE_TYPE_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;
@@ -132,57 +128,6 @@
}
@Test
- public void testRouteInfoInequality() {
- MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name")
- .setDescription("description")
- .setClientPackageName("com.android.mediaroutertest")
- .setConnectionState(CONNECTION_STATE_CONNECTING)
- .setIconUri(new Uri.Builder().path("icon").build())
- .addFeature(FEATURE_SAMPLE)
- .setVolume(5)
- .setVolumeMax(20)
- .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
- .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
- .build();
-
- MediaRoute2Info routeDescription = new MediaRoute2Info.Builder(route)
- .setDescription("another description").build();
- assertNotEquals(route, routeDescription);
-
- MediaRoute2Info routeConnectionState = new MediaRoute2Info.Builder(route)
- .setConnectionState(CONNECTION_STATE_CONNECTED).build();
- assertNotEquals(route, routeConnectionState);
-
- MediaRoute2Info routeIcon = new MediaRoute2Info.Builder(route)
- .setIconUri(new Uri.Builder().path("new icon").build()).build();
- assertNotEquals(route, routeIcon);
-
- MediaRoute2Info routeClient = new MediaRoute2Info.Builder(route)
- .setClientPackageName("another.client.package").build();
- assertNotEquals(route, routeClient);
-
- MediaRoute2Info routeType = new MediaRoute2Info.Builder(route)
- .addFeature(FEATURE_SPECIAL).build();
- assertNotEquals(route, routeType);
-
- MediaRoute2Info routeVolume = new MediaRoute2Info.Builder(route)
- .setVolume(10).build();
- assertNotEquals(route, routeVolume);
-
- MediaRoute2Info routeVolumeMax = new MediaRoute2Info.Builder(route)
- .setVolumeMax(30).build();
- assertNotEquals(route, routeVolumeMax);
-
- MediaRoute2Info routeVolumeHandling = new MediaRoute2Info.Builder(route)
- .setVolumeHandling(PLAYBACK_VOLUME_FIXED).build();
- assertNotEquals(route, routeVolumeHandling);
-
- MediaRoute2Info routeDeviceType = new MediaRoute2Info.Builder(route)
- .setVolume(DEVICE_TYPE_REMOTE_TV).build();
- assertNotEquals(route, routeDeviceType);
- }
-
- @Test
public void testControlVolumeWithRouter() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_ALL);
@@ -228,8 +173,10 @@
@Test
public void testRequestCreateSessionWithInvalidArguments() {
- MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build();
String routeFeature = "routeFeature";
+ MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name")
+ .addFeature(routeFeature)
+ .build();
// Tests null route
assertThrows(NullPointerException.class,
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index ae15b91..83dd0c0 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -20,7 +20,6 @@
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -143,20 +142,6 @@
clearCallbacks();
}
- //TODO: Move to a separate file
- @Test
- public void testMediaRoute2Info() {
- MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder("id", "name")
- .build();
- MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(routeInfo1).build();
-
- MediaRoute2Info routeInfo3 = new MediaRoute2Info.Builder(routeInfo1)
- .setClientPackageName(mPackageName).build();
-
- assertEquals(routeInfo1, routeInfo2);
- assertNotEquals(routeInfo1, routeInfo3);
- }
-
/**
* Tests if routes are added correctly when a new callback is registered.
*/
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 41a9b24..5054281 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -49,7 +49,6 @@
import android.widget.ProgressBar;
import android.widget.TextView;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.TrafficStatsConstants;
@@ -207,7 +206,7 @@
if (TextUtils.isEmpty(url)) url = mCm.getCaptivePortalServerUrl();
final CarrierConfigManager configManager = getApplicationContext()
.getSystemService(CarrierConfigManager.class);
- final int subId = getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ final int subId = getIntent().getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
final String[] portalURLs = configManager.getConfigForSubId(subId).getStringArray(
CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY);
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index c2ce840..9ccb837 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -56,6 +56,7 @@
import android.os.RemoteException;
import android.os.image.DynamicSystemClient;
import android.os.image.DynamicSystemManager;
+import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
@@ -74,6 +75,8 @@
// TODO (b/131866826): This is currently for test only. Will move this to System API.
static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
+ static final String KEY_DSU_SLOT = "KEY_DSU_SLOT";
+ static final String DEFAULT_DSU_SLOT = "dsu";
/*
* Intent actions
@@ -244,10 +247,15 @@
long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
+ String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT);
+ if (TextUtils.isEmpty(dsuSlot)) {
+ dsuSlot = DEFAULT_DSU_SLOT;
+ }
// TODO: better constructor or builder
- mInstallTask = new InstallationAsyncTask(
- url, systemSize, userdataSize, this, mDynSystem, this);
+ mInstallTask =
+ new InstallationAsyncTask(
+ url, dsuSlot, systemSize, userdataSize, this, mDynSystem, this);
mInstallTask.execute();
@@ -409,7 +417,9 @@
break;
case STATUS_READY:
- builder.setContentText(getString(R.string.notification_install_completed));
+ String msgCompleted = getString(R.string.notification_install_completed);
+ builder.setContentText(msgCompleted)
+ .setStyle(new Notification.BigTextStyle().bigText(msgCompleted));
builder.addAction(new Notification.Action.Builder(
null, getString(R.string.notification_action_discard),
@@ -422,7 +432,9 @@
break;
case STATUS_IN_USE:
- builder.setContentText(getString(R.string.notification_dynsystem_in_use));
+ String msgInUse = getString(R.string.notification_dynsystem_in_use);
+ builder.setContentText(msgInUse)
+ .setStyle(new Notification.BigTextStyle().bigText(msgInUse));
builder.addAction(new Notification.Action.Builder(
null, getString(R.string.notification_action_uninstall),
@@ -452,7 +464,49 @@
}
private void postStatus(int status, int cause, Throwable detail) {
- Log.d(TAG, "postStatus(): statusCode=" + status + ", causeCode=" + cause);
+ String statusString;
+ String causeString;
+
+ switch (status) {
+ case STATUS_NOT_STARTED:
+ statusString = "NOT_STARTED";
+ break;
+ case STATUS_IN_PROGRESS:
+ statusString = "IN_PROGRESS";
+ break;
+ case STATUS_READY:
+ statusString = "READY";
+ break;
+ case STATUS_IN_USE:
+ statusString = "IN_USE";
+ break;
+ default:
+ statusString = "UNKNOWN";
+ break;
+ }
+
+ switch (cause) {
+ case CAUSE_INSTALL_COMPLETED:
+ causeString = "INSTALL_COMPLETED";
+ break;
+ case CAUSE_INSTALL_CANCELLED:
+ causeString = "INSTALL_CANCELLED";
+ break;
+ case CAUSE_ERROR_IO:
+ causeString = "ERROR_IO";
+ break;
+ case CAUSE_ERROR_INVALID_URL:
+ causeString = "ERROR_INVALID_URL";
+ break;
+ case CAUSE_ERROR_EXCEPTION:
+ causeString = "ERROR_EXCEPTION";
+ break;
+ default:
+ causeString = "CAUSE_NOT_SPECIFIED";
+ break;
+ }
+
+ Log.d(TAG, "status=" + statusString + ", cause=" + causeString);
boolean notifyOnNotificationBar = true;
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index b206a1f..9aea0e7 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -89,10 +89,12 @@
interface ProgressListener {
void onProgressUpdate(Progress progress);
+
void onResult(int resultCode, Throwable detail);
}
private final String mUrl;
+ private final String mDsuSlot;
private final long mSystemSize;
private final long mUserdataSize;
private final Context mContext;
@@ -106,9 +108,16 @@
private InputStream mStream;
private ZipFile mZipFile;
- InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context,
- DynamicSystemManager dynSystem, ProgressListener listener) {
+ InstallationAsyncTask(
+ String url,
+ String dsuSlot,
+ long systemSize,
+ long userdataSize,
+ Context context,
+ DynamicSystemManager dynSystem,
+ ProgressListener listener) {
mUrl = url;
+ mDsuSlot = dsuSlot;
mSystemSize = systemSize;
mUserdataSize = userdataSize;
mContext = context;
@@ -126,14 +135,17 @@
verifyAndPrepare();
- mDynSystem.startInstallation();
+ mDynSystem.startInstallation(mDsuSlot);
installUserdata();
if (isCancelled()) {
mDynSystem.remove();
return null;
}
-
+ if (mUrl == null) {
+ mDynSystem.finishInstallation();
+ return null;
+ }
installImages();
if (isCancelled()) {
mDynSystem.remove();
@@ -194,6 +206,9 @@
}
private void verifyAndPrepare() throws Exception {
+ if (mUrl == null) {
+ return;
+ }
String extension = mUrl.substring(mUrl.lastIndexOf('.') + 1);
if ("gz".equals(extension) || "gzip".equals(extension)) {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
index 3b3933b..e42ded7 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
@@ -28,11 +28,9 @@
import android.util.FeatureFlagUtils;
import android.util.Log;
-
/**
- * This Activity starts KeyguardManager and ask the user to confirm
- * before any installation request. If the device is not protected by
- * a password, it approves the request by default.
+ * This Activity starts KeyguardManager and ask the user to confirm before any installation request.
+ * If the device is not protected by a password, it approves the request by default.
*/
public class VerificationActivity extends Activity {
@@ -88,11 +86,15 @@
Uri url = callingIntent.getData();
Bundle extras = callingIntent.getExtras();
- sVerifiedUrl = url.toString();
+ if (url != null) {
+ sVerifiedUrl = url.toString();
+ }
// start service
Intent intent = new Intent(this, DynamicSystemInstallationService.class);
- intent.setData(url);
+ if (url != null) {
+ intent.setData(url);
+ }
intent.setAction(DynamicSystemClient.ACTION_START_INSTALL);
intent.putExtras(extras);
@@ -106,6 +108,7 @@
}
static boolean isVerified(String url) {
+ if (url == null) return true;
return sVerifiedUrl != null && sVerifiedUrl.equals(url);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index 440315f..2d7d59c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -118,7 +118,7 @@
notifyChanged();
}
- setSummary(mWifiEntry.getSummary());
+ setSummary(mWifiEntry.getSummary(false /* concise */));
mContentDescription = buildContentDescription();
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
index 752a549..0f1e0ff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
@@ -67,7 +67,7 @@
MockitoAnnotations.initMocks(this);
when(mMockWifiEntry.getTitle()).thenReturn(MOCK_TITLE);
- when(mMockWifiEntry.getSummary()).thenReturn(MOCK_SUMMARY);
+ when(mMockWifiEntry.getSummary(false /* concise */)).thenReturn(MOCK_SUMMARY);
when(mMockIconInjector.getIcon(0)).thenReturn(mMockDrawable0);
when(mMockIconInjector.getIcon(1)).thenReturn(mMockDrawable1);
@@ -112,7 +112,7 @@
final WifiEntryPreference pref =
new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
final String updatedSummary = "updated summary";
- when(mMockWifiEntry.getSummary()).thenReturn(updatedSummary);
+ when(mMockWifiEntry.getSummary(false /* concise */)).thenReturn(updatedSummary);
pref.refresh();
diff --git a/packages/SettingsProvider/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 4443524..c913999 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -367,7 +367,9 @@
String value = getSettingValue(args);
String tag = getSettingTag(args);
final boolean makeDefault = getSettingMakeDefault(args);
- insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false);
+ final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
+ insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false,
+ overrideableByRestore);
break;
}
@@ -375,13 +377,16 @@
String value = getSettingValue(args);
String tag = getSettingTag(args);
final boolean makeDefault = getSettingMakeDefault(args);
- insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false);
+ final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
+ insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false,
+ overrideableByRestore);
break;
}
case Settings.CALL_METHOD_PUT_SYSTEM: {
String value = getSettingValue(args);
- insertSystemSetting(name, value, requestingUserId);
+ boolean overrideableByRestore = getSettingOverrideableByRestore(args);
+ insertSystemSetting(name, value, requestingUserId, overrideableByRestore);
break;
}
@@ -576,20 +581,23 @@
switch (table) {
case TABLE_GLOBAL: {
if (insertGlobalSetting(name, value, null, false,
- UserHandle.getCallingUserId(), false)) {
+ UserHandle.getCallingUserId(), false,
+ /* overrideableByRestore */ false)) {
return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name);
}
} break;
case TABLE_SECURE: {
if (insertSecureSetting(name, value, null, false,
- UserHandle.getCallingUserId(), false)) {
+ UserHandle.getCallingUserId(), false,
+ /* overrideableByRestore */ false)) {
return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
}
} break;
case TABLE_SYSTEM: {
- if (insertSystemSetting(name, value, UserHandle.getCallingUserId())) {
+ if (insertSystemSetting(name, value, UserHandle.getCallingUserId(),
+ /* overridableByRestore */ false)) {
return Uri.withAppendedPath(Settings.System.CONTENT_URI, name);
}
} break;
@@ -1075,7 +1083,8 @@
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG,
UserHandle.USER_SYSTEM, name, value, null, makeDefault, true,
- resolveCallingPackage(), false, null);
+ resolveCallingPackage(), false, null,
+ /* overrideableByRestore */ false);
}
case MUTATION_OPERATION_DELETE: {
@@ -1179,14 +1188,15 @@
}
private boolean insertGlobalSetting(String name, String value, String tag,
- boolean makeDefault, int requestingUserId, boolean forceNotify) {
+ boolean makeDefault, int requestingUserId, boolean forceNotify,
+ boolean overrideableByRestore) {
if (DEBUG) {
Slog.v(LOG_TAG, "insertGlobalSetting(" + name + ", " + value + ", "
+ ", " + tag + ", " + makeDefault + ", " + requestingUserId
+ ", " + forceNotify + ")");
}
return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId,
- MUTATION_OPERATION_INSERT, forceNotify, 0);
+ MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore);
}
private boolean deleteGlobalSetting(String name, int requestingUserId, boolean forceNotify) {
@@ -1221,6 +1231,15 @@
private boolean mutateGlobalSetting(String name, String value, String tag,
boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
int mode) {
+ // overrideableByRestore = false as by default settings values shouldn't be overrideable by
+ // restore.
+ return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId, operation,
+ forceNotify, mode, /* overrideableByRestore */ false);
+ }
+
+ private boolean mutateGlobalSetting(String name, String value, String tag,
+ boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
+ int mode, boolean overrideableByRestore) {
// Make sure the caller can change the settings - treated as secure.
enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
@@ -1239,7 +1258,8 @@
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
- getCallingPackage(), forceNotify, CRITICAL_GLOBAL_SETTINGS);
+ getCallingPackage(), forceNotify,
+ CRITICAL_GLOBAL_SETTINGS, overrideableByRestore);
}
case MUTATION_OPERATION_DELETE: {
@@ -1475,7 +1495,7 @@
) {
@Override
public boolean update(String value, boolean setDefault, String packageName,
- String tag, boolean forceNonSystemPackage) {
+ String tag, boolean forceNonSystemPackage, boolean overrideableByRestore) {
Slog.wtf(LOG_TAG, "update shouldn't be called on this instance.");
return false;
}
@@ -1484,14 +1504,15 @@
}
private boolean insertSecureSetting(String name, String value, String tag,
- boolean makeDefault, int requestingUserId, boolean forceNotify) {
+ boolean makeDefault, int requestingUserId, boolean forceNotify,
+ boolean overrideableByRestore) {
if (DEBUG) {
Slog.v(LOG_TAG, "insertSecureSetting(" + name + ", " + value + ", "
+ ", " + tag + ", " + makeDefault + ", " + requestingUserId
+ ", " + forceNotify + ")");
}
return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId,
- MUTATION_OPERATION_INSERT, forceNotify, 0);
+ MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore);
}
private boolean deleteSecureSetting(String name, int requestingUserId, boolean forceNotify) {
@@ -1529,6 +1550,15 @@
private boolean mutateSecureSetting(String name, String value, String tag,
boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
int mode) {
+ // overrideableByRestore = false as by default settings values shouldn't be overrideable by
+ // restore.
+ return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId, operation,
+ forceNotify, mode, /* overrideableByRestore */ false);
+ }
+
+ private boolean mutateSecureSetting(String name, String value, String tag,
+ boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
+ int mode, boolean overrideableByRestore) {
// Make sure the caller can change the settings.
enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
@@ -1561,7 +1591,8 @@
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, name, value, tag, makeDefault,
- getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
+ getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS,
+ overrideableByRestore);
}
case MUTATION_OPERATION_DELETE: {
@@ -1637,13 +1668,15 @@
}
}
- private boolean insertSystemSetting(String name, String value, int requestingUserId) {
+ private boolean insertSystemSetting(String name, String value, int requestingUserId,
+ boolean overrideableByRestore) {
if (DEBUG) {
Slog.v(LOG_TAG, "insertSystemSetting(" + name + ", " + value + ", "
+ requestingUserId + ")");
}
- return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT);
+ return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT,
+ overrideableByRestore);
}
private boolean deleteSystemSetting(String name, int requestingUserId) {
@@ -1663,8 +1696,15 @@
return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
}
- private boolean mutateSystemSetting(String name, String value, int runAsUserId,
- int operation) {
+ private boolean mutateSystemSetting(String name, String value, int runAsUserId, int operation) {
+ // overrideableByRestore = false as by default settings values shouldn't be overrideable by
+ // restore.
+ return mutateSystemSetting(name, value, runAsUserId, operation,
+ /* overrideableByRestore */ false);
+ }
+
+ private boolean mutateSystemSetting(String name, String value, int runAsUserId, int operation,
+ boolean overrideableByRestore) {
if (!hasWriteSecureSettingsPermission()) {
// If the caller doesn't hold WRITE_SECURE_SETTINGS, we verify whether this
// operation is allowed for the calling package through appops.
@@ -1714,7 +1754,7 @@
validateSystemSettingValue(name, value);
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM,
owningUserId, name, value, null, false, getCallingPackage(),
- false, null);
+ false, null, overrideableByRestore);
}
case MUTATION_OPERATION_DELETE: {
@@ -2051,7 +2091,8 @@
}
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, newProviders, tag,
- makeDefault, getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
+ makeDefault, getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS,
+ /* overrideableByRestore */ false);
}
private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
@@ -2145,6 +2186,10 @@
return (args != null) && args.getBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY);
}
+ private static boolean getSettingOverrideableByRestore(Bundle args) {
+ return (args != null) && args.getBoolean(Settings.CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY);
+ }
+
private static int getResetModeEnforcingPermission(Bundle args) {
final int mode = (args != null) ? args.getInt(Settings.CALL_METHOD_RESET_MODE_KEY) : 0;
switch (mode) {
@@ -2642,21 +2687,21 @@
public boolean insertSettingLocked(int type, int userId, String name, String value,
String tag, boolean makeDefault, String packageName, boolean forceNotify,
- Set<String> criticalSettings) {
+ Set<String> criticalSettings, boolean overrideableByRestore) {
return insertSettingLocked(type, userId, name, value, tag, makeDefault, false,
- packageName, forceNotify, criticalSettings);
+ packageName, forceNotify, criticalSettings, overrideableByRestore);
}
public boolean insertSettingLocked(int type, int userId, String name, String value,
String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName,
- boolean forceNotify, Set<String> criticalSettings) {
+ boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) {
final int key = makeKey(type, userId);
boolean success = false;
SettingsState settingsState = peekSettingsStateLocked(key);
if (settingsState != null) {
success = settingsState.insertSettingLocked(name, value,
- tag, makeDefault, forceNonSystemPackage, packageName);
+ tag, makeDefault, forceNonSystemPackage, packageName, overrideableByRestore);
}
if (success && criticalSettings != null && criticalSettings.contains(name)) {
@@ -3391,6 +3436,10 @@
* for this user from the old to the new version. When you add a new
* upgrade step you *must* update SETTINGS_VERSION.
*
+ * All settings modifications should be made through
+ * {@link SettingsState#insertSettingOverrideableByRestoreLocked(String, String, String,
+ * boolean, String)} so that restore can override those values if needed.
+ *
* This is an example of moving a setting from secure to global.
*
* // v119: Example settings changes.
@@ -3436,7 +3485,8 @@
// v120: Add double tap to wake setting.
if (currentVersion == 119) {
SettingsState secureSettings = getSecureSettingsLocked(userId);
- secureSettings.insertSettingLocked(Settings.Secure.DOUBLE_TAP_TO_WAKE,
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.DOUBLE_TAP_TO_WAKE,
getContext().getResources().getBoolean(
R.bool.def_double_tap_to_wake) ? "1" : "0", null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3461,7 +3511,7 @@
Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
if (defaultComponent != null && !defaultComponent.isEmpty() &&
currentSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
defaultComponent, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -3476,7 +3526,7 @@
Setting currentSetting = globalSettings.getSettingLocked(
Settings.Global.ADD_USERS_WHEN_LOCKED);
if (currentSetting.isNull()) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.ADD_USERS_WHEN_LOCKED,
getContext().getResources().getBoolean(
R.bool.def_add_users_from_lockscreen) ? "1" : "0",
@@ -3490,8 +3540,9 @@
final SettingsState globalSettings = getGlobalSettingsLocked();
String defaultDisabledProfiles = (getContext().getResources().getString(
R.string.def_bluetooth_disabled_profiles));
- globalSettings.insertSettingLocked(Settings.Global.BLUETOOTH_DISABLED_PROFILES,
- defaultDisabledProfiles, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ globalSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Global.BLUETOOTH_DISABLED_PROFILES, defaultDisabledProfiles,
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
currentVersion = 124;
}
@@ -3502,7 +3553,7 @@
Setting currentSetting = secureSettings.getSettingLocked(
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
if (currentSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
getContext().getResources().getBoolean(
R.bool.def_show_ime_with_hard_keyboard) ? "1" : "0",
@@ -3531,7 +3582,7 @@
b.append(c.flattenToString());
start = false;
}
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.ENABLED_VR_LISTENERS, b.toString(),
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -3551,7 +3602,7 @@
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
if (!showNotifications.isNull()) {
final SettingsState secureSettings = getSecureSettingsLocked(userId);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
showNotifications.getValue(), null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3561,7 +3612,7 @@
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
if (!allowPrivate.isNull()) {
final SettingsState secureSettings = getSecureSettingsLocked(userId);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
allowPrivate.getValue(), null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3588,7 +3639,7 @@
final String oldValue = systemSecureSettings.getSettingLocked(
Settings.Secure.LONG_PRESS_TIMEOUT).getValue();
if (TextUtils.equals("500", oldValue)) {
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.LONG_PRESS_TIMEOUT,
String.valueOf(getContext().getResources().getInteger(
R.integer.def_long_press_timeout_millis)),
@@ -3604,10 +3655,12 @@
getSettingLocked(Settings.Secure.DOZE_ENABLED).getValue());
if (dozeExplicitlyDisabled) {
- secureSettings.insertSettingLocked(Settings.Secure.DOZE_PICK_UP_GESTURE,
- "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- secureSettings.insertSettingLocked(Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
- "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.DOZE_PICK_UP_GESTURE, "0", null, true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, "0", null, true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 131;
}
@@ -3618,7 +3671,7 @@
final String oldValue = systemSecureSettings.getSettingLocked(
Settings.Secure.MULTI_PRESS_TIMEOUT).getValue();
if (TextUtils.equals(null, oldValue)) {
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.MULTI_PRESS_TIMEOUT,
String.valueOf(getContext().getResources().getInteger(
R.integer.def_multi_press_timeout_millis)),
@@ -3633,7 +3686,7 @@
final SettingsState systemSecureSettings = getSecureSettingsLocked(userId);
String defaultSyncParentSounds = (getContext().getResources()
.getBoolean(R.bool.def_sync_parent_sounds) ? "1" : "0");
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.SYNC_PARENT_SOUNDS, defaultSyncParentSounds,
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
currentVersion = 133;
@@ -3646,9 +3699,9 @@
.isNull()) {
String defaultEndButtonBehavior = Integer.toString(getContext()
.getResources().getInteger(R.integer.def_end_button_behavior));
- systemSettings.insertSettingLocked(Settings.System.END_BUTTON_BEHAVIOR,
- defaultEndButtonBehavior, null, true,
- SettingsState.SYSTEM_PACKAGE_NAME);
+ systemSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.System.END_BUTTON_BEHAVIOR, defaultEndButtonBehavior, null,
+ true, SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 134;
}
@@ -3706,8 +3759,8 @@
if (ssaid.isNull() || ssaid.getValue() == null) {
// Android Id doesn't exist for this package so create it.
- ssaidSettings.insertSettingLocked(uid, legacySsaid, null, true,
- info.packageName);
+ ssaidSettings.insertSettingOverrideableByRestoreLocked(uid,
+ legacySsaid, null, true, info.packageName);
if (DEBUG) {
Slog.d(LOG_TAG, "Keep the legacy ssaid for uid=" + uid);
}
@@ -3727,13 +3780,14 @@
&& secureSetting.getSettingLocked(
Settings.Secure.INSTALL_NON_MARKET_APPS).getValue().equals("0")) {
- secureSetting.insertSettingLocked(Settings.Secure.INSTALL_NON_MARKET_APPS,
- "1", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSetting.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.INSTALL_NON_MARKET_APPS, "1", null, true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
// For managed profiles with profile owners, DevicePolicyManagerService
// may want to set the user restriction in this case
- secureSetting.insertSettingLocked(
- Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, "1", null, true,
- SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSetting.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, "1", null,
+ true, SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 138;
}
@@ -3774,7 +3828,7 @@
Setting currentSetting = globalSettings.getSettingLocked(
Settings.Global.WIFI_WAKEUP_ENABLED);
if (currentSetting.isNull()) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.WIFI_WAKEUP_ENABLED,
getContext().getResources().getBoolean(
R.bool.def_wifi_wakeup_enabled) ? "1" : "0",
@@ -3796,8 +3850,9 @@
if (defaultValue != null) {
Slog.d(LOG_TAG, "Setting [" + defaultValue + "] as Autofill Service "
+ "for user " + userId);
- secureSettings.insertSettingLocked(Settings.Secure.AUTOFILL_SERVICE,
- defaultValue, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.AUTOFILL_SERVICE, defaultValue, null, true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
}
}
@@ -3848,7 +3903,7 @@
final Setting currentSetting = globalSettings.getSettingLocked(
Global.DEFAULT_RESTRICT_BACKGROUND_DATA);
if (currentSetting.isNull()) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.DEFAULT_RESTRICT_BACKGROUND_DATA,
getContext().getResources().getBoolean(
R.bool.def_restrict_background_data) ? "1" : "0",
@@ -3867,7 +3922,7 @@
final String defaultValue = getContext().getResources().getString(
R.string.def_backup_manager_constants);
if (!TextUtils.isEmpty(defaultValue)) {
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.BACKUP_MANAGER_CONSTANTS, defaultValue, null,
true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -3881,7 +3936,7 @@
final Setting currentSetting = globalSettings.getSettingLocked(
Settings.Global.MOBILE_DATA_ALWAYS_ON);
if (currentSetting.isNull()) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.MOBILE_DATA_ALWAYS_ON,
getContext().getResources().getBoolean(
R.bool.def_mobile_data_always_on) ? "1" : "0",
@@ -3917,7 +3972,7 @@
if (showNotificationBadges.isNull()) {
final boolean defaultValue = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_notificationBadging);
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Secure.NOTIFICATION_BADGING,
defaultValue ? "1" : "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3934,7 +3989,7 @@
final String defaultValue = getContext().getResources().getString(
R.string.def_backup_local_transport_parameters);
if (!TextUtils.isEmpty(defaultValue)) {
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS, defaultValue,
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -3957,7 +4012,7 @@
if (currentSetting.isNull()) {
String defaultZenDuration = Integer.toString(getContext()
.getResources().getInteger(R.integer.def_zen_duration));
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.ZEN_DURATION, defaultZenDuration,
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -3973,7 +4028,7 @@
final String defaultValue = getContext().getResources().getString(
R.string.def_backup_agent_timeout_parameters);
if (!TextUtils.isEmpty(defaultValue)) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS, defaultValue,
null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4002,7 +4057,7 @@
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
// The default value is "1", check if user has turned it off.
if ("0".equals(showNotifications.getValue())) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, "0",
null /* tag */, false /* makeDefault */,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4023,7 +4078,7 @@
String oldValue = globalSettings.getSettingLocked(
Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY).getValue();
if (TextUtils.equals(null, oldValue)) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
Integer.toString(getContext().getResources().getInteger(
R.integer.def_max_sound_trigger_detection_service_ops_per_day)),
@@ -4033,7 +4088,7 @@
oldValue = globalSettings.getSettingLocked(
Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT).getValue();
if (TextUtils.equals(null, oldValue)) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
Integer.toString(getContext().getResources().getInteger(
R.integer.def_sound_trigger_detection_service_op_timeout)),
@@ -4048,7 +4103,7 @@
final Setting currentSetting = secureSettings.getSettingLocked(
Secure.VOLUME_HUSH_GESTURE);
if (currentSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.VOLUME_HUSH_GESTURE,
Integer.toString(Secure.VOLUME_HUSH_VIBRATE),
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4069,7 +4124,7 @@
final Setting currentSetting = settings.getSettingLocked(
Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY);
if (currentSetting.isDefaultFromSystem()) {
- settings.insertSettingLocked(
+ settings.insertSettingOverrideableByRestoreLocked(
Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
Integer.toString(getContext().getResources().getInteger(
R.integer
@@ -4098,7 +4153,7 @@
Setting currentHushUsedSetting = secureSettings.getSettingLocked(
Secure.HUSH_GESTURE_USED);
if (currentHushUsedSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.HUSH_GESTURE_USED, "0", null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4106,7 +4161,7 @@
Setting currentRingerToggleCountSetting = secureSettings.getSettingLocked(
Secure.MANUAL_RINGER_TOGGLE_COUNT);
if (currentRingerToggleCountSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, "0", null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4125,7 +4180,7 @@
final Setting currentSetting = systemSettings.getSettingLocked(
Settings.System.VIBRATE_WHEN_RINGING);
if (currentSetting.isNull()) {
- systemSettings.insertSettingLocked(
+ systemSettings.insertSettingOverrideableByRestoreLocked(
Settings.System.VIBRATE_WHEN_RINGING,
getContext().getResources().getBoolean(
R.bool.def_vibrate_when_ringing) ? "1" : "0",
@@ -4149,18 +4204,18 @@
// ZEN_DURATION
if (!globalZenDuration.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.ZEN_DURATION, globalZenDuration.getValue(), null, false,
SettingsState.SYSTEM_PACKAGE_NAME);
// set global zen duration setting to null since it's deprecated
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.ZEN_DURATION, null, null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
} else if (secureZenDuration.isNull()) {
String defaultZenDuration = Integer.toString(getContext()
.getResources().getInteger(R.integer.def_zen_duration));
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.ZEN_DURATION, defaultZenDuration, null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4169,7 +4224,7 @@
final Setting currentShowZenSettingSuggestion = secureSettings.getSettingLocked(
Secure.SHOW_ZEN_SETTINGS_SUGGESTION);
if (currentShowZenSettingSuggestion.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.SHOW_ZEN_SETTINGS_SUGGESTION, "1",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4178,7 +4233,7 @@
final Setting currentUpdatedSetting = secureSettings.getSettingLocked(
Secure.ZEN_SETTINGS_UPDATED);
if (currentUpdatedSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.ZEN_SETTINGS_UPDATED, "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4187,7 +4242,7 @@
final Setting currentSettingSuggestionViewed = secureSettings.getSettingLocked(
Secure.ZEN_SETTINGS_SUGGESTION_VIEWED);
if (currentSettingSuggestionViewed.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4210,20 +4265,20 @@
if (!globalChargingSoundEnabled.isNull()) {
if (secureChargingSoundsEnabled.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.CHARGING_SOUNDS_ENABLED,
globalChargingSoundEnabled.getValue(), null, false,
SettingsState.SYSTEM_PACKAGE_NAME);
}
// set global charging_sounds_enabled setting to null since it's deprecated
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.CHARGING_SOUNDS_ENABLED, null, null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
} else if (secureChargingSoundsEnabled.isNull()) {
String defChargingSoundsEnabled = getContext().getResources()
.getBoolean(R.bool.def_charging_sounds_enabled) ? "1" : "0";
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.CHARGING_SOUNDS_ENABLED, defChargingSoundsEnabled, null,
true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4235,7 +4290,7 @@
if (secureChargingVibrationEnabled.isNull()) {
String defChargingVibrationEnabled = getContext().getResources()
.getBoolean(R.bool.def_charging_vibration_enabled) ? "1" : "0";
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.CHARGING_VIBRATION_ENABLED, defChargingVibrationEnabled,
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4255,7 +4310,7 @@
currentSetting.getValue());
if ((currentSettingIntegerValue
& (1 << AudioManager.STREAM_VOICE_CALL)) == 0) {
- systemSettings.insertSettingLocked(
+ systemSettings.insertSettingOverrideableByRestoreLocked(
Settings.System.MUTE_STREAMS_AFFECTED,
Integer.toString(
currentSettingIntegerValue
@@ -4296,7 +4351,7 @@
? Secure.LOCATION_MODE_ON
: Secure.LOCATION_MODE_OFF;
}
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.LOCATION_MODE, Integer.toString(defLocationMode),
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4318,7 +4373,7 @@
Setting currentRampingRingerSetting = globalSettings.getSettingLocked(
Settings.Global.APPLY_RAMPING_RINGER);
if (currentRampingRingerSetting.isNull()) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.APPLY_RAMPING_RINGER,
getContext().getResources().getBoolean(
R.bool.def_apply_ramping_ringer) ? "1" : "0", null,
@@ -4344,7 +4399,7 @@
if (!notificationVibrationIntensity.isNull()
&& ringVibrationIntensity.isNull()) {
- systemSettings.insertSettingLocked(
+ systemSettings.insertSettingOverrideableByRestoreLocked(
Settings.System.RING_VIBRATION_INTENSITY,
notificationVibrationIntensity.getValue(),
null , true, SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4388,7 +4443,7 @@
if (awareEnabled.isNull()) {
final boolean defAwareEnabled = getContext().getResources().getBoolean(
R.bool.def_aware_enabled);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.AWARE_ENABLED, defAwareEnabled ? "1" : "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4408,7 +4463,7 @@
if (skipGesture.isNull()) {
final boolean defSkipGesture = getContext().getResources().getBoolean(
R.bool.def_skip_gesture);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.SKIP_GESTURE, defSkipGesture ? "1" : "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4419,7 +4474,7 @@
if (silenceGesture.isNull()) {
final boolean defSilenceGesture = getContext().getResources().getBoolean(
R.bool.def_silence_gesture);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.SILENCE_GESTURE, defSilenceGesture ? "1" : "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4447,7 +4502,7 @@
if (awareLockEnabled.isNull()) {
final boolean defAwareLockEnabled = getContext().getResources().getBoolean(
R.bool.def_aware_lock_enabled);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.AWARE_LOCK_ENABLED, defAwareLockEnabled ? "1" : "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4467,7 +4522,7 @@
currentSetting.getValue());
if ((currentSettingIntegerValue
& (1 << AudioManager.STREAM_BLUETOOTH_SCO)) == 0) {
- systemSettings.insertSettingLocked(
+ systemSettings.insertSettingOverrideableByRestoreLocked(
Settings.System.MUTE_STREAMS_AFFECTED,
Integer.toString(
currentSettingIntegerValue
@@ -4513,13 +4568,13 @@
if (oldValueWireless == null
|| TextUtils.equals(oldValueWireless, defaultValueWired)) {
if (!TextUtils.isEmpty(defaultValueWireless)) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWireless,
null /* tag */, true /* makeDefault */,
SettingsState.SYSTEM_PACKAGE_NAME);
} else if (!TextUtils.isEmpty(defaultValueWired)) {
// if the wireless sound is empty, use the wired charging sound
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWired,
null /* tag */, true /* makeDefault */,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4528,7 +4583,7 @@
// wired charging sound
if (oldValueWired == null && !TextUtils.isEmpty(defaultValueWired)) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.CHARGING_STARTED_SOUND, defaultValueWired,
null /* tag */, true /* makeDefault */,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4540,8 +4595,8 @@
// Version 184: Reset the default for Global Settings: NOTIFICATION_BUBBLES
// This is originally set in version 182, however, the default value changed
// so this step is to ensure the value is updated to the correct default.
- getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES,
- getContext().getResources().getBoolean(
+ getGlobalSettingsLocked().insertSettingOverrideableByRestoreLocked(
+ Global.NOTIFICATION_BUBBLES, getContext().getResources().getBoolean(
R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */,
true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4617,7 +4672,7 @@
final boolean systemSet = SettingsState.isSystemPackage(getContext(),
setting.getPackageName(), callingUid, userId);
if (systemSet) {
- settings.insertSettingLocked(name, setting.getValue(),
+ settings.insertSettingOverrideableByRestoreLocked(name, setting.getValue(),
setting.getTag(), true, setting.getPackageName());
} else if (setting.getDefaultValue() != null && setting.isDefaultFromSystem()) {
// We had a bug where changes by non-system packages were marked
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 5b1b530..db18213 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -117,6 +117,8 @@
private static final String ATTR_NAMESPACE = "namespace";
private static final String ATTR_BANNED_HASH = "bannedHash";
+ private static final String ATTR_PRESERVE_IN_RESTORE = "preserve_in_restore";
+
/**
* Non-binary value will be written in this attributes.
*/
@@ -388,15 +390,25 @@
// The settings provider must hold its lock when calling here.
@GuardedBy("mLock")
- public boolean insertSettingLocked(String name, String value, String tag,
+ public boolean insertSettingOverrideableByRestoreLocked(String name, String value, String tag,
boolean makeDefault, String packageName) {
- return insertSettingLocked(name, value, tag, makeDefault, false, packageName);
+ return insertSettingLocked(name, value, tag, makeDefault, false, packageName,
+ /* overrideableByRestore */ true);
}
// The settings provider must hold its lock when calling here.
@GuardedBy("mLock")
public boolean insertSettingLocked(String name, String value, String tag,
- boolean makeDefault, boolean forceNonSystemPackage, String packageName) {
+ boolean makeDefault, String packageName) {
+ return insertSettingLocked(name, value, tag, makeDefault, false, packageName,
+ /* overrideableByRestore */ false);
+ }
+
+ // The settings provider must hold its lock when calling here.
+ @GuardedBy("mLock")
+ public boolean insertSettingLocked(String name, String value, String tag,
+ boolean makeDefault, boolean forceNonSystemPackage, String packageName,
+ boolean overrideableByRestore) {
if (TextUtils.isEmpty(name)) {
return false;
}
@@ -407,7 +419,8 @@
Setting newState;
if (oldState != null) {
- if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage)) {
+ if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage,
+ overrideableByRestore)) {
return false;
}
newState = oldState;
@@ -495,7 +508,8 @@
changedKeys.add(key); // key was added
} else if (state.value != value) {
oldValue = state.value;
- state.update(value, false, packageName, null, true);
+ state.update(value, false, packageName, null, true,
+ /* overrideableByRestore */ false);
changedKeys.add(key); // key was updated
} else {
// this key/value already exists, no change and no logging necessary
@@ -797,7 +811,8 @@
writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),
- setting.getTag(), setting.isDefaultFromSystem());
+ setting.getTag(), setting.isDefaultFromSystem(),
+ setting.isValuePreservedInRestore());
if (DEBUG_PERSISTENCE) {
Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "="
@@ -886,7 +901,8 @@
static void writeSingleSetting(int version, XmlSerializer serializer, String id,
String name, String value, String defaultValue, String packageName,
- String tag, boolean defaultSysSet) throws IOException {
+ String tag, boolean defaultSysSet, boolean isValuePreservedInRestore)
+ throws IOException {
if (id == null || isBinary(id) || name == null || isBinary(name)
|| packageName == null || isBinary(packageName)) {
// This shouldn't happen.
@@ -905,6 +921,9 @@
setValueAttribute(ATTR_TAG, ATTR_TAG_BASE64,
version, serializer, tag);
}
+ if (isValuePreservedInRestore) {
+ serializer.attribute(null, ATTR_PRESERVE_IN_RESTORE, Boolean.toString(true));
+ }
serializer.endTag(null, TAG_SETTING);
}
@@ -1041,6 +1060,10 @@
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
String defaultValue = getValueAttribute(parser, ATTR_DEFAULT_VALUE,
ATTR_DEFAULT_VALUE_BASE64);
+ String isPreservedInRestoreString = parser.getAttributeValue(null,
+ ATTR_PRESERVE_IN_RESTORE);
+ boolean isPreservedInRestore = isPreservedInRestoreString != null
+ && Boolean.parseBoolean(isPreservedInRestoreString);
String tag = null;
boolean fromSystem = false;
if (defaultValue != null) {
@@ -1049,7 +1072,7 @@
tag = getValueAttribute(parser, ATTR_TAG, ATTR_TAG_BASE64);
}
mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
- fromSystem, id));
+ fromSystem, id, isPreservedInRestore));
if (DEBUG_PERSISTENCE) {
Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value);
@@ -1133,6 +1156,8 @@
private String tag;
// Whether the default is set by the system
private boolean defaultFromSystem;
+ // Whether the value of this setting will be preserved when restore happens.
+ private boolean isValuePreservedInRestore;
public Setting(Setting other) {
name = other.name;
@@ -1142,25 +1167,38 @@
id = other.id;
defaultFromSystem = other.defaultFromSystem;
tag = other.tag;
+ isValuePreservedInRestore = other.isValuePreservedInRestore;
}
public Setting(String name, String value, boolean makeDefault, String packageName,
String tag) {
this.name = name;
- update(value, makeDefault, packageName, tag, false);
+ // overrideableByRestore = true as the first initialization isn't considered a
+ // modification.
+ update(value, makeDefault, packageName, tag, false,
+ /* overrideableByRestore */ true);
}
public Setting(String name, String value, String defaultValue,
String packageName, String tag, boolean fromSystem, String id) {
+ this(name, value, defaultValue, packageName, tag, fromSystem, id,
+ /* isOverrideableByRestore */ false);
+ }
+
+ Setting(String name, String value, String defaultValue,
+ String packageName, String tag, boolean fromSystem, String id,
+ boolean isValuePreservedInRestore) {
mNextId = Math.max(mNextId, Long.parseLong(id) + 1);
if (NULL_VALUE.equals(value)) {
value = null;
}
- init(name, value, tag, defaultValue, packageName, fromSystem, id);
+ init(name, value, tag, defaultValue, packageName, fromSystem, id,
+ isValuePreservedInRestore);
}
private void init(String name, String value, String tag, String defaultValue,
- String packageName, boolean fromSystem, String id) {
+ String packageName, boolean fromSystem, String id,
+ boolean isValuePreservedInRestore) {
this.name = name;
this.value = value;
this.tag = tag;
@@ -1168,6 +1206,7 @@
this.packageName = packageName;
this.id = id;
this.defaultFromSystem = fromSystem;
+ this.isValuePreservedInRestore = isValuePreservedInRestore;
}
public String getName() {
@@ -1198,6 +1237,10 @@
return defaultFromSystem;
}
+ public boolean isValuePreservedInRestore() {
+ return isValuePreservedInRestore;
+ }
+
public String getId() {
return id;
}
@@ -1208,7 +1251,9 @@
/** @return whether the value changed */
public boolean reset() {
- return update(this.defaultValue, false, packageName, null, true);
+ // overrideableByRestore = true as resetting to default value isn't considered a
+ // modification.
+ return update(this.defaultValue, false, packageName, null, true, true);
}
public boolean isTransient() {
@@ -1220,7 +1265,7 @@
}
public boolean update(String value, boolean setDefault, String packageName, String tag,
- boolean forceNonSystemPackage) {
+ boolean forceNonSystemPackage, boolean overrideableByRestore) {
if (NULL_VALUE.equals(value)) {
value = null;
}
@@ -1253,17 +1298,22 @@
}
}
+ // isValuePreservedInRestore shouldn't change back to false if it has been set to true.
+ boolean isPreserved = this.isValuePreservedInRestore || !overrideableByRestore;
+
// Is something gonna change?
if (Objects.equals(value, this.value)
&& Objects.equals(defaultValue, this.defaultValue)
&& Objects.equals(packageName, this.packageName)
&& Objects.equals(tag, this.tag)
- && defaultFromSystem == this.defaultFromSystem) {
+ && defaultFromSystem == this.defaultFromSystem
+ && isPreserved == this.isValuePreservedInRestore) {
return false;
}
init(name, value, tag, defaultValue, packageName, defaultFromSystem,
- String.valueOf(mNextId++));
+ String.valueOf(mNextId++), isPreserved);
+
return true;
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 1a1a480..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,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 3f68554..b855d87 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -46,6 +46,18 @@
"\uD800ab\uDC00 " + // broken surrogate pairs
"日本語";
+ private static final String TEST_PACKAGE = "package";
+ private static final String SETTING_NAME = "test_setting";
+
+ private final Object mLock = new Object();
+
+ private File mSettingsFile;
+
+ @Override
+ protected void setUp() {
+ mSettingsFile = new File(getContext().getCacheDir(), "setting.xml");
+ mSettingsFile.delete();
+ }
public void testIsBinary() {
assertFalse(SettingsState.isBinary(" abc 日本語"));
@@ -99,10 +111,10 @@
checkWriteSingleSetting(serializer, CRAZY_STRING, null);
SettingsState.writeSingleSetting(
SettingsState.SETTINGS_VERSION_NEW_ENCODING,
- serializer, null, "k", "v", null, "package", null, false);
+ serializer, null, "k", "v", null, "package", null, false, false);
SettingsState.writeSingleSetting(
SettingsState.SETTINGS_VERSION_NEW_ENCODING,
- serializer, "1", "k", "v", null, null, null, false);
+ serializer, "1", "k", "v", null, null, null, false, false);
}
private void checkWriteSingleSetting(XmlSerializer serializer, String key, String value)
@@ -115,7 +127,7 @@
// Make sure the XML serializer won't crash.
SettingsState.writeSingleSetting(
SettingsState.SETTINGS_VERSION_NEW_ENCODING,
- serializer, "1", key, value, null, "package", null, false);
+ serializer, "1", key, value, null, "package", null, false, false);
}
/**
@@ -182,4 +194,57 @@
assertEquals("p2", s.getPackageName());
}
}
+
+ public void testInitializeSetting_preserveFlagNotSet() {
+ SettingsState settingsWriter = getSettingStateObject();
+ settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+ settingsWriter.persistSyncLocked();
+
+ SettingsState settingsReader = getSettingStateObject();
+ assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+ }
+
+ public void testModifySetting_preserveFlagSet() {
+ SettingsState settingsWriter = getSettingStateObject();
+ settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+ settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, TEST_PACKAGE);
+ settingsWriter.persistSyncLocked();
+
+ SettingsState settingsReader = getSettingStateObject();
+ assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+ }
+
+ public void testModifySettingOverrideableByRestore_preserveFlagNotSet() {
+ SettingsState settingsWriter = getSettingStateObject();
+ settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+ settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
+ /* overrideableByRestore */ true);
+ settingsWriter.persistSyncLocked();
+
+ SettingsState settingsReader = getSettingStateObject();
+ assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+ }
+
+ public void testModifySettingOverrideableByRestore_preserveFlagAlreadySet_flagValueUnchanged() {
+ SettingsState settingsWriter = getSettingStateObject();
+ // Init the setting.
+ settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+ // This modification will set isValuePreservedInRestore = true.
+ settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+ // This modification shouldn't change the value of isValuePreservedInRestore since it's
+ // already been set to true.
+ settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
+ /* overrideableByRestore */ true);
+ settingsWriter.persistSyncLocked();
+
+ SettingsState settingsReader = getSettingStateObject();
+ assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+ }
+
+ private SettingsState getSettingStateObject() {
+ SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
+ return settingsState;
+ }
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index df77b5b..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" />
@@ -308,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"
@@ -637,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/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/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/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 85cfe91..53df025 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1165,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 635ac68..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] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index a58e3d7..65fc215 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -31,8 +31,8 @@
import static android.os.BatteryManager.EXTRA_PLUGGED;
import static android.os.BatteryManager.EXTRA_STATUS;
import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
+import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM;
-import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -92,7 +92,6 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.WirelessUtils;
@@ -446,7 +445,7 @@
*/
public List<SubscriptionInfo> getFilteredSubscriptionInfo(boolean forceReload) {
List<SubscriptionInfo> subscriptions = getSubscriptionInfo(false);
- if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
+ if (subscriptions.size() == MODEM_COUNT_DUAL_MODEM) {
SubscriptionInfo info1 = subscriptions.get(0);
SubscriptionInfo info2 = subscriptions.get(1);
if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
@@ -1074,7 +1073,7 @@
mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED);
} else if (Intent.ACTION_SERVICE_STATE.equals(action)) {
ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (DEBUG) {
Log.v(TAG, "action " + action + " serviceState=" + serviceState + " subId="
@@ -1236,8 +1235,8 @@
throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED");
}
String stateExtra = intent.getStringExtra(Intent.EXTRA_SIM_STATE);
- int slotId = intent.getIntExtra(PhoneConstants.PHONE_KEY, 0);
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int slotId = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0);
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (Intent.SIM_STATE_ABSENT.equals(stateExtra)) {
final String absentReason = intent
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/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 018b631..d99607f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -10,6 +10,8 @@
import com.android.systemui.R;
+import javax.inject.Inject;
+
/**
* Activity for showing aged out bubbles.
* Must be public to be accessible to androidx...AppComponentFactory
@@ -17,6 +19,12 @@
public class BubbleOverflowActivity extends Activity {
private RecyclerView mRecyclerView;
private int mMaxBubbles;
+ private BubbleController mBubbleController;
+
+ @Inject
+ public BubbleOverflowActivity(BubbleController controller) {
+ mBubbleController = controller;
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -64,6 +72,6 @@
}
public void onDestroy() {
- super.onStop();
+ super.onDestroy();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 8987683..15c1c55 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -73,7 +73,6 @@
import com.android.systemui.bubbles.animation.ExpandedAnimationController;
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
import com.android.systemui.bubbles.animation.StackAnimationController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -716,22 +715,6 @@
return mExpandedBubble;
}
- /**
- * Sets the bubble that should be expanded and expands if needed.
- *
- * @param key the {@link NotificationEntry#key} associated with the bubble to expand.
- * @deprecated replaced by setSelectedBubble(Bubble) + setExpanded(true)
- */
- @Deprecated
- void setExpandedBubble(String key) {
- Bubble bubbleToExpand = mBubbleData.getBubbleWithKey(key);
- if (bubbleToExpand != null) {
- setSelectedBubble(bubbleToExpand);
- bubbleToExpand.setShowInShade(false);
- setExpanded(true);
- }
- }
-
// via BubbleData.Listener
void addBubble(Bubble bubble) {
if (DEBUG_BUBBLE_STACK_VIEW) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
new file mode 100644
index 0000000..e6cdf50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
@@ -0,0 +1,21 @@
+/*
+ * 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
+
+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 20917bd..2877ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -20,6 +20,7 @@
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;
@@ -85,7 +86,7 @@
/**
* Maps interfaces to implementations for use with Dagger.
*/
-@Module
+@Module(includes = {ControlsModule.class})
public abstract class DependencyBinder {
/**
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/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index 9f21950..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
@@ -99,6 +99,13 @@
.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();
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/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index 7124517..0377f57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -22,7 +22,7 @@
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.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -48,7 +48,7 @@
* 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 #setSectionsProvider} and {@link #setComparators}
+ * {@link #setSections} and {@link #setComparators}
*
* The exact order of all hooks is as follows:
* 0. Collection listeners are fired ({@link #addCollectionListener}).
@@ -58,7 +58,7 @@
* 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. SectionsProvider is called on each top-level entry in the list ({@link #setSectionsProvider})
+ * 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})
@@ -142,14 +142,13 @@
}
/**
- * 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.
+ * 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 setSectionsProvider(SectionsProvider provider) {
- mShadeListBuilder.setSectionsProvider(provider);
+ public void setSections(List<NotifSection> sections) {
+ mShadeListBuilder.setSections(sections);
}
/**
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 820c042..f7fe064 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection
+import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_MIN
import android.service.notification.NotificationListenerService.Ranking
@@ -191,9 +192,9 @@
}
private fun NotificationEntry.isPeopleNotification() =
- sbn.isPeopleNotification()
- private fun StatusBarNotification.isPeopleNotification() =
- peopleNotificationIdentifier.isPeopleNotification(this)
+ sbn.isPeopleNotification(channel)
+ private fun StatusBarNotification.isPeopleNotification(channel: NotificationChannel) =
+ peopleNotificationIdentifier.isPeopleNotification(this, channel)
private fun NotificationEntry.isHighPriority() =
highPriorityProvider.isHighPriority(this)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index c19ce27..97f8ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -30,6 +30,7 @@
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.util.ArrayMap;
+import android.util.Pair;
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
@@ -40,7 +41,7 @@
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;
@@ -82,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<>();
@@ -170,12 +171,15 @@
promoter.setInvalidationListener(this::onPromoterInvalidated);
}
- 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);
+ }
}
void setComparators(List<NotifComparator> comparators) {
@@ -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);
@@ -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();
@@ -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)) {
@@ -803,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/coalescer/EventBatch.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java
index 2c6a165..2eec68b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.coalescer;
+import androidx.annotation.Nullable;
+
import java.util.ArrayList;
import java.util.List;
@@ -35,6 +37,8 @@
*/
final List<CoalescedEvent> mMembers = new ArrayList<>();
+ @Nullable Runnable mCancelShortTimeout;
+
EventBatch(long createdTimestamp, String groupKey) {
mCreatedTimestamp = createdTimestamp;
this.mGroupKey = groupKey;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
index 8076616..f589038 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.coalescer;
+import static com.android.systemui.statusbar.notification.logging.NotifEvent.BATCH_MAX_TIMEOUT;
import static com.android.systemui.statusbar.notification.logging.NotifEvent.COALESCED_EVENT;
import static com.android.systemui.statusbar.notification.logging.NotifEvent.EARLY_BATCH_EMIT;
import static com.android.systemui.statusbar.notification.logging.NotifEvent.EMIT_EVENT_BATCH;
@@ -71,7 +72,8 @@
private final DelayableExecutor mMainExecutor;
private final SystemClock mClock;
private final NotifLog mLog;
- private final long mGroupLingerDuration;
+ private final long mMinGroupLingerDuration;
+ private final long mMaxGroupLingerDuration;
private BatchableNotificationHandler mHandler;
@@ -82,22 +84,28 @@
public GroupCoalescer(
@Main DelayableExecutor mainExecutor,
SystemClock clock, NotifLog log) {
- this(mainExecutor, clock, log, GROUP_LINGER_DURATION);
+ this(mainExecutor, clock, log, MIN_GROUP_LINGER_DURATION, MAX_GROUP_LINGER_DURATION);
}
/**
- * @param groupLingerDuration How long, in ms, that notifications that are members of a group
- * are delayed within the GroupCoalescer before being posted
+ * @param minGroupLingerDuration How long, in ms, to wait for another notification from the same
+ * group to arrive before emitting all pending events for that
+ * group. Each subsequent arrival of a group member resets the
+ * timer for that group.
+ * @param maxGroupLingerDuration The maximum time, in ms, that a group can linger in the
+ * coalescer before it's force-emitted.
*/
GroupCoalescer(
@Main DelayableExecutor mainExecutor,
SystemClock clock,
NotifLog log,
- long groupLingerDuration) {
+ long minGroupLingerDuration,
+ long maxGroupLingerDuration) {
mMainExecutor = mainExecutor;
mClock = clock;
mLog = log;
- mGroupLingerDuration = groupLingerDuration;
+ mMinGroupLingerDuration = minGroupLingerDuration;
+ mMaxGroupLingerDuration = maxGroupLingerDuration;
}
/**
@@ -115,7 +123,7 @@
private final NotificationHandler mListener = new NotificationHandler() {
@Override
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
- maybeEmitBatch(sbn.getKey());
+ maybeEmitBatch(sbn);
applyRanking(rankingMap);
final boolean shouldCoalesce = handleNotificationPosted(sbn, rankingMap);
@@ -130,7 +138,7 @@
@Override
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
- maybeEmitBatch(sbn.getKey());
+ maybeEmitBatch(sbn);
applyRanking(rankingMap);
mHandler.onNotificationRemoved(sbn, rankingMap);
}
@@ -140,7 +148,7 @@
StatusBarNotification sbn,
RankingMap rankingMap,
int reason) {
- maybeEmitBatch(sbn.getKey());
+ maybeEmitBatch(sbn);
applyRanking(rankingMap);
mHandler.onNotificationRemoved(sbn, rankingMap, reason);
}
@@ -152,13 +160,20 @@
}
};
- private void maybeEmitBatch(String memberKey) {
- CoalescedEvent event = mCoalescedEvents.get(memberKey);
+ private void maybeEmitBatch(StatusBarNotification sbn) {
+ final CoalescedEvent event = mCoalescedEvents.get(sbn.getKey());
+ final EventBatch batch = mBatches.get(sbn.getGroupKey());
if (event != null) {
mLog.log(EARLY_BATCH_EMIT,
String.format("Modification of %s triggered early emit of batched group %s",
- memberKey, requireNonNull(event.getBatch()).mGroupKey));
+ sbn.getKey(), requireNonNull(event.getBatch()).mGroupKey));
emitBatch(requireNonNull(event.getBatch()));
+ } else if (batch != null
+ && mClock.uptimeMillis() - batch.mCreatedTimestamp >= mMaxGroupLingerDuration) {
+ mLog.log(BATCH_MAX_TIMEOUT,
+ String.format("Modification of %s triggered timeout emit of batched group %s",
+ sbn.getKey(), batch.mGroupKey));
+ emitBatch(batch);
}
}
@@ -175,7 +190,8 @@
}
if (sbn.isGroup()) {
- EventBatch batch = startBatchingGroup(sbn.getGroupKey());
+ final EventBatch batch = getOrBuildBatch(sbn.getGroupKey());
+
CoalescedEvent event =
new CoalescedEvent(
sbn.getKey(),
@@ -183,10 +199,10 @@
sbn,
requireRanking(rankingMap, sbn.getKey()),
batch);
+ mCoalescedEvents.put(event.getKey(), event);
batch.mMembers.add(event);
-
- mCoalescedEvents.put(event.getKey(), event);
+ resetShortTimeout(batch);
return true;
} else {
@@ -194,27 +210,39 @@
}
}
- private EventBatch startBatchingGroup(final String groupKey) {
+ private EventBatch getOrBuildBatch(final String groupKey) {
EventBatch batch = mBatches.get(groupKey);
if (batch == null) {
- final EventBatch newBatch = new EventBatch(mClock.uptimeMillis(), groupKey);
- mBatches.put(groupKey, newBatch);
- mMainExecutor.executeDelayed(() -> emitBatch(newBatch), mGroupLingerDuration);
-
- batch = newBatch;
+ batch = new EventBatch(mClock.uptimeMillis(), groupKey);
+ mBatches.put(groupKey, batch);
}
return batch;
}
+ private void resetShortTimeout(EventBatch batch) {
+ if (batch.mCancelShortTimeout != null) {
+ batch.mCancelShortTimeout.run();
+ }
+ batch.mCancelShortTimeout =
+ mMainExecutor.executeDelayed(
+ () -> {
+ batch.mCancelShortTimeout = null;
+ emitBatch(batch);
+ },
+ mMinGroupLingerDuration);
+ }
+
private void emitBatch(EventBatch batch) {
if (batch != mBatches.get(batch.mGroupKey)) {
- // If we emit a batch early, we don't want to emit it a second time when its timeout
- // expires.
- return;
+ throw new IllegalStateException("Cannot emit out-of-date batch " + batch.mGroupKey);
}
if (batch.mMembers.isEmpty()) {
throw new IllegalStateException("Batch " + batch.mGroupKey + " cannot be empty");
}
+ if (batch.mCancelShortTimeout != null) {
+ batch.mCancelShortTimeout.run();
+ batch.mCancelShortTimeout = null;
+ }
mBatches.remove(batch.mGroupKey);
@@ -299,5 +327,6 @@
void onNotificationBatchPosted(List<CoalescedEvent> events);
}
- private static final int GROUP_LINGER_DURATION = 500;
+ private static final int MIN_GROUP_LINGER_DURATION = 50;
+ private static final int MAX_GROUP_LINGER_DURATION = 500;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
index c1a11b2..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
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
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;
/**
@@ -28,4 +29,8 @@
* Coordinators should register their listeners and {@link Pluggable}s to the pipeline.
*/
void attach(NotifPipeline pipeline);
+
+ default NotifSection getSection() {
+ return null;
+ }
}
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 562a618..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,9 +16,11 @@
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.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;
@@ -39,18 +41,21 @@
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);
@@ -59,6 +64,13 @@
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());
+ }
+ }
}
/**
@@ -69,6 +81,8 @@
for (Coordinator c : mCoordinators) {
c.attach(pipeline);
}
+
+ pipeline.setSections(mOrderedSections);
}
@Override
@@ -78,5 +92,9 @@
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/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/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/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
index 3cc5e62..ccd7fa3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -99,7 +99,8 @@
}
private boolean isPeopleNotification(NotificationEntry entry) {
- return mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn());
+ return mPeopleNotificationIdentifier.isPeopleNotification(
+ entry.getSbn(), entry.getChannel());
}
private boolean hasUserSetImportance(NotificationEntry entry) {
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 02acc81..9adceb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -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",
@@ -155,7 +156,8 @@
// GroupCoalescer labels:
"CoalescedEvent",
"EarlyBatchEmit",
- "EmitEventBatch"
+ "EmitEventBatch",
+ "BatchMaxTimeout"
};
private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length;
@@ -170,13 +172,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}
@@ -204,5 +207,6 @@
public static final int COALESCED_EVENT = COALESCER_EVENT_START_INDEX;
public static final int EARLY_BATCH_EMIT = COALESCER_EVENT_START_INDEX + 1;
public static final int EMIT_EVENT_BATCH = COALESCER_EVENT_START_INDEX + 2;
+ public static final int BATCH_MAX_TIMEOUT = COALESCER_EVENT_START_INDEX + 3;
private static final int TOTAL_COALESCER_EVENT_TYPES = 3;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index 452d1eb..5c90211 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -18,13 +18,14 @@
import android.app.Notification
import android.content.Context
+import android.app.NotificationChannel
import android.service.notification.StatusBarNotification
import android.util.FeatureFlagUtils
import javax.inject.Inject
import javax.inject.Singleton
interface PeopleNotificationIdentifier {
- fun isPeopleNotification(sbn: StatusBarNotification): Boolean
+ fun isPeopleNotification(sbn: StatusBarNotification, channel: NotificationChannel): Boolean
}
@Singleton
@@ -33,12 +34,13 @@
private val context: Context
) : PeopleNotificationIdentifier {
- override fun isPeopleNotification(sbn: StatusBarNotification) =
- (sbn.notification.notificationStyle == Notification.MessagingStyle::class.java &&
+ override fun isPeopleNotification(sbn: StatusBarNotification, channel: NotificationChannel) =
+ ((sbn.notification.notificationStyle == Notification.MessagingStyle::class.java &&
(sbn.notification.shortcutId != null ||
FeatureFlagUtils.isEnabled(
context,
FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ
))) ||
- personExtractor.isPersonNotification(sbn)
+ personExtractor.isPersonNotification(sbn)) &&
+ !channel.isDemoted
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 31da4f1..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
@@ -1721,6 +1721,8 @@
*/
public void reset() {
mShowingPublicInitialized = false;
+ unDismiss();
+ resetTranslation();
onHeightReset();
requestLayout();
}
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
index ec420f3..3b106cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -25,6 +25,7 @@
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_DEMOTE;
import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE;
import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME;
import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE;
@@ -61,6 +62,7 @@
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
+import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -151,6 +153,11 @@
closeControls(v, true);
};
+ private OnClickListener mOnDemoteClick = v -> {
+ mSelectedAction = ACTION_DEMOTE;
+ closeControls(v, true);
+ };
+
public NotificationConversationInfo(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -295,6 +302,8 @@
mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null);
}
+ ImageButton demote = findViewById(R.id.demote);
+ demote.setOnClickListener(mOnDemoteClick);
}
private void bindHeader() {
@@ -609,7 +618,7 @@
}
break;
case ACTION_DEMOTE:
- // TODO: when demotion status field exists on notificationchannel
+ mChannelToUpdate.setDemoted(!mChannelToUpdate.isDemoted());
break;
}
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 4f56f56..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;
@@ -175,6 +177,8 @@
private Locale mLocale;
private int mLayoutDirection;
+ private boolean mForceNavBarHandleOpaque;
+
/** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */
private @Appearance int mAppearance;
@@ -227,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);
}
}
};
@@ -291,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 -----
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 f05b585..cca100f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -26,6 +26,7 @@
import android.os.Looper;
import android.os.Message;
import android.provider.Settings.Global;
+import android.telephony.Annotation;
import android.telephony.CellSignalStrength;
import android.telephony.CellSignalStrengthCdma;
import android.telephony.NetworkRegistrationInfo;
@@ -37,7 +38,6 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
-import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.TelephonyIntents;
@@ -53,7 +53,9 @@
import java.io.PrintWriter;
import java.util.BitSet;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.regex.Matcher;
@@ -77,12 +79,14 @@
final SubscriptionInfo mSubscriptionInfo;
// @VisibleForDemoMode
- final SparseArray<MobileIconGroup> mNetworkToIconLookup;
+ final Map<String, MobileIconGroup> mNetworkToIconLookup;
// Since some pieces of the phone state are interdependent we store it locally,
// this could potentially become part of MobileState for simplification/complication
// of code.
private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ private boolean mCA = false;
+ private boolean mCAPlus = false;
private int mDataState = TelephonyManager.DATA_DISCONNECTED;
private ServiceState mServiceState;
private SignalStrength mSignalStrength;
@@ -93,9 +97,6 @@
boolean mInflateSignalStrengths = false;
@VisibleForTesting
boolean mIsShowingIconGracefully = false;
- // Some specific carriers have 5GE network which is special LTE CA network.
- private static final int NETWORK_TYPE_LTE_CA_5GE =
- TelephonyManager.getAllNetworkTypes().length + 1;
// TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
// need listener lists anymore.
@@ -106,7 +107,7 @@
super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
NetworkCapabilities.TRANSPORT_CELLULAR, callbackHandler,
networkController);
- mNetworkToIconLookup = new SparseArray<>();
+ mNetworkToIconLookup = new HashMap<>();
mConfig = config;
mPhone = phone;
mDefaults = defaults;
@@ -213,29 +214,38 @@
private void mapIconSets() {
mNetworkToIconLookup.clear();
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_TD_SCDMA, TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_0),
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_A),
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_B),
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EHRPD),
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UMTS),
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_TD_SCDMA),
+ TelephonyIcons.THREE_G);
if (!mConfig.showAtLeast3G) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UNKNOWN),
TelephonyIcons.UNKNOWN);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EDGE),
+ TelephonyIcons.E);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_CDMA),
+ TelephonyIcons.ONE_X);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_1xRTT),
+ TelephonyIcons.ONE_X);
mDefaultIcons = TelephonyIcons.G;
} else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UNKNOWN),
TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EDGE),
TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_CDMA),
TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_1xRTT),
TelephonyIcons.THREE_G);
mDefaultIcons = TelephonyIcons.THREE_G;
}
@@ -250,33 +260,59 @@
hPlusGroup = TelephonyIcons.H_PLUS;
}
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hPlusGroup);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSDPA), hGroup);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSUPA), hGroup);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSPA), hGroup);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSPAP), hPlusGroup);
if (mConfig.show4gForLte) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_LTE),
+ TelephonyIcons.FOUR_G);
if (mConfig.hideLtePlus) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+ mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
TelephonyIcons.FOUR_G);
} else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+ mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
TelephonyIcons.FOUR_G_PLUS);
}
} else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_LTE),
+ TelephonyIcons.LTE);
if (mConfig.hideLtePlus) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+ mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
TelephonyIcons.LTE);
} else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+ mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
TelephonyIcons.LTE_PLUS);
}
}
- mNetworkToIconLookup.put(NETWORK_TYPE_LTE_CA_5GE,
+ mNetworkToIconLookup.put(toIconKeyCAPlus(TelephonyManager.NETWORK_TYPE_LTE),
TelephonyIcons.LTE_CA_5G_E);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyIcons.WFC);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_IWLAN),
+ TelephonyIcons.WFC);
+ }
+
+ private String getIconKey() {
+ if (mCA) {
+ return toIconKeyCA(mDataNetType);
+ } else if (mCAPlus) {
+ return toIconKeyCAPlus(mDataNetType);
+ } else {
+ return toIconKey(mDataNetType);
+ }
+ }
+
+ // Some specific carriers have 5GE network which is special CA network.
+ private String toIconKeyCAPlus(@Annotation.NetworkType int networkType) {
+ return toIconKeyCA(networkType) + "_Plus";
+ }
+
+ private String toIconKeyCA(@Annotation.NetworkType int networkType) {
+ return toIconKey(networkType) + "_CA";
+ }
+
+ private String toIconKey(@Annotation.NetworkType int networkType) {
+ return Integer.toString(networkType);
}
private void updateInflateSignalStrength() {
@@ -523,10 +559,11 @@
nr5GIconGroup = adjustNr5GIconGroupByDisplayGraceTime(nr5GIconGroup);
}
+ String iconKey = getIconKey();
if (nr5GIconGroup != null) {
mCurrentState.iconGroup = nr5GIconGroup;
- } else if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
- mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
+ } else if (mNetworkToIconLookup.get(iconKey) != null) {
+ mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey);
} else {
mCurrentState.iconGroup = mDefaultIcons;
}
@@ -679,6 +716,8 @@
pw.println(" mSignalStrength=" + mSignalStrength + ",");
pw.println(" mDataState=" + mDataState + ",");
pw.println(" mDataNetType=" + mDataNetType + ",");
+ pw.println(" mCA=" + mCA + ",");
+ pw.println(" mCAPlus=" + mCAPlus + ",");
pw.println(" mInflateSignalStrengths=" + mInflateSignalStrengths + ",");
pw.println(" isDataDisabled=" + isDataDisabled() + ",");
pw.println(" mIsShowingIconGracefully=" + mIsShowingIconGracefully + ",");
@@ -729,11 +768,13 @@
private void updateDataNetType(int networkType) {
mDataNetType = networkType;
+ mCA = false;
+ mCAPlus = false;
if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE) {
if (isCarrierSpecificDataIcon()) {
- mDataNetType = NETWORK_TYPE_LTE_CA_5GE;
+ mCAPlus = true;
} else if (mServiceState != null && mServiceState.isUsingCarrierAggregation()) {
- mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA;
+ mCA = true;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 679fa7e..6b3c5dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -22,8 +22,7 @@
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
-
-import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
+import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -57,7 +56,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.DemoMode;
@@ -547,7 +545,7 @@
mReceiverHandler.post(this::handleConfigurationChanged);
break;
default:
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (SubscriptionManager.isValidSubscriptionId(subId)) {
if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
@@ -582,7 +580,7 @@
}
private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) {
- if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
+ if (subscriptions.size() == MODEM_COUNT_DUAL_MODEM) {
SubscriptionInfo info1 = subscriptions.get(0);
SubscriptionInfo info2 = subscriptions.get(1);
if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/Assert.java b/packages/SystemUI/src/com/android/systemui/util/Assert.java
index 096ac3f..f6e921e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Assert.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Assert.java
@@ -18,7 +18,7 @@
import android.os.Looper;
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.VisibleForTesting;
/**
* Helper providing common assertions.
@@ -30,7 +30,9 @@
public static void isMainThread() {
if (!sMainLooper.isCurrentThread()) {
- throw new IllegalStateException("should be called from the main thread.");
+ throw new IllegalStateException("should be called from the main thread."
+ + " sMainLooper.threadName=" + sMainLooper.getThread().getName()
+ + " Thread.currentThread()=" + Thread.currentThread().getName());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/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/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index bfb0e15..c51624b 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -35,7 +35,6 @@
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
<uses-permission android:name="android.permission.CONTROL_VPN" />
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 2e0fb3b..12da006 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -57,7 +57,6 @@
import android.testing.TestableContext;
import android.testing.TestableLooper;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.DumpController;
import com.android.systemui.SysuiTestCase;
@@ -524,9 +523,9 @@
int subscription = simInited
? 1/* mock subid=1 */ : SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE;
if (data != null) intent.putExtras(data);
- intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
- intent.putExtra("subscription", subscription);
- intent.putExtra("slot", 0/* SLOT 1 */);
+
+ intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subscription);
+ intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0);
return intent;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index e0b4b81..c3df3f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -356,8 +356,7 @@
// Switch which bubble is expanded
mBubbleController.selectBubble(mRow.getEntry().getKey());
- stackView.setExpandedBubble(mRow.getEntry().getKey());
- assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry());
+ mBubbleController.expandStack();
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
mRow.getEntry().getKey()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
new file mode 100644
index 0000000..7c8c7c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.Binder
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import dagger.Lazy
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsBindingControllerTest : SysuiTestCase() {
+
+ companion object {
+ fun <T> any(): T = Mockito.any<T>()
+ private val TEST_COMPONENT_NAME_1 = ComponentName("TEST_PKG", "TEST_CLS_1")
+ private val TEST_COMPONENT_NAME_2 = ComponentName("TEST_PKG", "TEST_CLS_2")
+ private val TEST_COMPONENT_NAME_3 = ComponentName("TEST_PKG", "TEST_CLS_3")
+ }
+
+ @Mock
+ private lateinit var mockControlsController: ControlsController
+
+ private val executor = FakeExecutor(FakeSystemClock())
+ private lateinit var controller: ControlsBindingController
+ private val providers = TestableControlsBindingControllerImpl.providers
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ controller = TestableControlsBindingControllerImpl(
+ mContext, executor, Lazy { mockControlsController })
+ }
+
+ @After
+ fun tearDown() {
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ providers.clear()
+ }
+
+ @Test
+ fun testBindAndLoad() {
+ val callback: (List<Control>) -> Unit = {}
+ controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
+
+ assertEquals(1, providers.size)
+ val provider = providers.first()
+ verify(provider).maybeBindAndLoad(callback)
+ }
+
+ @Test
+ fun testBindServices() {
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+ executor.runAllReady()
+
+ assertEquals(2, providers.size)
+ assertEquals(setOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2),
+ providers.map { it.componentName }.toSet())
+ providers.forEach {
+ verify(it).bindPermanently()
+ }
+ }
+
+ @Test
+ fun testSubscribe() {
+ val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_3))
+
+ controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+ executor.runAllReady()
+
+ assertEquals(3, providers.size)
+ val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 }
+ val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 }
+ val provider3 = providers.first { it.componentName == TEST_COMPONENT_NAME_3 }
+
+ verify(provider1).maybeBindAndSubscribe(listOf(controlInfo1.controlId))
+ verify(provider2).maybeBindAndSubscribe(listOf(controlInfo2.controlId))
+ verify(provider3, never()).maybeBindAndSubscribe(any())
+ verify(provider3).unbindService() // Not needed services will be unbound
+ }
+
+ @Test
+ fun testUnsubscribe_notRefreshing() {
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+ controller.unsubscribe()
+
+ executor.runAllReady()
+
+ providers.forEach {
+ verify(it, never()).unsubscribe()
+ }
+ }
+
+ @Test
+ fun testUnsubscribe_refreshing() {
+ val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+
+ controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+ controller.unsubscribe()
+
+ executor.runAllReady()
+
+ providers.forEach {
+ verify(it).unsubscribe()
+ }
+ }
+}
+
+class TestableControlsBindingControllerImpl(
+ context: Context,
+ executor: DelayableExecutor,
+ lazyController: Lazy<ControlsController>
+) : ControlsBindingControllerImpl(context, executor, lazyController) {
+
+ companion object {
+ val providers = mutableSetOf<ControlsProviderLifecycleManager>()
+ }
+
+ override fun createProviderManager(component: ComponentName):
+ ControlsProviderLifecycleManager {
+ val provider = mock(ControlsProviderLifecycleManager::class.java)
+ val token = Binder()
+ `when`(provider.componentName).thenReturn(component)
+ `when`(provider.token).thenReturn(token)
+ providers.add(provider)
+ return provider
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
new file mode 100644
index 0000000..a19c299
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.provider.Settings
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.DumpController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Optional
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsControllerImplTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var uiController: ControlsUiController
+ @Mock
+ private lateinit var bindingController: ControlsBindingController
+ @Mock
+ private lateinit var dumpController: DumpController
+ @Mock
+ private lateinit var pendingIntent: PendingIntent
+ @Mock
+ private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper
+
+ @Captor
+ private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>>
+ @Captor
+ private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit>
+
+ private lateinit var delayableExecutor: FakeExecutor
+ private lateinit var controller: ControlsController
+
+ companion object {
+ fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+ fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+
+ private val TEST_COMPONENT = ComponentName("test.pkg", "test.class")
+ private const val TEST_CONTROL_ID = "control1"
+ private const val TEST_CONTROL_TITLE = "Test"
+ private const val TEST_DEVICE_TYPE = DeviceTypes.TYPE_AC_HEATER
+ private val TEST_CONTROL_INFO = ControlInfo(
+ TEST_COMPONENT, TEST_CONTROL_ID, TEST_CONTROL_TITLE, TEST_DEVICE_TYPE)
+
+ private val TEST_COMPONENT_2 = ComponentName("test.pkg", "test.class.2")
+ private const val TEST_CONTROL_ID_2 = "control2"
+ private const val TEST_CONTROL_TITLE_2 = "Test 2"
+ private const val TEST_DEVICE_TYPE_2 = DeviceTypes.TYPE_CAMERA
+ private val TEST_CONTROL_INFO_2 = ControlInfo(
+ TEST_COMPONENT_2, TEST_CONTROL_ID_2, TEST_CONTROL_TITLE_2, TEST_DEVICE_TYPE_2)
+ }
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ Settings.Secure.putInt(mContext.contentResolver,
+ ControlsControllerImpl.CONTROLS_AVAILABLE, 1)
+
+ delayableExecutor = FakeExecutor(FakeSystemClock())
+
+ controller = ControlsControllerImpl(
+ mContext,
+ delayableExecutor,
+ uiController,
+ bindingController,
+ Optional.of(persistenceWrapper),
+ dumpController
+ )
+ assertTrue(controller.available)
+ }
+
+ private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder {
+ return Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
+ .setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle)
+ }
+
+ @Test
+ fun testStartWithoutFavorites() {
+ assertTrue(controller.getFavoriteControls().isEmpty())
+ }
+
+ @Test
+ fun testStartWithSavedFavorites() {
+ `when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_CONTROL_INFO))
+ val controller_other = ControlsControllerImpl(
+ mContext,
+ delayableExecutor,
+ uiController,
+ bindingController,
+ Optional.of(persistenceWrapper),
+ dumpController
+ )
+ assertEquals(listOf(TEST_CONTROL_INFO), controller_other.getFavoriteControls())
+ }
+
+ @Test
+ fun testAddFavorite() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO in favorites)
+ assertEquals(1, favorites.size)
+ }
+
+ @Test
+ fun testAddMultipleFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO in favorites)
+ assertTrue(TEST_CONTROL_INFO_2 in favorites)
+ assertEquals(2, favorites.size)
+ }
+
+ @Test
+ fun testAddAndRemoveFavorite() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO !in favorites)
+ assertTrue(TEST_CONTROL_INFO_2 in favorites)
+ assertEquals(1, favorites.size)
+ }
+
+ @Test
+ fun testFavoritesSavedOnAdd() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ verify(persistenceWrapper).storeFavorites(listOf(TEST_CONTROL_INFO))
+ }
+
+ @Test
+ fun testFavoritesSavedOnRemove() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ reset(persistenceWrapper)
+
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+ verify(persistenceWrapper).storeFavorites(emptyList())
+ }
+
+ @Test
+ fun testFavoritesSavedOnChange() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {}
+
+ reset(persistenceWrapper)
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ verify(persistenceWrapper).storeFavorites(listOf(newControlInfo))
+ }
+
+ @Test
+ fun testFavoritesNotSavedOnRedundantAdd() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ reset(persistenceWrapper)
+
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+ }
+
+ @Test
+ fun testFavoritesNotSavedOnNotRemove() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+ verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+ }
+
+ @Test
+ fun testOnActionResponse() {
+ controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK)
+
+ verify(uiController).onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID,
+ ControlAction.RESPONSE_OK)
+ }
+
+ @Test
+ fun testRefreshStatus() {
+ val list = listOf(Control.StatefulBuilder(TEST_CONTROL_ID, pendingIntent).build())
+ controller.refreshStatus(TEST_COMPONENT, list)
+
+ verify(uiController).onRefreshState(TEST_COMPONENT, list)
+ }
+
+ @Test
+ fun testUnsubscribe() {
+ controller.unsubscribe()
+ verify(bindingController).unsubscribe()
+ }
+
+ @Test
+ fun testSubscribeFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+ controller.subscribeToFavorites()
+
+ verify(bindingController).subscribe(capture(controlInfoListCaptor))
+
+ assertTrue(TEST_CONTROL_INFO in controlInfoListCaptor.value)
+ assertTrue(TEST_CONTROL_INFO_2 in controlInfoListCaptor.value)
+ }
+
+ @Test
+ fun testLoadForComponent_noFavorites() {
+ var loaded = false
+ val control = builderFromInfo(TEST_CONTROL_INFO).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(1, it.size)
+ val controlStatus = it[0]
+ assertEquals(ControlStatus(control, false), controlStatus)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testLoadForComponent_favorites() {
+ var loaded = false
+ val control = builderFromInfo(TEST_CONTROL_INFO).build()
+ val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build()
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(2, it.size)
+ val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID }
+ assertEquals(ControlStatus(control, true), controlStatus)
+
+ val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 }
+ assertEquals(ControlStatus(control2, false), controlStatus2)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control, control2))
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testLoadForComponent_removed() {
+ var loaded = false
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(1, it.size)
+ val controlStatus = it[0]
+ assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
+ assertTrue(controlStatus.favorite)
+ assertTrue(controlStatus.removed)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(emptyList())
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testFavoriteInformationModifiedOnLoad() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {}
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ val favorites = controller.getFavoriteControls()
+ assertEquals(1, favorites.size)
+ assertEquals(newControlInfo, favorites[0])
+ }
+
+ @Test
+ fun testFavoriteInformationModifiedOnRefresh() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.refreshStatus(TEST_COMPONENT, listOf(control))
+
+ delayableExecutor.runAllReady()
+
+ val favorites = controller.getFavoriteControls()
+ assertEquals(1, favorites.size)
+ assertEquals(newControlInfo, favorites[0])
+ }
+
+ @Test
+ fun testClearFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ assertEquals(1, controller.getFavoriteControls().size)
+
+ controller.clearFavorites()
+ assertTrue(controller.getFavoriteControls().isEmpty())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
new file mode 100644
index 0000000..c145c1f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsFavoritePersistenceWrapperTest : SysuiTestCase() {
+
+ private lateinit var file: File
+
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var wrapper: ControlsFavoritePersistenceWrapper
+
+ @Before
+ fun setUp() {
+ file = File.createTempFile("controls_favorites", ".temp")
+ wrapper = ControlsFavoritePersistenceWrapper(file, executor)
+ }
+
+ @After
+ fun tearDown() {
+ if (file.exists() ?: false) {
+ file.delete()
+ }
+ }
+
+ @Test
+ fun testSaveAndRestore() {
+ val controlInfo1 = ControlInfo(
+ ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_1")!!,
+ "id1", "name_1", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(
+ ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_2")!!,
+ "id2", "name_2", DeviceTypes.TYPE_GENERIC_ON_OFF)
+ val list = listOf(controlInfo1, controlInfo2)
+
+ wrapper.storeFavorites(list)
+
+ executor.runAllReady()
+
+ assertEquals(list, wrapper.readFavorites())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
new file mode 100644
index 0000000..556bb40
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.IControlsProvider
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var serviceCallback: IControlsProviderCallback.Stub
+ @Mock
+ private lateinit var service: IControlsProvider.Stub
+
+ private val componentName = ComponentName("test.pkg", "test.cls")
+ private lateinit var manager: ControlsProviderLifecycleManager
+ private lateinit var executor: DelayableExecutor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mContext.addMockService(componentName, service)
+ executor = FakeExecutor(FakeSystemClock())
+ `when`(service.asBinder()).thenCallRealMethod()
+ `when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service)
+
+ manager = ControlsProviderLifecycleManager(
+ context,
+ executor,
+ serviceCallback,
+ componentName
+ )
+ }
+
+ @After
+ fun tearDown() {
+ manager.unbindService()
+ }
+
+ @Test
+ fun testBindService() {
+ manager.bindPermanently()
+ assertTrue(mContext.isBound(componentName))
+ }
+
+ @Test
+ fun testUnbindService() {
+ manager.bindPermanently()
+ manager.unbindService()
+ assertFalse(mContext.isBound(componentName))
+ }
+
+ @Test
+ fun testMaybeBindAndLoad() {
+ val callback: (List<Control>) -> Unit = {}
+ manager.maybeBindAndLoad(callback)
+
+ verify(service).load()
+
+ assertTrue(mContext.isBound(componentName))
+ assertEquals(callback, manager.lastLoadCallback)
+ }
+
+ @Test
+ fun testMaybeUnbind_bindingAndCallback() {
+ manager.maybeBindAndLoad {}
+
+ manager.maybeUnbindAndRemoveCallback()
+ assertFalse(mContext.isBound(componentName))
+ assertNull(manager.lastLoadCallback)
+ }
+
+ @Test
+ fun testUnsubscribe() {
+ manager.bindPermanently()
+ manager.unsubscribe()
+
+ verify(service).unsubscribe()
+ }
+
+ @Test
+ fun testMaybeBindAndSubscribe() {
+ val list = listOf("TEST_ID")
+ manager.maybeBindAndSubscribe(list)
+
+ assertTrue(mContext.isBound(componentName))
+ verify(service).subscribe(list)
+ }
+
+ @Test
+ fun testMaybeBindAndAction() {
+ val controlId = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+ manager.maybeBindAndSendAction(controlId, action)
+
+ assertTrue(mContext.isBound(componentName))
+ verify(service).onAction(controlId, action)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
new file mode 100644
index 0000000..d6993c0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.os.RemoteException
+import android.service.controls.IControlsProvider
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderServiceWrapperTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var service: IControlsProvider
+
+ private val exception = RemoteException()
+
+ private lateinit var wrapper: ControlsProviderServiceWrapper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ wrapper = ControlsProviderServiceWrapper(service)
+ }
+
+ @Test
+ fun testLoad_happyPath() {
+ val result = wrapper.load()
+
+ assertTrue(result)
+ verify(service).load()
+ }
+
+ @Test
+ fun testLoad_error() {
+ `when`(service.load()).thenThrow(exception)
+ val result = wrapper.load()
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testSubscribe_happyPath() {
+ val list = listOf("TEST_ID")
+ val result = wrapper.subscribe(list)
+
+ assertTrue(result)
+ verify(service).subscribe(list)
+ }
+
+ @Test
+ fun testSubscribe_error() {
+ `when`(service.subscribe(any())).thenThrow(exception)
+
+ val list = listOf("TEST_ID")
+ val result = wrapper.subscribe(list)
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testUnsubscribe_happyPath() {
+ val result = wrapper.unsubscribe()
+
+ assertTrue(result)
+ verify(service).unsubscribe()
+ }
+
+ @Test
+ fun testUnsubscribe_error() {
+ `when`(service.unsubscribe()).thenThrow(exception)
+ val result = wrapper.unsubscribe()
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testOnAction_happyPath() {
+ val id = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+
+ val result = wrapper.onAction(id, action)
+
+ assertTrue(result)
+ verify(service).onAction(id, action)
+ }
+
+ @Test
+ fun testOnAction_error() {
+ `when`(service.onAction(any(), any())).thenThrow(exception)
+
+ val id = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+
+ val result = wrapper.onAction(id, action)
+
+ assertFalse(result)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
new file mode 100644
index 0000000..f09aab9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.pm.ServiceInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.settingslib.applications.ServiceListing
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsListingControllerImplTest : SysuiTestCase() {
+
+ companion object {
+ private const val TEST_LABEL = "TEST_LABEL"
+ private const val TEST_PERMISSION = "permission"
+ fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+ fun <T> any(): T = Mockito.any<T>()
+ }
+
+ @Mock
+ private lateinit var mockSL: ServiceListing
+ @Mock
+ private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
+ @Mock
+ private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
+ @Mock
+ private lateinit var serviceInfo: ServiceInfo
+ @Mock
+ private lateinit var componentName: ComponentName
+
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var controller: ControlsListingControllerImpl
+
+ private var serviceListingCallbackCaptor =
+ ArgumentCaptor.forClass(ServiceListing.Callback::class.java)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(serviceInfo.componentName).thenReturn(componentName)
+
+ controller = ControlsListingControllerImpl(mContext, executor, mockSL)
+ verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
+ }
+
+ @After
+ fun tearDown() {
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ }
+
+ @Test
+ fun testNoServices_notListening() {
+ assertTrue(controller.getCurrentServices().isEmpty())
+ }
+
+ @Test
+ fun testStartListening_onFirstCallback() {
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+
+ verify(mockSL).setListening(true)
+ }
+
+ @Test
+ fun testStartListening_onlyOnce() {
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ executor.runAllReady()
+
+ verify(mockSL).setListening(true)
+ }
+
+ @Test
+ fun testStopListening_callbackRemoved() {
+ controller.addCallback(mockCallback)
+
+ executor.runAllReady()
+
+ controller.removeCallback(mockCallback)
+
+ executor.runAllReady()
+
+ verify(mockSL).setListening(false)
+ }
+
+ @Test
+ fun testStopListening_notWhileRemainingCallbacks() {
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ executor.runAllReady()
+
+ controller.removeCallback(mockCallback)
+
+ executor.runAllReady()
+
+ verify(mockSL, never()).setListening(false)
+ }
+
+ @Test
+ fun testReloadOnFirstCallbackAdded() {
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+
+ verify(mockSL).reload()
+ }
+
+ @Test
+ fun testCallbackCalledWhenAdded() {
+ `when`(mockSL.reload()).then {
+ serviceListingCallbackCaptor.value.onServicesReloaded(emptyList())
+ }
+
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+ verify(mockCallback).onServicesUpdated(any())
+ reset(mockCallback)
+
+ controller.addCallback(mockCallbackOther)
+ executor.runAllReady()
+ verify(mockCallbackOther).onServicesUpdated(any())
+ verify(mockCallback, never()).onServicesUpdated(any())
+ }
+
+ @Test
+ fun testCallbackGetsList() {
+ val list = listOf(serviceInfo)
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ @Suppress("unchecked_cast")
+ val captor: ArgumentCaptor<List<CandidateInfo>> =
+ ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<CandidateInfo>>
+
+ executor.runAllReady()
+ reset(mockCallback)
+ reset(mockCallbackOther)
+
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+ verify(mockCallback).onServicesUpdated(capture(captor))
+ assertEquals(1, captor.value.size)
+ assertEquals(componentName.flattenToString(), captor.value[0].key)
+
+ verify(mockCallbackOther).onServicesUpdated(capture(captor))
+ assertEquals(1, captor.value.size)
+ assertEquals(componentName.flattenToString(), captor.value[0].key)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index cc5514f..d852fa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -571,7 +571,7 @@
}
private NotificationEntry createNotification() {
- Notification.Builder n = new Notification.Builder(mContext, "")
+ Notification.Builder n = new Notification.Builder(mContext, "id")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text");
@@ -582,6 +582,7 @@
.setUid(TEST_UID)
.setId(mId++)
.setNotification(n.build())
+ .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
.setUser(new UserHandle(ActivityManager.getCurrentUser()))
.build();
}
@@ -616,7 +617,7 @@
@Test
public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() {
Assert.sMainLooper = TestableLooper.get(this).getLooper();
- Notification.Builder n = new Notification.Builder(mContext, "")
+ Notification.Builder n = new Notification.Builder(mContext, "di")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text");
@@ -628,6 +629,7 @@
.setId(mId++)
.setNotification(n.build())
.setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
.build();
mEntryManager.addActiveNotificationForTest(mEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
index 93909dc..7c3665b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -60,7 +60,8 @@
final NotificationEntry entry = new NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(false);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -75,7 +76,8 @@
.setNotification(notification)
.setImportance(IMPORTANCE_LOW)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true);
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(true);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -90,7 +92,8 @@
final NotificationEntry entry = new NotificationEntryBuilder()
.setNotification(notification)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(false);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -106,7 +109,8 @@
.setNotification(notification)
.setImportance(IMPORTANCE_LOW)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(false);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -122,7 +126,8 @@
.setNotification(notification)
.setImportance(IMPORTANCE_MIN)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(false);
// THEN it does NOT have high priority
assertFalse(mHighPriorityProvider.isHighPriority(entry));
@@ -144,7 +149,8 @@
.setNotification(notification)
.setChannel(channel)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true);
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(true);
// THEN it does NOT have high priority
assertFalse(mHighPriorityProvider.isHighPriority(entry));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index e273191..7ab4846 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection
import android.app.Notification
+import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_LOW
@@ -81,6 +82,7 @@
.setNotification(
Notification.Builder(mContext, "test")
.build())
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
@@ -94,6 +96,7 @@
.setNotification(
Notification.Builder(mContext, "test")
.build())
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
@@ -116,6 +119,7 @@
.setOpPkg("pkg")
.setTag("tag")
.setNotification(aN)
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
@@ -130,6 +134,7 @@
.setOpPkg("pkg2")
.setTag("tag")
.setNotification(bN)
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
@@ -149,6 +154,7 @@
.setTag("tag")
.setNotification(notif)
.setUser(mContext.user)
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setOverrideGroupKey("")
.build()
@@ -168,6 +174,7 @@
.setTag("tag")
.setNotification(notif)
.setUser(mContext.user)
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setOverrideGroupKey("")
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 0a23571..e915be3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -19,6 +19,7 @@
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;
@@ -47,7 +48,7 @@
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;
@@ -450,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));
@@ -552,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);
@@ -584,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
@@ -631,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);
@@ -639,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);
@@ -659,7 +762,7 @@
mOnBeforeTransformGroupsListener,
promoter,
mOnBeforeSortListener,
- sectioner,
+ section,
comparator,
preRenderFilter,
mOnBeforeRenderListListener,
@@ -672,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())
@@ -686,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
@@ -711,7 +814,7 @@
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
- sectionsProvider.invalidateList();
+ section.invalidateList();
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
@@ -984,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());
@@ -1001,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) {
@@ -1169,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;
@@ -1205,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/coalescer/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
index 5e0baf2..86c1eb97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
@@ -80,7 +80,8 @@
mExecutor,
mClock,
mLog,
- LINGER_DURATION);
+ MIN_LINGER_DURATION,
+ MAX_LINGER_DURATION);
mCoalescer.setNotificationHandler(mListener);
mCoalescer.attach(mListenerService);
@@ -96,7 +97,7 @@
new NotificationEntryBuilder()
.setId(0)
.setPkg(TEST_PACKAGE_A));
- mClock.advanceTime(LINGER_DURATION);
+ mClock.advanceTime(MIN_LINGER_DURATION);
// THEN the event is passed through to the handler
verify(mListener).onNotificationPosted(notif1.sbn, notif1.rankingMap);
@@ -144,12 +145,16 @@
.setId(1)
.setGroup(mContext, GROUP_1));
+ mClock.advanceTime(2);
+
NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_A)
.setId(2)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
+ mClock.advanceTime(3);
+
NotifEvent notif3 = mNoMan.postNotif(new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_A)
.setId(3)
@@ -161,7 +166,7 @@
verify(mListener, never()).onNotificationBatchPosted(anyList());
// WHEN enough time passes
- mClock.advanceTime(LINGER_DURATION);
+ mClock.advanceTime(MIN_LINGER_DURATION);
// THEN the coalesced notifs are applied. The summary is sorted to the front.
verify(mListener).onNotificationBatchPosted(Arrays.asList(
@@ -212,7 +217,7 @@
// WHEN the time runs out on the remainder of the group
clearInvocations(mListener);
- mClock.advanceTime(LINGER_DURATION);
+ mClock.advanceTime(MIN_LINGER_DURATION);
// THEN no lingering batch is applied
verify(mListener, never()).onNotificationBatchPosted(anyList());
@@ -225,11 +230,13 @@
.setPkg(TEST_PACKAGE_A)
.setId(1)
.setGroup(mContext, GROUP_1));
+ mClock.advanceTime(2);
NotifEvent notif2a = mNoMan.postNotif(new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_A)
.setId(2)
.setContentTitle(mContext, "Version 1")
.setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
// WHEN one of them gets updated
NotifEvent notif2b = mNoMan.postNotif(new NotificationEntryBuilder()
@@ -248,7 +255,7 @@
any(RankingMap.class));
// THEN second, the update is emitted
- mClock.advanceTime(LINGER_DURATION);
+ mClock.advanceTime(MIN_LINGER_DURATION);
verify(mListener).onNotificationBatchPosted(Collections.singletonList(
new CoalescedEvent(notif2b.key, 0, notif2b.sbn, notif2b.ranking, null)
));
@@ -308,14 +315,61 @@
.setId(17));
// THEN they have the new rankings when they are eventually emitted
- mClock.advanceTime(LINGER_DURATION);
+ mClock.advanceTime(MIN_LINGER_DURATION);
verify(mListener).onNotificationBatchPosted(Arrays.asList(
new CoalescedEvent(notif1.key, 0, notif1.sbn, ranking1b, null),
new CoalescedEvent(notif2.key, 1, notif2.sbn, ranking2b, null)
));
}
- private static final long LINGER_DURATION = 4700;
+ @Test
+ public void testMaxLingerDuration() {
+ // GIVEN five coalesced notifications that have collectively taken 20ms to arrive, 2ms
+ // longer than the max linger duration
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(1)
+ .setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
+ NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(2)
+ .setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
+ NotifEvent notif3 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(3)
+ .setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
+ NotifEvent notif4 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(4)
+ .setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
+ NotifEvent notif5 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(5)
+ .setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
+
+ // WHEN a sixth notification arrives
+ NotifEvent notif6 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(6)
+ .setGroup(mContext, GROUP_1));
+
+ // THEN the first five notifications are emitted in a batch
+ verify(mListener).onNotificationBatchPosted(Arrays.asList(
+ new CoalescedEvent(notif1.key, 0, notif1.sbn, notif1.ranking, null),
+ new CoalescedEvent(notif2.key, 1, notif2.sbn, notif2.ranking, null),
+ new CoalescedEvent(notif3.key, 2, notif3.sbn, notif3.ranking, null),
+ new CoalescedEvent(notif4.key, 3, notif4.sbn, notif4.ranking, null),
+ new CoalescedEvent(notif5.key, 4, notif5.sbn, notif5.ranking, null)
+ ));
+ }
+
+ private static final long MIN_LINGER_DURATION = 5;
+ private static final long MAX_LINGER_DURATION = 18;
private static final String TEST_PACKAGE_A = "com.test.package_a";
private static final String TEST_PACKAGE_B = "com.test.package_b";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 0bf458c..61f0b26 100644
--- 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
@@ -62,6 +62,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
+import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -684,6 +685,34 @@
}
@Test
+ public void testDemote() throws Exception {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ ImageButton demote = mNotificationInfo.findViewById(R.id.demote);
+ demote.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertTrue(captor.getValue().isDemoted());
+ }
+
+ @Test
public void testMute_mute() throws Exception {
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 068707f..21100458ad 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -237,6 +237,9 @@
// Inform the user that the current network may not support using a randomized MAC address.
NOTE_NETWORK_NO_MAC_RANDOMIZATION_SUPPORT = 56;
+ // Inform the user that EAP failure occurs
+ NOTE_WIFI_EAP_FAILURE = 57;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 36a9e0eb..c9fdd5a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1147,8 +1147,8 @@
@ShortcutType int shortcutType) {
Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE, shortcutType);
final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
- bundle.putInt(AccessibilityManager.EXTRA_SHORTCUT_TYPE, shortcutType);
mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
}
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/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7d354d2..cdd7510 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -19,6 +19,7 @@
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
+import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED;
import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
@@ -269,6 +270,9 @@
@GuardedBy("mLock")
private final LocalLog mWtfHistory;
+ @GuardedBy("mLock")
+ private boolean mExpiredResponse;
+
/**
* Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id.
*/
@@ -690,6 +694,7 @@
@GuardedBy("mLock")
private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
int flags) {
+ mExpiredResponse = false;
if (mForAugmentedAutofillOnly || mRemoteFillService == null) {
if (sVerbose) {
Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead "
@@ -1307,6 +1312,9 @@
}
}
+ // The client becomes invisible for the authentication, the response is effective.
+ mExpiredResponse = false;
+
final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
if (sDebug) {
@@ -2322,16 +2330,18 @@
* @param id The id of the view that is entered.
* @param viewState The view that is entered.
* @param flags The flag that was passed by the AutofillManager.
+ *
+ * @return {@code true} if a new fill response is requested.
*/
@GuardedBy("mLock")
- private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
+ private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
@NonNull ViewState viewState, int flags) {
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
mForAugmentedAutofillOnly = false;
if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
ViewState.STATE_RESTARTED_SESSION, flags);
- return;
+ return true;
}
// If it's not, then check if it it should start a partition.
@@ -2342,12 +2352,14 @@
}
maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
ViewState.STATE_STARTED_PARTITION, flags);
+ return true;
} else {
if (sVerbose) {
Slog.v(TAG, "Not starting new partition for view " + id + ": "
+ viewState.getStateAsString());
}
}
+ return false;
}
/**
@@ -2355,7 +2367,7 @@
*
* @param id The id of the view that is entered
*
- * @return {@code true} iff a new partition should be started
+ * @return {@code true} if a new partition should be started
*/
@GuardedBy("mLock")
private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
@@ -2363,6 +2375,13 @@
return true;
}
+ if (mExpiredResponse) {
+ if (sDebug) {
+ Slog.d(TAG, "Starting a new partition because the response has expired.");
+ }
+ return true;
+ }
+
final int numResponses = mResponses.size();
if (numResponses >= AutofillManagerService.getPartitionMaxCount()) {
Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
@@ -2414,6 +2433,14 @@
+ id + " destroyed");
return;
}
+ if (action == ACTION_RESPONSE_EXPIRED) {
+ mExpiredResponse = true;
+ if (sDebug) {
+ Slog.d(TAG, "Set the response has expired.");
+ }
+ return;
+ }
+
id.setSessionId(this.id);
if (sVerbose) {
Slog.v(TAG, "updateLocked(" + this.id + "): id=" + id + ", action="
@@ -2577,7 +2604,9 @@
return;
}
- requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags);
+ if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) {
+ return;
+ }
if (isSameViewEntered) {
return;
@@ -3678,6 +3707,8 @@
return "VIEW_EXITED";
case ACTION_VALUE_CHANGED:
return "VALUE_CHANGED";
+ case ACTION_RESPONSE_EXPIRED:
+ return "RESPONSE_EXPIRED";
default:
return "UNKNOWN_" + action;
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
index 17cb739..1e3ee88 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
@@ -16,24 +16,36 @@
package com.android.server.autofill.ui;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.PixelFormat;
+import android.graphics.drawable.Icon;
import android.os.IBinder;
import android.service.autofill.Dataset;
import android.util.Log;
import android.util.Slog;
+import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.R;
+
+import java.util.List;
+
/**
* This is a temporary inline suggestion UI inflater which will be replaced by the ExtServices
* implementation.
@@ -72,18 +84,66 @@
mContext.getDisplay(), (IBinder) null);
final SurfaceControl sc = wvr.getSurfacePackage().getSurfaceControl();
- TextView textView = new TextView(mContext);
- textView.setText(datasetValue.getTextValue());
- textView.setBackgroundColor(Color.WHITE);
- textView.setTextColor(Color.BLACK);
- if (onClickListener != null) {
- textView.setOnClickListener(onClickListener);
- }
+ final ViewGroup suggestionView =
+ (ViewGroup) renderSlice(dataset.getFieldInlinePresentation(index).getSlice());
WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(width, height,
WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
- wvr.addView(textView, lp);
+ wvr.addView(suggestionView, lp);
return sc;
}
+
+ private View renderSlice(Slice slice) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ final ViewGroup suggestionView =
+ (ViewGroup) inflater.inflate(R.layout.autofill_inline_suggestion, null);
+
+ final ImageView startIconView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_start_icon);
+ final TextView titleView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_title);
+ final TextView subtitleView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_subtitle);
+ final ImageView endIconView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_end_icon);
+
+ boolean hasStartIcon = false;
+ boolean hasEndIcon = false;
+ boolean hasSubtitle = false;
+ final List<SliceItem> sliceItems = slice.getItems();
+ for (int i = 0; i < sliceItems.size(); i++) {
+ final SliceItem sliceItem = sliceItems.get(i);
+ if (sliceItem.getFormat().equals(FORMAT_IMAGE)) {
+ final Icon sliceIcon = sliceItem.getIcon();
+ if (i == 0) { // start icon
+ startIconView.setImageIcon(sliceIcon);
+ hasStartIcon = true;
+ } else { // end icon
+ endIconView.setImageIcon(sliceIcon);
+ hasEndIcon = true;
+ }
+ } else if (sliceItem.getFormat().equals(FORMAT_TEXT)) {
+ final List<String> sliceHints = sliceItem.getHints();
+ final String sliceText = sliceItem.getText().toString();
+ if (sliceHints.contains("inline_title")) { // title
+ titleView.setText(sliceText);
+ } else { // subtitle
+ subtitleView.setText(sliceText);
+ hasSubtitle = true;
+ }
+ }
+ }
+ if (!hasStartIcon) {
+ startIconView.setVisibility(View.GONE);
+ }
+ if (!hasEndIcon) {
+ endIconView.setVisibility(View.GONE);
+ }
+ if (!hasSubtitle) {
+ subtitleView.setVisibility(View.GONE);
+ }
+
+ return suggestionView;
+ }
}
diff --git a/services/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/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/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index dc393d1..e9db9c8 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.location.LocationManager.FUSED_PROVIDER;
import static android.location.LocationManager.GPS_PROVIDER;
@@ -83,6 +85,7 @@
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;
@@ -459,8 +462,10 @@
Log.d(TAG, "[u" + userId + "] location enabled = " + isLocationEnabledForUser(userId));
}
- Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION);
- intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId));
+ Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
+ .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId))
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
for (LocationProviderManager manager : mProviderManagers) {
@@ -929,9 +934,11 @@
// update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
mSettingsStore.setLocationProviderAllowed(mName, useable, userId);
- Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION);
- intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName);
- intent.putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, useable);
+ Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION)
+ .putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName)
+ .putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, useable)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
}
@@ -1396,20 +1403,19 @@
private String getResolutionPermission(int resolutionLevel) {
switch (resolutionLevel) {
case RESOLUTION_LEVEL_FINE:
- return android.Manifest.permission.ACCESS_FINE_LOCATION;
+ return ACCESS_FINE_LOCATION;
case RESOLUTION_LEVEL_COARSE:
- return android.Manifest.permission.ACCESS_COARSE_LOCATION;
+ return ACCESS_COARSE_LOCATION;
default:
return null;
}
}
private int getAllowedResolutionLevel(int pid, int uid) {
- if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
- pid, uid) == PERMISSION_GRANTED) {
+ if (mContext.checkPermission(ACCESS_FINE_LOCATION, pid, uid) == PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_FINE;
- } else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
- pid, uid) == PERMISSION_GRANTED) {
+ } else if (mContext.checkPermission(ACCESS_COARSE_LOCATION, pid, uid)
+ == PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_COARSE;
} else {
return RESOLUTION_LEVEL_NONE;
@@ -1420,59 +1426,28 @@
return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
}
- private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) {
- if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
- throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission");
- }
+ private boolean checkCallingOrSelfLocationPermission() {
+ return mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED
+ || mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION)
+ == PERMISSION_GRANTED;
}
- @GuardedBy("mLock")
- private int getMinimumResolutionLevelForProviderUseLocked(String provider) {
- if (GPS_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) {
- // gps and passive providers require FINE permission
- return RESOLUTION_LEVEL_FINE;
- } else if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) {
- // network and fused providers are ok with COARSE or FINE
- return RESOLUTION_LEVEL_COARSE;
- } else {
- for (LocationProviderManager lp : mProviderManagers) {
- if (!lp.getName().equals(provider)) {
- continue;
- }
-
- ProviderProperties properties = lp.getProperties();
- if (properties != null) {
- if (properties.mRequiresSatellite) {
- // provider requiring satellites require FINE permission
- return RESOLUTION_LEVEL_FINE;
- } else if (properties.mRequiresNetwork || properties.mRequiresCell) {
- // provider requiring network and or cell require COARSE or FINE
- return RESOLUTION_LEVEL_COARSE;
- }
- }
- }
+ private void enforceCallingOrSelfLocationPermission() {
+ if (checkCallingOrSelfLocationPermission()) {
+ return;
}
- return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE
+ throw new SecurityException("uid " + Binder.getCallingUid() + " does not have "
+ + ACCESS_COARSE_LOCATION + " or " + ACCESS_FINE_LOCATION + ".");
}
- @GuardedBy("mLock")
- private void checkResolutionLevelIsSufficientForProviderUseLocked(int allowedResolutionLevel,
- String providerName) {
- int requiredResolutionLevel = getMinimumResolutionLevelForProviderUseLocked(providerName);
- if (allowedResolutionLevel < requiredResolutionLevel) {
- switch (requiredResolutionLevel) {
- case RESOLUTION_LEVEL_FINE:
- throw new SecurityException("\"" + providerName + "\" location provider " +
- "requires ACCESS_FINE_LOCATION permission.");
- case RESOLUTION_LEVEL_COARSE:
- throw new SecurityException("\"" + providerName + "\" location provider " +
- "requires ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission.");
- default:
- throw new SecurityException("Insufficient permission for \"" + providerName +
- "\" location provider.");
- }
+ private void enforceCallingOrSelfPackageName(String packageName) {
+ int uid = Binder.getCallingUid();
+ if (ArrayUtils.contains(mPackageManager.getPackagesForUid(uid), packageName)) {
+ return;
}
+
+ throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid);
}
public static int resolutionLevelToOp(int allowedResolutionLevel) {
@@ -1548,7 +1523,10 @@
*/
@Override
public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
- int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+ if (!checkCallingOrSelfLocationPermission()) {
+ return Collections.emptyList();
+ }
+
synchronized (mLock) {
ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
for (LocationProviderManager manager : mProviderManagers) {
@@ -1556,9 +1534,6 @@
if (FUSED_PROVIDER.equals(name)) {
continue;
}
- if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) {
- continue;
- }
if (enabledOnly && !manager.isUseable()) {
continue;
}
@@ -2002,33 +1977,18 @@
return sanitizedRequest;
}
- private void checkPackageName(String packageName) {
- if (packageName == null) {
- throw new SecurityException("invalid package name: " + null);
- }
- int uid = Binder.getCallingUid();
- String[] packages = mPackageManager.getPackagesForUid(uid);
- if (packages == null) {
- throw new SecurityException("invalid UID " + uid);
- }
- for (String pkg : packages) {
- if (packageName.equals(pkg)) return;
- }
- throw new SecurityException("invalid package name: " + packageName);
- }
-
@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
PendingIntent intent, String packageName, String featureId,
String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
+ enforceCallingOrSelfLocationPermission();
+ enforceCallingOrSelfPackageName(packageName);
+
synchronized (mLock) {
if (request == null) request = DEFAULT_LOCATION_REQUEST;
- checkPackageName(packageName);
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
- request.getProvider());
WorkSource workSource = request.getWorkSource();
if (workSource != null && !workSource.isEmpty()) {
mContext.enforceCallingOrSelfPermission(
@@ -2135,7 +2095,7 @@
@Override
public void removeUpdates(ILocationListener listener, PendingIntent intent,
String packageName) {
- checkPackageName(packageName);
+ enforceCallingOrSelfPackageName(packageName);
int pid = Binder.getCallingPid();
int uid = Binder.getCallingUid();
@@ -2197,12 +2157,12 @@
@Override
public Location getLastLocation(LocationRequest r, String packageName, String featureId) {
+ enforceCallingOrSelfLocationPermission();
+ enforceCallingOrSelfPackageName(packageName);
+
synchronized (mLock) {
LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- checkPackageName(packageName);
- checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
- request.getProvider());
// no need to sanitize this request, as only the provider name is used
final int pid = Binder.getCallingPid();
@@ -2348,7 +2308,7 @@
public boolean injectLocation(Location location) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
"Location Hardware permission not granted to inject location");
- mContext.enforceCallingPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
+ mContext.enforceCallingPermission(ACCESS_FINE_LOCATION,
"Access Fine Location permission not granted to inject Location");
synchronized (mLock) {
@@ -2374,17 +2334,14 @@
String packageName, String featureId, String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
+ mContext.enforceCallingOrSelfPermission(ACCESS_FINE_LOCATION, null);
+ enforceCallingOrSelfPackageName(packageName);
+
if (request == null) request = DEFAULT_LOCATION_REQUEST;
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
if (intent == null) {
throw new IllegalArgumentException("invalid pending intent: " + null);
}
- checkPackageName(packageName);
- synchronized (mLock) {
- checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
- request.getProvider());
- }
// Require that caller can manage given document
boolean callerHasLocationHardwarePermission =
mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
@@ -2430,7 +2387,7 @@
if (intent == null) {
throw new IllegalArgumentException("invalid pending intent: " + null);
}
- checkPackageName(packageName);
+ enforceCallingOrSelfPackageName(packageName);
if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent);
@@ -2517,36 +2474,30 @@
@Override
public boolean sendExtraCommand(String providerName, String command, Bundle extras) {
- if (providerName == null) {
- // throw NullPointerException to remain compatible with previous implementation
- throw new NullPointerException();
- }
+ Objects.requireNonNull(providerName);
+ Objects.requireNonNull(command);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, null);
+ enforceCallingOrSelfLocationPermission();
- synchronized (mLock) {
- checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
- providerName);
+ mLocationUsageLogger.logLocationApiUsage(
+ LocationStatsEnums.USAGE_STARTED,
+ LocationStatsEnums.API_SEND_EXTRA_COMMAND,
+ providerName);
- mLocationUsageLogger.logLocationApiUsage(
- LocationStatsEnums.USAGE_STARTED,
- LocationStatsEnums.API_SEND_EXTRA_COMMAND,
- providerName);
-
- LocationProviderManager manager = getLocationProviderManager(providerName);
- if (manager != null) {
- manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command,
- extras);
- }
-
- mLocationUsageLogger.logLocationApiUsage(
- LocationStatsEnums.USAGE_ENDED,
- LocationStatsEnums.API_SEND_EXTRA_COMMAND,
- providerName);
-
- return true;
+ LocationProviderManager manager = getLocationProviderManager(providerName);
+ if (manager != null) {
+ manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command,
+ extras);
}
+
+ mLocationUsageLogger.logLocationApiUsage(
+ LocationStatsEnums.USAGE_ENDED,
+ LocationStatsEnums.API_SEND_EXTRA_COMMAND,
+ providerName);
+
+ return true;
}
@Override
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index b9b7bf7..4a1820a 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -22,10 +22,10 @@
import android.database.ContentObserver;
import android.net.NetworkStack;
import android.net.Uri;
-import android.net.nsd.DnsSdTxtRecord;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.net.util.nsd.DnsSdTxtRecord;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 7b4fd37..b464422 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -29,6 +29,7 @@
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.text.TextUtils;
@@ -36,6 +37,7 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.LongArrayQueue;
+import android.util.MathUtils;
import android.util.Slog;
import android.util.Xml;
@@ -117,6 +119,12 @@
// Whether explicit health checks are enabled or not
private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
+ @VisibleForTesting
+ static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5;
+ static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);
+ private static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
+ private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
+
private long mNumberOfNativeCrashPollsRemaining;
private static final int DB_VERSION = 1;
@@ -152,6 +160,7 @@
private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason;
private final Runnable mSaveToFile = this::saveToFile;
private final SystemClock mSystemClock;
+ private final BootThreshold mBootThreshold;
@GuardedBy("mLock")
private boolean mIsPackagesReady;
// Flag to control whether explicit health checks are supported or not
@@ -169,6 +178,7 @@
@FunctionalInterface
@VisibleForTesting
interface SystemClock {
+ // TODO: Add elapsedRealtime to this interface
long uptimeMillis();
}
@@ -198,6 +208,8 @@
mConnectivityModuleConnector = connectivityModuleConnector;
mSystemClock = clock;
mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
+ mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
loadFromFile();
sPackageWatchdog = this;
}
@@ -411,6 +423,35 @@
}
}
+ /**
+ * Called when the system server boots. If the system server is detected to be in a boot loop,
+ * query each observer and perform the mitigation action with the lowest user impact.
+ */
+ public void noteBoot() {
+ synchronized (mLock) {
+ if (mBootThreshold.incrementAndTest()) {
+ mBootThreshold.reset();
+ PackageHealthObserver currentObserverToNotify = null;
+ int currentObserverImpact = Integer.MAX_VALUE;
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ final ObserverInternal observer = mAllObservers.valueAt(i);
+ PackageHealthObserver registeredObserver = observer.registeredObserver;
+ if (registeredObserver != null) {
+ int impact = registeredObserver.onBootLoop();
+ if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+ && impact < currentObserverImpact) {
+ currentObserverToNotify = registeredObserver;
+ currentObserverImpact = impact;
+ }
+ }
+ }
+ if (currentObserverToNotify != null) {
+ currentObserverToNotify.executeBootLoopMitigation();
+ }
+ }
+ }
+ }
+
// TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
// avoid holding lock?
// This currently adds about 7ms extra to shutdown thread
@@ -519,6 +560,22 @@
boolean execute(@Nullable VersionedPackage versionedPackage,
@FailureReasons int failureReason);
+
+ /**
+ * Called when the system server has booted several times within a window of time, defined
+ * by {@link #mBootThreshold}
+ */
+ default @PackageHealthObserverImpact int onBootLoop() {
+ return PackageHealthObserverImpact.USER_IMPACT_NONE;
+ }
+
+ /**
+ * Executes mitigation for {@link #onBootLoop}
+ */
+ default boolean executeBootLoopMitigation() {
+ return false;
+ }
+
// TODO(b/120598832): Ensure uniqueness?
/**
* Identifier for the observer, should not change across device updates otherwise the
@@ -1367,4 +1424,62 @@
return value > 0 ? value : Long.MAX_VALUE;
}
}
+
+ /**
+ * Handles the thresholding logic for system server boots.
+ */
+ static class BootThreshold {
+
+ private final int mBootTriggerCount;
+ private final long mTriggerWindow;
+
+ BootThreshold(int bootTriggerCount, long triggerWindow) {
+ this.mBootTriggerCount = bootTriggerCount;
+ this.mTriggerWindow = triggerWindow;
+ }
+
+ public void reset() {
+ setStart(0);
+ setCount(0);
+ }
+
+ private int getCount() {
+ return SystemProperties.getInt(PROP_RESCUE_BOOT_COUNT, 0);
+ }
+
+ private void setCount(int count) {
+ SystemProperties.set(PROP_RESCUE_BOOT_COUNT, Integer.toString(count));
+ }
+
+ public long getStart() {
+ return SystemProperties.getLong(PROP_RESCUE_BOOT_START, 0);
+ }
+
+ public void setStart(long start) {
+ final long now = android.os.SystemClock.elapsedRealtime();
+ final long newStart = MathUtils.constrain(start, 0, now);
+ SystemProperties.set(PROP_RESCUE_BOOT_START, Long.toString(newStart));
+ }
+
+ /** Increments the boot counter, and returns whether the device is bootlooping. */
+ public boolean incrementAndTest() {
+ final long now = android.os.SystemClock.elapsedRealtime();
+ if (now - getStart() < 0) {
+ Slog.e(TAG, "Window was less than zero. Resetting start to current time.");
+ setStart(now);
+ }
+ final long window = now - getStart();
+ if (window >= mTriggerWindow) {
+ setCount(1);
+ setStart(now);
+ return false;
+ } else {
+ int count = getCount() + 1;
+ setCount(count);
+ EventLogTags.writeRescueNote(Process.ROOT_UID, count, window);
+ return count >= mBootTriggerCount;
+ }
+ }
+
+ }
}
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 3dafc64..e8e3b39d 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -27,17 +27,16 @@
import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
+import android.os.Process;
import android.os.RecoverySystem;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
-import android.text.format.DateUtils;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
@@ -80,12 +79,6 @@
static final int LEVEL_FACTORY_RESET = 4;
@VisibleForTesting
static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
- /**
- * The boot trigger window size must always be greater than Watchdog's deadlock timeout
- * {@link Watchdog#DEFAULT_TIMEOUT}.
- */
- @VisibleForTesting
- static final long BOOT_TRIGGER_WINDOW_MILLIS = 600 * DateUtils.SECOND_IN_MILLIS;
@VisibleForTesting
static final String TAG = "RescueParty";
@@ -93,18 +86,11 @@
private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
- private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
-
- /** Threshold for boot loops */
- private static final Threshold sBoot = new BootThreshold();
- /** Threshold for app crash loops */
- private static SparseArray<Threshold> sApps = new SparseArray<>();
-
/** Register the Rescue Party observer as a Package Watchdog health observer */
public static void registerHealthObserver(Context context) {
PackageWatchdog.getInstance(context).registerHealthObserver(
@@ -141,19 +127,6 @@
}
/**
- * Take note of a boot event. If we notice too many of these events
- * happening in rapid succession, we'll send out a rescue party.
- */
- public static void noteBoot(Context context) {
- if (isDisabled()) return;
- if (sBoot.incrementAndTest()) {
- sBoot.reset();
- incrementRescueLevel(sBoot.uid);
- executeRescueLevel(context);
- }
- }
-
- /**
* Check if we're currently attempting to reboot for a factory reset.
*/
public static boolean isAttemptingFactoryReset() {
@@ -170,11 +143,6 @@
}
@VisibleForTesting
- static void resetAllThresholds() {
- sBoot.reset();
- }
-
- @VisibleForTesting
static long getElapsedRealtime() {
return SystemClock.elapsedRealtime();
}
@@ -187,6 +155,14 @@
}
/**
+ * Get the current rescue level.
+ */
+ private static int getRescueLevel() {
+ return MathUtils.constrain(SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE),
+ LEVEL_NONE, LEVEL_FACTORY_RESET);
+ }
+
+ /**
* Escalate to the next rescue level. After incrementing the level you'll
* probably want to call {@link #executeRescueLevel(Context)}.
*/
@@ -366,90 +342,29 @@
}
@Override
+ public int onBootLoop() {
+ if (isDisabled()) {
+ return PackageHealthObserverImpact.USER_IMPACT_NONE;
+ }
+ return mapRescueLevelToUserImpact(getRescueLevel());
+ }
+
+ @Override
+ public boolean executeBootLoopMitigation() {
+ if (isDisabled()) {
+ return false;
+ }
+ incrementRescueLevel(Process.ROOT_UID);
+ executeRescueLevel(mContext);
+ return true;
+ }
+
+ @Override
public String getName() {
return NAME;
}
}
- /**
- * Threshold that can be triggered if a number of events occur within a
- * window of time.
- */
- private abstract static class Threshold {
- public abstract int getCount();
- public abstract void setCount(int count);
- public abstract long getStart();
- public abstract void setStart(long start);
-
- private final int uid;
- private final int triggerCount;
- private final long triggerWindow;
-
- public Threshold(int uid, int triggerCount, long triggerWindow) {
- this.uid = uid;
- this.triggerCount = triggerCount;
- this.triggerWindow = triggerWindow;
- }
-
- public void reset() {
- setCount(0);
- setStart(0);
- }
-
- /**
- * @return if this threshold has been triggered
- */
- public boolean incrementAndTest() {
- final long now = getElapsedRealtime();
- final long window = now - getStart();
- if (window > triggerWindow) {
- setCount(1);
- setStart(now);
- return false;
- } else {
- int count = getCount() + 1;
- setCount(count);
- EventLogTags.writeRescueNote(uid, count, window);
- Slog.w(TAG, "Noticed " + count + " events for UID " + uid + " in last "
- + (window / 1000) + " sec");
- return (count >= triggerCount);
- }
- }
- }
-
- /**
- * Specialization of {@link Threshold} for monitoring boot events. It stores
- * counters in system properties for robustness.
- */
- private static class BootThreshold extends Threshold {
- public BootThreshold() {
- // We're interested in TRIGGER_COUNT events in any
- // BOOT_TRIGGER_WINDOW_MILLIS second period; this window is super relaxed because
- // booting can take a long time if forced to dexopt things.
- super(android.os.Process.ROOT_UID, TRIGGER_COUNT, BOOT_TRIGGER_WINDOW_MILLIS);
- }
-
- @Override
- public int getCount() {
- return SystemProperties.getInt(PROP_RESCUE_BOOT_COUNT, 0);
- }
-
- @Override
- public void setCount(int count) {
- SystemProperties.set(PROP_RESCUE_BOOT_COUNT, Integer.toString(count));
- }
-
- @Override
- public long getStart() {
- return SystemProperties.getLong(PROP_RESCUE_BOOT_START, 0);
- }
-
- @Override
- public void setStart(long start) {
- SystemProperties.set(PROP_RESCUE_BOOT_START, Long.toString(start));
- }
- }
-
private static int[] getAllUserIds() {
int[] userIds = { UserHandle.USER_SYSTEM };
try {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 396b977..bcc3bdb 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -44,6 +44,7 @@
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
+import static com.android.server.storage.StorageUserConnection.REMOTE_TIMEOUT_SECONDS;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -1985,16 +1986,28 @@
Slog.i(TAG, "Mounting volume " + vol);
mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
@Override
- public boolean onVolumeChecking(FileDescriptor deviceFd, String path,
+ public boolean onVolumeChecking(FileDescriptor fd, String path,
String internalPath) {
vol.path = path;
vol.internalPath = internalPath;
+ ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd);
try {
- mStorageSessionController.onVolumeMount(deviceFd, vol);
+ mStorageSessionController.onVolumeMount(pfd, vol);
return true;
} catch (ExternalStorageServiceException e) {
- Slog.i(TAG, "Failed to mount volume " + vol, e);
+ Slog.e(TAG, "Failed to mount volume " + vol, e);
+
+ Slog.i(TAG, "Scheduling reset in one minute");
+ mHandler.removeMessages(H_RESET);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET),
+ TimeUnit.SECONDS.toMillis(REMOTE_TIMEOUT_SECONDS * 2));
return false;
+ } finally {
+ try {
+ pfd.close();
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to close FUSE device fd", e);
+ }
}
}
});
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index f46b9ae..b1584fe 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -21,6 +21,9 @@
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;
@@ -62,7 +65,7 @@
*
* {@hide}
*/
-//@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
+@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
public abstract class SystemService {
/** @hide */
@@ -129,7 +132,7 @@
* Class representing user in question in the lifecycle callbacks.
* @hide
*/
- //@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
+ @SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
public static final class TargetUser {
@NonNull
private final UserInfo mUserInfo;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 2b7745b..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;
@@ -254,6 +255,8 @@
private int[] mCallPreciseDisconnectCause;
+ private List<BarringInfo> mBarringInfo = null;
+
private boolean mCarrierNetworkChangeState = false;
private PhoneCapability mPhoneCapability = null;
@@ -436,6 +439,7 @@
cutListToSize(mCellInfo, mNumPhones);
cutListToSize(mImsReasonInfo, mNumPhones);
cutListToSize(mPreciseDataConnectionStates, mNumPhones);
+ cutListToSize(mBarringInfo, mNumPhones);
return;
}
@@ -467,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());
}
}
@@ -524,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;
@@ -551,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);
@@ -993,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 {
@@ -2102,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, " ");
@@ -2142,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);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 37026fd..a98b83b 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -402,7 +402,7 @@
}
public ParcelFileDescriptor getStatisticsStream() {
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BATTERY_STATS, null);
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
index 17a4b9c..fffe7d9 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;
}
}
@@ -187,6 +185,10 @@
&& tmpDir.renameTo(mStagingDir))) {
throw new IOException("Error switching staging/rules directory");
}
+
+ for (File file : mStagingDir.listFiles()) {
+ file.delete();
+ }
}
}
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 da778aa..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,6 +16,8 @@
package com.android.server.integrity.model;
+import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS;
+
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
@@ -24,7 +26,6 @@
public class BitOutputStream {
private static final int BUFFER_SIZE = 4 * 1024;
- private static final int BYTE_BITS = 8;
private int mNextBitIndex;
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 4bf8fe8..0000000
--- a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java
+++ /dev/null
@@ -1,76 +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 int mReadBitsCount;
-
- /** Constructor with byte array. */
- public BitTrackedInputStream(byte[] inputStream) {
- super(inputStream);
- mReadBitsCount = 0;
- }
-
- /** Constructor with input stream. */
- public BitTrackedInputStream(InputStream inputStream) {
- super(inputStream);
- mReadBitsCount = 0;
- }
-
- /** Obtains an integer value of the next {@code numOfBits}. */
- @Override
- public int getNext(int numOfBits) throws IOException {
- mReadBitsCount += numOfBits;
- return super.getNext(numOfBits);
- }
-
- /** Returns the current cursor position showing the number of bits that are read. */
- public int getReadBitsCount() {
- return mReadBitsCount;
- }
-
- /**
- * Returns true if we can read more rules by checking whether the end index is not reached yet.
- */
- public boolean canReadMoreRules(int endIndexBytes) {
- return mReadBitsCount < endIndexBytes * 8;
- }
-
- /**
- * 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 - mReadBitsCount;
- if (bitCountToRead < 0) {
- throw new IllegalStateException("The byte position is already read.");
- }
- super.getNext(bitCountToRead);
- mReadBitsCount = 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 0d6807a..ceed054 100644
--- a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
+++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
@@ -27,8 +27,6 @@
*/
public class ByteTrackedOutputStream extends OutputStream {
- private static final int INT_BYTES = 4;
-
private int mWrittenBytesCount;
private final OutputStream mOutputStream;
@@ -39,7 +37,7 @@
@Override
public void write(int b) throws IOException {
- mWrittenBytesCount += INT_BYTES;
+ mWrittenBytesCount++;
mOutputStream.write(b);
}
@@ -49,8 +47,7 @@
*/
@Override
public void write(byte[] bytes) throws IOException {
- mWrittenBytesCount += bytes.length;
- mOutputStream.write(bytes);
+ write(bytes, 0, bytes.length);
}
@Override
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..0c4052a 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 50 is in the
+ // optimal range of efficient computation.
+ public static final int INDEXING_BLOCK_SIZE = 50;
public static final String START_INDEXING_KEY = "START_KEY";
public static final String END_INDEXING_KEY = "END_KEY";
diff --git a/services/core/java/com/android/server/integrity/parser/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 2f28563..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,18 +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.canReadMoreRules(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));
}
}
}
@@ -115,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:
@@ -141,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 453fa5d..595a035 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
@@ -47,4 +47,9 @@
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 03392ab..87eee4e 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
@@ -28,9 +28,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.TreeSet;
import java.util.stream.Collectors;
/** Helper class to identify the necessary indexes that needs to be read. */
@@ -93,19 +93,28 @@
return keyToIndexMap;
}
- private RuleIndexRange searchIndexingKeysRangeContainingKey(
+ private static RuleIndexRange searchIndexingKeysRangeContainingKey(
LinkedHashMap<String, Integer> indexMap, String searchedKey) {
- TreeSet<String> keyTreeSet =
- indexMap.keySet().stream()
- .filter(key -> !key.matches(START_INDEXING_KEY) && !key.matches(
- END_INDEXING_KEY))
- .collect(Collectors.toCollection(TreeSet::new));
-
- String minIndex = keyTreeSet.floor(searchedKey);
- String maxIndex = keyTreeSet.higher(searchedKey);
-
+ List<String> keys = indexMap.keySet().stream().collect(Collectors.toList());
+ List<String> identifiedKeyRange =
+ searchKeysRangeContainingKey(keys, searchedKey, 0, keys.size() - 1);
return new RuleIndexRange(
- indexMap.get(minIndex == null ? START_INDEXING_KEY : minIndex),
- indexMap.get(maxIndex == null ? END_INDEXING_KEY : maxIndex));
+ indexMap.get(identifiedKeyRange.get(0)), indexMap.get(identifiedKeyRange.get(1)));
+ }
+
+ private static List<String> searchKeysRangeContainingKey(
+ List<String> sortedKeyList, String key, int startIndex, int endIndex) {
+ if (endIndex - startIndex == 1) {
+ return Arrays.asList(sortedKeyList.get(startIndex), sortedKeyList.get(endIndex));
+ }
+
+ int midKeyIndex = startIndex + ((endIndex - startIndex) / 2);
+ String midKey = sortedKeyList.get(midKeyIndex);
+
+ if (key.compareTo(midKey) >= 0) {
+ return searchKeysRangeContainingKey(sortedKeyList, key, midKeyIndex, endIndex);
+ } else {
+ return searchKeysRangeContainingKey(sortedKeyList, key, startIndex, midKeyIndex);
+ }
}
}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java
index 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/location/UserInfoStore.java b/services/core/java/com/android/server/location/UserInfoStore.java
index 550f51c..f282ed2 100644
--- a/services/core/java/com/android/server/location/UserInfoStore.java
+++ b/services/core/java/com/android/server/location/UserInfoStore.java
@@ -24,6 +24,7 @@
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;
@@ -152,7 +153,7 @@
// this intent is only sent to the current user
if (mCachedParentUserId == mCurrentUserId) {
mCachedParentUserId = UserHandle.USER_NULL;
- mCachedProfileUserIds = null;
+ mCachedProfileUserIds = new int[]{UserHandle.USER_NULL};
}
}
@@ -185,16 +186,21 @@
} else {
Preconditions.checkState(mUserManager != null);
- UserInfo userInfo = mUserManager.getProfileParent(userId);
- if (userInfo != null) {
- parentUserId = userInfo.id;
- } else {
- // getProfileParent() returns null if the userId is already the parent...
- parentUserId = userId;
- }
+ 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);
+ // force profiles into cache
+ getProfileUserIdsForParentUser(parentUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
return parentUserId;
@@ -204,13 +210,24 @@
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) {
- Preconditions.checkArgument(mUserManager.getProfileParent(parentUserId) == null);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ Preconditions.checkArgument(mUserManager.getProfileParent(parentUserId) == null);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
if (parentUserId != mCachedParentUserId) {
- mCachedParentUserId = parentUserId;
- mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mCachedParentUserId = parentUserId;
+ mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
return mCachedProfileUserIds;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 0f8561e..4943c25 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -24,6 +24,7 @@
import android.app.ActivityManager;
import android.app.admin.PasswordMetrics;
import android.os.ShellCommand;
+import android.text.TextUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
@@ -195,6 +196,9 @@
}
private LockscreenCredential getOldCredential() {
+ if (TextUtils.isEmpty(mOld)) {
+ return LockscreenCredential.createNone();
+ }
if (mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId)) {
final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(mCurrentUserId);
if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
@@ -202,12 +206,15 @@
} else {
return LockscreenCredential.createPin(mOld);
}
- } else if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) {
+ }
+ if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) {
return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
mOld.getBytes()));
- } else {
- return LockscreenCredential.createNone();
}
+ // User supplied some old credential but the device has neither password nor pattern,
+ // so just return a password credential (and let it be rejected during LSS verification)
+ return LockscreenCredential.createPassword(mOld);
+
}
private boolean runSetPattern() {
diff --git a/services/core/java/com/android/server/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/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index d940e35..161afb5 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -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 {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index b7aa484..c80a898 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -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/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/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 3e76096..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--) {
@@ -632,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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 410017a..d0f91c2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1534,7 +1534,7 @@
final @Nullable String mAppPredictionServicePackage;
final @Nullable String mIncidentReportApproverPackage;
final @Nullable String[] mTelephonyPackages;
- final @NonNull String mServicesSystemSharedLibraryPackageName;
+ final @NonNull String mServicesExtensionPackageName;
final @NonNull String mSharedSystemSharedLibraryPackageName;
private final PackageUsage mPackageUsage = new PackageUsage();
@@ -3303,9 +3303,7 @@
} else {
mIntentFilterVerifier = null;
}
- mServicesSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
- PackageManager.SYSTEM_SHARED_LIBRARY_SERVICES,
- SharedLibraryInfo.VERSION_UNDEFINED);
+ mServicesExtensionPackageName = getRequiredServicesExtensionPackageLPr();
mSharedSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
PackageManager.SYSTEM_SHARED_LIBRARY_SHARED,
SharedLibraryInfo.VERSION_UNDEFINED);
@@ -3315,7 +3313,7 @@
mRequiredUninstallerPackage = null;
mIntentFilterVerifierComponent = null;
mIntentFilterVerifier = null;
- mServicesSystemSharedLibraryPackageName = null;
+ mServicesExtensionPackageName = null;
mSharedSystemSharedLibraryPackageName = null;
}
// PermissionController hosts default permission granting and role management, so it's a
@@ -3745,6 +3743,19 @@
}
}
+ @NonNull
+ private String getRequiredServicesExtensionPackageLPr() {
+ String servicesExtensionPackage =
+ ensureSystemPackageName(
+ mContext.getString(R.string.config_servicesExtensionPackage));
+ if (TextUtils.isEmpty(servicesExtensionPackage)) {
+ throw new RuntimeException(
+ "Required services extension package is missing, check "
+ + "config_servicesExtensionPackage.");
+ }
+ return servicesExtensionPackage;
+ }
+
private @NonNull String getRequiredInstallerLPr() {
final Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
@@ -5467,7 +5478,7 @@
public @NonNull String getServicesSystemSharedLibraryPackageName() {
// allow instant applications
synchronized (mLock) {
- return mServicesSystemSharedLibraryPackageName;
+ return mServicesExtensionPackageName;
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 6ee8648..66a2b01 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3395,6 +3395,9 @@
private void dispatchUserAddedIntent(@NonNull UserInfo userInfo) {
Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
+ // Also, add the UserHandle for mainline modules which can't use the @hide
+ // EXTRA_USER_HANDLE.
+ addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userInfo.id));
mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
android.Manifest.permission.MANAGE_USERS);
MetricsLogger.count(mContext, userInfo.isGuest() ? TRON_GUEST_CREATED
@@ -3678,9 +3681,12 @@
// wiping the user's system directory and removing from the user list
long ident = Binder.clearCallingIdentity();
try {
- Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
- addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- mContext.sendOrderedBroadcastAsUser(addedIntent, UserHandle.ALL,
+ Intent removedIntent = new Intent(Intent.ACTION_USER_REMOVED);
+ removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ // Also, add the UserHandle for mainline modules which can't use the @hide
+ // EXTRA_USER_HANDLE.
+ removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
+ mContext.sendOrderedBroadcastAsUser(removedIntent, UserHandle.ALL,
android.Manifest.permission.MANAGE_USERS,
new BroadcastReceiver() {
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/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 7d0072a..5c0dd9a 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -360,7 +360,7 @@
*/
boolean enableForPackageInApex(String packageName, long installedVersion,
int rollbackDataPolicy) {
- // TODO(b/142712057): Extract the new version number of apk-in-apex
+ // TODO(b/147666157): Extract the new version number of apk-in-apex
// The new version for the apk-in-apex is set to 0 for now. If the package is then further
// updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced()
// will be called and this rollback will be deleted. Other ways of package update have not
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index eefcde6..de48939 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -43,6 +43,7 @@
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.Process;
import android.os.SystemClock;
@@ -78,6 +79,7 @@
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -134,6 +136,7 @@
private final Context mContext;
private final HandlerThread mHandlerThread;
+ private final Executor mExecutor;
private final Installer mInstaller;
private final RollbackPackageHealthObserver mPackageHealthObserver;
private final AppDataRollbackHelper mAppDataRollbackHelper;
@@ -173,6 +176,7 @@
mHandlerThread = new HandlerThread("RollbackManagerServiceHandler");
mHandlerThread.start();
Watchdog.getInstance().addThread(getHandler(), HANDLER_THREAD_TIMEOUT_DURATION_MILLIS);
+ mExecutor = new HandlerExecutor(getHandler());
for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) {
registerUserCallbacks(userInfo.getUserHandle());
@@ -409,7 +413,6 @@
CountDownLatch latch = new CountDownLatch(1);
getHandler().post(() -> {
- updateRollbackLifetimeDurationInMillis();
synchronized (mLock) {
mRollbacks.clear();
mRollbacks.addAll(mRollbackStore.loadRollbacks());
@@ -520,11 +523,13 @@
@AnyThread
void onBootCompleted() {
- getHandler().post(() -> updateRollbackLifetimeDurationInMillis());
- // Also posts to handler thread
- scheduleExpiration(0);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+ mExecutor, properties -> updateRollbackLifetimeDurationInMillis());
getHandler().post(() -> {
+ updateRollbackLifetimeDurationInMillis();
+ runExpiration();
+
// Check to see if any rollback-enabled staged sessions or staged
// rollback sessions been applied.
List<Rollback> enabling = new ArrayList<>();
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index b9ef7b3..6686de9 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -179,10 +179,10 @@
// Use the version of the metadata package that was installed before
// we rolled back for logging purposes.
- VersionedPackage oldModuleMetadataPackage = null;
+ VersionedPackage oldLogPackage = null;
for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
if (packageRollback.getPackageName().equals(moduleMetadataPackageName)) {
- oldModuleMetadataPackage = packageRollback.getVersionRolledBackFrom();
+ oldLogPackage = packageRollback.getVersionRolledBackFrom();
break;
}
}
@@ -194,13 +194,13 @@
return;
}
if (sessionInfo.isStagedSessionApplied()) {
- logEvent(oldModuleMetadataPackage,
+ logEvent(oldLogPackage,
StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
} else if (sessionInfo.isStagedSessionReady()) {
// TODO: What do for staged session ready but not applied
} else {
- logEvent(oldModuleMetadataPackage,
+ logEvent(oldLogPackage,
StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
}
@@ -213,6 +213,23 @@
if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
return rollback;
}
+ // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
+ // to rely on complicated reasoning as below
+
+ // Due to b/147666157, for apk in apex, we do not know the version we are rolling
+ // back from. But if a package X is embedded in apex A exclusively (not embedded in
+ // any other apex), which is not guaranteed, then it is sufficient to check only
+ // package names here, as the version of failedPackage and the PackageRollbackInfo
+ // can't be different. If failedPackage has a higher version, then it must have
+ // been updated somehow. There are two ways: it was updated by an update of apex A
+ // or updated directly as apk. In both cases, this rollback would have gotten
+ // expired when onPackageReplaced() was called. Since the rollback exists, it has
+ // same version as failedPackage.
+ if (packageRollback.isApkInApex()
+ && packageRollback.getVersionRolledBackFrom().getPackageName()
+ .equals(failedPackage.getPackageName())) {
+ return rollback;
+ }
}
}
return null;
@@ -245,12 +262,12 @@
}
private BroadcastReceiver listenForStagedSessionReady(RollbackManager rollbackManager,
- int rollbackId, @Nullable VersionedPackage moduleMetadataPackage) {
+ int rollbackId, @Nullable VersionedPackage logPackage) {
BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
handleStagedSessionChange(rollbackManager,
- rollbackId, this /* BroadcastReceiver */, moduleMetadataPackage);
+ rollbackId, this /* BroadcastReceiver */, logPackage);
}
};
IntentFilter sessionUpdatedFilter =
@@ -260,7 +277,7 @@
}
private void handleStagedSessionChange(RollbackManager rollbackManager, int rollbackId,
- BroadcastReceiver listener, @Nullable VersionedPackage moduleMetadataPackage) {
+ BroadcastReceiver listener, @Nullable VersionedPackage logPackage) {
PackageInstaller packageInstaller =
mContext.getPackageManager().getPackageInstaller();
List<RollbackInfo> recentRollbacks =
@@ -274,15 +291,19 @@
packageInstaller.getSessionInfo(sessionId);
if (sessionInfo.isStagedSessionReady() && markStagedSessionHandled(rollbackId)) {
mContext.unregisterReceiver(listener);
- saveLastStagedRollbackId(rollbackId);
- logEvent(moduleMetadataPackage,
+ if (logPackage != null) {
+ // We save the rollback id so that after reboot, we can log if rollback was
+ // successful or not. If logPackage is null, then there is nothing to log.
+ saveLastStagedRollbackId(rollbackId);
+ }
+ logEvent(logPackage,
StatsLog
.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
"");
} else if (sessionInfo.isStagedSessionFailed()
&& markStagedSessionHandled(rollbackId)) {
- logEvent(moduleMetadataPackage,
+ logEvent(logPackage,
StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
"");
@@ -362,12 +383,12 @@
}
}
- private static void logEvent(@Nullable VersionedPackage moduleMetadataPackage, int type,
+ private static void logEvent(@Nullable VersionedPackage logPackage, int type,
int rollbackReason, @NonNull String failingPackageName) {
Slog.i(TAG, "Watchdog event occurred of type: " + rollbackTypeToString(type));
- if (moduleMetadataPackage != null) {
- StatsLog.logWatchdogRollbackOccurred(type, moduleMetadataPackage.getPackageName(),
- moduleMetadataPackage.getVersionCode(), rollbackReason, failingPackageName);
+ if (logPackage != null) {
+ StatsLog.logWatchdogRollbackOccurred(type, logPackage.getPackageName(),
+ logPackage.getVersionCode(), rollbackReason, failingPackageName);
}
}
diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java
index 1f7ed8a..f78330e 100644
--- a/services/core/java/com/android/server/stats/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/StatsPullAtomService.java
@@ -19,6 +19,7 @@
import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.os.Debug.getIonHeapsSizeKb;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.os.Process.getUidForPid;
import static android.os.storage.VolumeInfo.TYPE_PRIVATE;
@@ -210,6 +211,8 @@
@Override
public void onStart() {
mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER);
+ mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ mTelephony = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
// Used to initialize the CPU Frequency atom.
PowerProfile powerProfile = new PowerProfile(mContext);
@@ -263,6 +266,7 @@
registerProcessMemoryHighWaterMark();
registerProcessMemorySnapshot();
registerSystemIonHeapSize();
+ registerIonHeapSize();
registerProcessSystemIonHeapSize();
registerTemperature();
registerCoolingDevice();
@@ -812,19 +816,96 @@
}
private void registerWifiActivityInfo() {
- // No op.
+ int tagId = StatsLog.WIFI_ACTIVITY_INFO;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullWifiActivityInfo(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
}
- private void pullWifiActivityInfo() {
- // No op.
+ private WifiManager mWifiManager;
+ private TelephonyManager mTelephony;
+
+ private int pullWifiActivityInfo(int atomTag, List<StatsEvent> pulledData) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi");
+ mWifiManager.getWifiActivityEnergyInfoAsync(
+ new Executor() {
+ @Override
+ public void execute(Runnable runnable) {
+ // run the listener on the binder thread, if it was run on the main
+ // thread it would deadlock since we would be waiting on ourselves
+ runnable.run();
+ }
+ },
+ info -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info);
+ wifiReceiver.send(0, bundle);
+ }
+ );
+ final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
+ if (wifiInfo == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeLong(wifiInfo.getTimeSinceBootMillis())
+ .writeInt(wifiInfo.getStackState())
+ .writeLong(wifiInfo.getControllerTxDurationMillis())
+ .writeLong(wifiInfo.getControllerRxDurationMillis())
+ .writeLong(wifiInfo.getControllerIdleDurationMillis())
+ .writeLong(wifiInfo.getControllerEnergyUsedMicroJoules())
+ .build();
+ pulledData.add(e);
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "failed to getWifiActivityEnergyInfoAsync", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
}
private void registerModemActivityInfo() {
- // No op.
+ int tagId = StatsLog.MODEM_ACTIVITY_INFO;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullModemActivityInfo(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
}
- private void pullModemActivityInfo() {
- // No op.
+ private int pullModemActivityInfo(int atomTag, List<StatsEvent> pulledData) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony");
+ mTelephony.requestModemActivityInfo(modemReceiver);
+ final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
+ if (modemInfo == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeLong(modemInfo.getTimestamp())
+ .writeLong(modemInfo.getSleepTimeMillis())
+ .writeLong(modemInfo.getIdleTimeMillis())
+ .writeLong(modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis())
+ .writeLong(modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis())
+ .writeLong(modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis())
+ .writeLong(modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis())
+ .writeLong(modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis())
+ .writeLong(modemInfo.getReceiveTimeMillis())
+ .build();
+ pulledData.add(e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
}
private void registerBluetoothActivityInfo() {
@@ -954,6 +1035,26 @@
// No op.
}
+ private void registerIonHeapSize() {
+ int tagId = StatsLog.ION_HEAP_SIZE;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ /* PullAtomMetadata */ null,
+ (atomTag, data) -> pullIonHeapSize(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullIonHeapSize(int atomTag, List<StatsEvent> pulledData) {
+ int ionHeapSizeInKilobytes = (int) getIonHeapsSizeKb();
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(ionHeapSizeInKilobytes)
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
+ }
+
private void registerProcessSystemIonHeapSize() {
// No op.
}
@@ -1113,28 +1214,119 @@
// No op.
}
- private void registerDeviceCalculatedPowerUse() {
- // No op.
+ // TODO: move to top of file when all migrations are complete
+ private BatteryStatsHelper mBatteryStatsHelper = null;
+ private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000;
+ private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS;
+ private static final long MILLI_AMP_HR_TO_NANO_AMP_SECS = 1_000_000L * 3600L;
+
+ private BatteryStatsHelper getBatteryStatsHelper() {
+ if (mBatteryStatsHelper == null) {
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ // clearCallingIdentity required for BatteryStatsHelper.checkWifiOnly().
+ mBatteryStatsHelper = new BatteryStatsHelper(mContext, false);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ mBatteryStatsHelper.create((Bundle) null);
+ }
+ long currentTime = SystemClock.elapsedRealtime();
+ if (currentTime - mBatteryStatsHelperTimestampMs >= MAX_BATTERY_STATS_HELPER_FREQUENCY_MS) {
+ // Load BatteryStats and do all the calculations.
+ mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+ // Calculations are done so we don't need to save the raw BatteryStats data in RAM.
+ mBatteryStatsHelper.clearStats();
+ mBatteryStatsHelperTimestampMs = currentTime;
+ }
+ return mBatteryStatsHelper;
}
- private void pullDeviceCalculatedPowerUse() {
- // No op.
+ private long milliAmpHrsToNanoAmpSecs(double mAh) {
+ return (long) (mAh * MILLI_AMP_HR_TO_NANO_AMP_SECS + 0.5);
+ }
+
+ private void registerDeviceCalculatedPowerUse() {
+ int tagId = StatsLog.DEVICE_CALCULATED_POWER_USE;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullDeviceCalculatedPowerUse(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
+ }
+
+ private int pullDeviceCalculatedPowerUse(int atomTag, List<StatsEvent> pulledData) {
+ BatteryStatsHelper bsHelper = getBatteryStatsHelper();
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeLong(milliAmpHrsToNanoAmpSecs(bsHelper.getComputedPower()))
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
}
private void registerDeviceCalculatedPowerBlameUid() {
- // No op.
+ int tagId = StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullDeviceCalculatedPowerBlameUid(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
}
- private void pullDeviceCalculatedPowerBlameUid() {
- // No op.
+ private int pullDeviceCalculatedPowerBlameUid(int atomTag, List<StatsEvent> pulledData) {
+ final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+ if (sippers == null) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ for (BatterySipper bs : sippers) {
+ if (bs.drainType != bs.drainType.APP) {
+ continue;
+ }
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(bs.uidObj.getUid())
+ .writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah))
+ .build();
+ pulledData.add(e);
+ }
+ return StatsManager.PULL_SUCCESS;
}
private void registerDeviceCalculatedPowerBlameOther() {
- // No op.
+ int tagId = StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullDeviceCalculatedPowerBlameOther(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
}
- private void pullDeviceCalculatedPowerBlameOther() {
- // No op.
+ private int pullDeviceCalculatedPowerBlameOther(int atomTag, List<StatsEvent> pulledData) {
+ final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+ if (sippers == null) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ for (BatterySipper bs : sippers) {
+ if (bs.drainType == bs.drainType.APP) {
+ continue; // This is a separate atom; see pullDeviceCalculatedPowerBlameUid().
+ }
+ if (bs.drainType == bs.drainType.USER) {
+ continue; // This is not supported. We purposefully calculate over USER_ALL.
+ }
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(bs.drainType.ordinal())
+ .writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah))
+ .build();
+ pulledData.add(e);
+ }
+ return StatsManager.PULL_SUCCESS;
}
private void registerDebugElapsedClock() {
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index f4fb93a..baef5d6 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -40,9 +40,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
-import java.io.FileDescriptor;
import java.util.Objects;
/**
@@ -82,7 +80,7 @@
* @throws ExternalStorageServiceException if the session fails to start
* @throws IllegalStateException if a session has already been created for {@code vol}
*/
- public void onVolumeMount(FileDescriptor deviceFd, VolumeInfo vol)
+ public void onVolumeMount(ParcelFileDescriptor deviceFd, VolumeInfo vol)
throws ExternalStorageServiceException {
if (!shouldHandle(vol)) {
return;
@@ -102,8 +100,8 @@
mConnections.put(userId, connection);
}
Slog.i(TAG, "Creating and starting session with id: " + sessionId);
- connection.startSession(sessionId, new ParcelFileDescriptor(deviceFd),
- vol.getPath().getPath(), vol.getInternalPath().getPath());
+ connection.startSession(sessionId, deviceFd, vol.getPath().getPath(),
+ vol.getInternalPath().getPath());
}
}
@@ -185,7 +183,7 @@
* This call removes all sessions for the user that is being stopped;
* this will make sure that we don't rebind to the service needlessly.
*/
- public void onUserStopping(int userId) throws ExternalStorageServiceException {
+ public void onUserStopping(int userId) {
if (!shouldHandle(null)) {
return;
}
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index c02ded8..dd18f4e 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -60,7 +60,8 @@
*/
public final class StorageUserConnection {
private static final String TAG = "StorageUserConnection";
- private static final int REMOTE_TIMEOUT_SECONDS = 15;
+
+ public static final int REMOTE_TIMEOUT_SECONDS = 5;
private final Object mLock = new Object();
private final Context mContext;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 19bc560..3f3408f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6363,6 +6363,14 @@
* aspect ratio.
*/
boolean shouldUseSizeCompatMode() {
+ if (inMultiWindowMode() || getWindowConfiguration().hasWindowDecorCaption()) {
+ final ActivityRecord root = task != null ? task.getRootActivity() : null;
+ if (root != null && root != this && !root.shouldUseSizeCompatMode()) {
+ // If the root activity doesn't use size compatibility mode, the activities above
+ // are forced to be the same for consistent visual appearance.
+ return false;
+ }
+ }
return !isResizeable() && (info.isFixedOrientation() || info.hasFixedAspectRatio())
// The configuration of non-standard type should be enforced by system.
&& isActivityTypeStandard()
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index e0a7b18..2cb7d5a 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -33,6 +33,27 @@
private final HighRefreshRateBlacklist mHighRefreshRateBlacklist;
private final WindowManagerService mWmService;
+ /**
+ * The following constants represent priority of the window. SF uses this information when
+ * deciding which window has a priority when deciding about the refresh rate of the screen.
+ * Priority 0 is considered the highest priority. -1 means that the priority is unset.
+ */
+ static final int LAYER_PRIORITY_UNSET = -1;
+ /** Windows that are in focus and voted for the preferred mode ID have the highest priority. */
+ static final int LAYER_PRIORITY_FOCUSED_WITH_MODE = 0;
+ /**
+ * This is a default priority for all windows that are in focus, but have not requested a
+ * specific mode ID.
+ */
+ static final int LAYER_PRIORITY_FOCUSED_WITHOUT_MODE = 1;
+ /**
+ * Windows that are not in focus, but voted for a specific mode ID should be
+ * acknowledged by SF. For example, there are two applications in a split screen.
+ * One voted for a given mode ID, and the second one doesn't care. Even though the
+ * second one might be in focus, we can honor the mode ID of the first one.
+ */
+ static final int LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE = 2;
+
RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
HighRefreshRateBlacklist blacklist) {
mLowRefreshRateId = findLowRefreshRateModeId(displayInfo);
@@ -92,4 +113,28 @@
}
return 0;
}
+
+ /**
+ * Calculate the priority based on whether the window is in focus and whether the application
+ * voted for a specific refresh rate.
+ *
+ * TODO(b/144307188): This is a very basic algorithm version. Explore other signals that might
+ * be useful in edge cases when we are deciding which layer should get priority when deciding
+ * about the refresh rate.
+ */
+ int calculatePriority(WindowState w) {
+ boolean isFocused = w.isFocused();
+ int preferredModeId = getPreferredModeId(w);
+
+ if (!isFocused && preferredModeId > 0) {
+ return LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE;
+ }
+ if (isFocused && preferredModeId == 0) {
+ return LAYER_PRIORITY_FOCUSED_WITHOUT_MODE;
+ }
+ if (isFocused && preferredModeId > 0) {
+ return LAYER_PRIORITY_FOCUSED_WITH_MODE;
+ }
+ return LAYER_PRIORITY_UNSET;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c2eb0e4..36e9273 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -657,6 +657,13 @@
private KeyInterceptionInfo mKeyInterceptionInfo;
/**
+ * This information is passed to SurfaceFlinger to decide which window should have a priority
+ * when deciding about the refresh rate of the display. All windows have the lowest priority by
+ * default. The variable is cached, so we do not send too many updates to SF.
+ */
+ int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET;
+
+ /**
* @return The insets state as requested by the client, i.e. the dispatched insets state
* for which the visibilities are overridden with what the client requested.
*/
@@ -5165,6 +5172,24 @@
}
}
+
+ /**
+ * Notifies SF about the priority of the window, if it changed. SF then uses this information
+ * to decide which window's desired rendering rate should have a priority when deciding about
+ * the refresh rate of the screen. Priority
+ * {@link RefreshRatePolicy#LAYER_PRIORITY_FOCUSED_WITH_MODE} is considered the highest.
+ */
+ @VisibleForTesting
+ void updateFrameRateSelectionPriorityIfNeeded() {
+ final int priority = getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+ .calculatePriority(this);
+ if (mFrameRateSelectionPriority != priority) {
+ mFrameRateSelectionPriority = priority;
+ getPendingTransaction().setFrameRateSelectionPriority(mSurfaceControl,
+ mFrameRateSelectionPriority);
+ }
+ }
+
@Override
void prepareSurfaces() {
final Dimmer dimmer = getDimmer();
@@ -5173,6 +5198,8 @@
applyDims(dimmer);
}
updateSurfacePosition();
+ // Send information to SufaceFlinger about the priority of the current window.
+ updateFrameRateSelectionPriorityIfNeeded();
mWinAnimator.prepareSurfaceLocked(true);
super.prepareSurfaces();
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
index 9353fbd..7644ade 100644
--- a/services/core/jni/com_android_server_GraphicsStatsService.cpp
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -178,15 +178,16 @@
}
// 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) {
+static status_pull_atom_return_t graphicsStatsPullCallback(int32_t atom_tag,
+ pulled_stats_event_list* data,
+ void* cookie) {
JNIEnv* env = getJNIEnv();
if (!env) {
return false;
}
if (gGraphicsStatsServiceObject == nullptr) {
ALOGE("Failed to get graphicsstats service");
- return false;
+ return STATS_PULL_SKIP;
}
for (bool lastFullDay : {true, false}) {
@@ -198,7 +199,7 @@
env->ExceptionDescribe();
env->ExceptionClear();
ALOGE("Failed to invoke graphicsstats service");
- return false;
+ return STATS_PULL_SKIP;
}
if (!jdata) {
// null means data is not available for that day.
@@ -217,7 +218,7 @@
if (!success) {
ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'",
serviceDump.InitializationErrorString().c_str(), dataSize);
- return false;
+ return STATS_PULL_SKIP;
}
for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
@@ -244,7 +245,7 @@
stats_event_build(event);
}
}
- return true;
+ return STATS_PULL_SUCCESS;
}
// Register a puller for GRAPHICS_STATS atom with the statsd service.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 485899e..b8b0dbf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1095,7 +1095,7 @@
String globalProxySpec = null;
String globalProxyExclusionList = null;
- ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>();
+ @NonNull ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>();
List<String> crossProfileWidgetProviders;
@@ -1650,6 +1650,7 @@
}
}
+ @NonNull
private ArrayMap<String, TrustAgentInfo> getAllTrustAgentInfos(
XmlPullParser parser, String tag) throws XmlPullParserException, IOException {
int outerDepthDAM = parser.getDepth();
@@ -2434,11 +2435,133 @@
migrateUserRestrictionsIfNecessaryLocked();
// TODO PO may not have a class name either due to b/17652534. Address that too.
-
updateDeviceOwnerLocked();
}
}
+ /**
+ * Checks if the device is in COMP mode, and if so migrates it to managed profile on a
+ * corporate owned device.
+ */
+ @GuardedBy("getLockObject()")
+ private void maybeMigrateToProfileOnOrganizationOwnedDeviceLocked() {
+ logIfVerbose("Checking whether we need to migrate COMP ");
+ final int doUserId = mOwners.getDeviceOwnerUserId();
+ if (doUserId == UserHandle.USER_NULL) {
+ logIfVerbose("No DO found, skipping migration.");
+ return;
+ }
+
+ final List<UserInfo> profiles = mUserManager.getProfiles(doUserId);
+ if (profiles.size() != 2) {
+ if (profiles.size() == 1) {
+ logIfVerbose("Profile not found, skipping migration.");
+ } else {
+ Slog.wtf(LOG_TAG, "Found " + profiles.size() + " profiles, skipping migration");
+ }
+ return;
+ }
+
+ final int poUserId = getManagedUserId(doUserId);
+ if (poUserId < 0) {
+ Slog.wtf(LOG_TAG, "Found DO and a profile, but it is not managed, skipping migration");
+ return;
+ }
+
+ final ActiveAdmin doAdmin = getDeviceOwnerAdminLocked();
+ final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(poUserId);
+ if (doAdmin == null || poAdmin == null) {
+ Slog.wtf(LOG_TAG, "Failed to get either PO or DO admin, aborting migration.");
+ return;
+ }
+
+ final ComponentName doAdminComponent = mOwners.getDeviceOwnerComponent();
+ final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(poUserId);
+ if (doAdminComponent == null || poAdminComponent == null) {
+ Slog.wtf(LOG_TAG, "Cannot find PO or DO component name, aborting migration.");
+ return;
+ }
+ if (!doAdminComponent.getPackageName().equals(poAdminComponent.getPackageName())) {
+ Slog.e(LOG_TAG, "DO and PO are different packages, aborting migration.");
+ return;
+ }
+
+ Slog.i(LOG_TAG, String.format(
+ "Migrating COMP to PO on a corp owned device; primary user: %d; profile: %d",
+ doUserId, poUserId));
+
+ Slog.i(LOG_TAG, "Giving the PO additional power...");
+ markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(poAdminComponent, poUserId);
+ Slog.i(LOG_TAG, "Migrating DO policies to PO...");
+ moveDoPoliciesToProfileParentAdmin(doAdmin, poAdmin.getParentActiveAdmin());
+ saveSettingsLocked(poUserId);
+ Slog.i(LOG_TAG, "Clearing the DO...");
+ final ComponentName doAdminReceiver = doAdmin.info.getComponent();
+ clearDeviceOwnerLocked(doAdmin, doUserId);
+ // TODO(b/143516163): If we have a power cut here, we might leave active admin. Consider if
+ // it is worth the complexity to make it more robust.
+ Slog.i(LOG_TAG, "Removing admin artifacts...");
+ // TODO(b/143516163): Clean up application restrictions in UserManager.
+ removeAdminArtifacts(doAdminReceiver, doUserId);
+ Slog.i(LOG_TAG, "Migration complete.");
+
+ // Note: KeyChain keys are not removed and will remain accessible for the apps that have
+ // been given grants to use them.
+ }
+
+ private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
+ // The following policies can be already controlled via parent instance, skip if so.
+ if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) {
+ parentAdmin.mPasswordPolicy = doAdmin.mPasswordPolicy;
+ }
+ if (parentAdmin.passwordHistoryLength == ActiveAdmin.DEF_PASSWORD_HISTORY_LENGTH) {
+ parentAdmin.passwordHistoryLength = doAdmin.passwordHistoryLength;
+ }
+ if (parentAdmin.passwordExpirationTimeout == ActiveAdmin.DEF_PASSWORD_HISTORY_LENGTH) {
+ parentAdmin.passwordExpirationTimeout = doAdmin.passwordExpirationTimeout;
+ }
+ if (parentAdmin.maximumFailedPasswordsForWipe
+ == ActiveAdmin.DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
+ parentAdmin.maximumFailedPasswordsForWipe = doAdmin.maximumFailedPasswordsForWipe;
+ }
+ if (parentAdmin.maximumTimeToUnlock == ActiveAdmin.DEF_MAXIMUM_TIME_TO_UNLOCK) {
+ parentAdmin.maximumTimeToUnlock = doAdmin.maximumTimeToUnlock;
+ }
+ if (parentAdmin.strongAuthUnlockTimeout
+ == DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
+ parentAdmin.strongAuthUnlockTimeout = doAdmin.strongAuthUnlockTimeout;
+ }
+ parentAdmin.disabledKeyguardFeatures |=
+ doAdmin.disabledKeyguardFeatures & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+
+ parentAdmin.trustAgentInfos.putAll(doAdmin.trustAgentInfos);
+
+ // The following policies weren't available to PO, but will be available after migration.
+ parentAdmin.disableCamera = doAdmin.disableCamera;
+
+ // TODO(b/143516163): Uncomment once corresponding APIs are available via parent instance.
+ // parentAdmin.disableScreenCapture = doAdmin.disableScreenCapture;
+ // parentAdmin.accountTypesWithManagementDisabled.addAll(
+ // doAdmin.accountTypesWithManagementDisabled);
+
+ moveDoUserRestrictionsToCopeParent(doAdmin, parentAdmin);
+
+ // TODO(b/143516163): migrate network and security logging state, currently they are
+ // turned off when DO is removed.
+ }
+
+ private void moveDoUserRestrictionsToCopeParent(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
+ if (doAdmin.userRestrictions == null) {
+ return;
+ }
+ for (final String restriction : doAdmin.userRestrictions.keySet()) {
+ if (UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(restriction)) {
+ parentAdmin.userRestrictions.putBoolean(
+ restriction, doAdmin.userRestrictions.getBoolean(restriction));
+ }
+ }
+ }
+
/** Apply default restrictions that haven't been applied to profile owners yet. */
private void maybeSetDefaultProfileOwnerUserRestrictions() {
synchronized (getLockObject()) {
@@ -3625,6 +3748,9 @@
break;
case SystemService.PHASE_ACTIVITY_MANAGER_READY:
maybeStartSecurityLogMonitorOnActivityManagerReady();
+ synchronized (getLockObject()) {
+ maybeMigrateToProfileOnOrganizationOwnedDeviceLocked();
+ }
break;
case SystemService.PHASE_BOOT_COMPLETED:
ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this.
@@ -7456,8 +7582,7 @@
return;
}
Objects.requireNonNull(who, "ComponentName is null");
- // TODO (b/145286957) Refactor security checks
- enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+ enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
@@ -7478,7 +7603,7 @@
return false;
}
Objects.requireNonNull(who, "ComponentName is null");
- enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+ enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
@@ -7492,8 +7617,7 @@
return;
}
Objects.requireNonNull(who, "ComponentName is null");
- // TODO (b/145286957) Refactor security checks
- enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+ enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
@@ -7514,7 +7638,7 @@
return false;
}
Objects.requireNonNull(who, "ComponentName is null");
- enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+ enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
@@ -8724,7 +8848,6 @@
if (!mHasFeature) {
return false;
}
- enforceManageUsers();
return mInjector.binderWithCleanCallingIdentity(() -> {
for (UserInfo ui : mUserManager.getUsers()) {
@@ -9061,23 +9184,22 @@
"Only profile owner, device owner and system may call this method.");
}
- private ActiveAdmin enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned() {
+ private void enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned() {
synchronized (getLockObject()) {
- // Check if there is a device owner
- ActiveAdmin deviceOwner = getActiveAdminWithPolicyForUidLocked(null,
- DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, mInjector.binderGetCallingUid());
- if (deviceOwner != null) return deviceOwner;
+ // Check if there is a device owner or profile owner of an organization-owned device
+ ActiveAdmin owner = getActiveAdminWithPolicyForUidLocked(null,
+ DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER,
+ mInjector.binderGetCallingUid());
+ if (owner != null) {
+ return;
+ }
- ActiveAdmin profileOwner = getActiveAdminWithPolicyForUidLocked(null,
+ // Checks whether the caller is a profile owner on user 0 rather than
+ // checking whether the active admin is on user 0
+ owner = getActiveAdminWithPolicyForUidLocked(null,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid());
-
- // Check if there is a profile owner of an organization owned device
- if (isProfileOwnerOfOrganizationOwnedDevice(profileOwner)) return profileOwner;
-
- // Check if there is a profile owner called on user 0
- if (profileOwner != null) {
- enforceCallerSystemUserHandle();
- return profileOwner;
+ if (owner != null && owner.getUserHandle().isSystem()) {
+ return;
}
}
throw new SecurityException("No active admin found");
@@ -12923,37 +13045,43 @@
// Grant access under lock.
synchronized (getLockObject()) {
- // Sanity check: Make sure that the user has a profile owner and that the specified
- // component is the profile owner of that user.
- if (!isProfileOwner(who, userId)) {
- throw new IllegalArgumentException(String.format(
- "Component %s is not a Profile Owner of user %d",
- who.flattenToString(), userId));
- }
+ markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(who, userId);
+ }
+ }
- Slog.i(LOG_TAG, String.format(
- "Marking %s as profile owner on organization-owned device for user %d",
+ @GuardedBy("getLockObject()")
+ private void markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(
+ ComponentName who, int userId) {
+ // Sanity check: Make sure that the user has a profile owner and that the specified
+ // component is the profile owner of that user.
+ if (!isProfileOwner(who, userId)) {
+ throw new IllegalArgumentException(String.format(
+ "Component %s is not a Profile Owner of user %d",
who.flattenToString(), userId));
+ }
- // First, set restriction on removing the profile.
- mInjector.binderWithCleanCallingIdentity(() -> {
- // Clear restriction as user.
- UserHandle parentUser = mUserManager.getProfileParent(UserHandle.of(userId));
- if (!parentUser.isSystem()) {
- throw new IllegalStateException(
- String.format("Only the profile owner of a managed profile on the"
+ Slog.i(LOG_TAG, String.format(
+ "Marking %s as profile owner on organization-owned device for user %d",
+ who.flattenToString(), userId));
+
+ // First, set restriction on removing the profile.
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ // Clear restriction as user.
+ final UserHandle parentUser = mUserManager.getProfileParent(UserHandle.of(userId));
+ if (!parentUser.isSystem()) {
+ throw new IllegalStateException(
+ String.format("Only the profile owner of a managed profile on the"
+ " primary user can be granted access to device identifiers, not"
+ " on user %d", parentUser.getIdentifier()));
- }
+ }
- mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true,
- parentUser);
- });
+ mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true,
+ parentUser);
+ });
- // markProfileOwnerOfOrganizationOwnedDevice will trigger writing of the profile owner
- // data, no need to do it manually.
- mOwners.markProfileOwnerOfOrganizationOwnedDevice(userId);
- }
+ // markProfileOwnerOfOrganizationOwnedDevice will trigger writing of the profile owner
+ // data, no need to do it manually.
+ mOwners.markProfileOwnerOfOrganizationOwnedDevice(userId);
}
private void pushMeteredDisabledPackagesLocked(int userId) {
@@ -14917,4 +15045,10 @@
return packages == null ? Collections.EMPTY_LIST : packages;
}
}
+
+ private void logIfVerbose(String message) {
+ if (VERBOSE_LOG) {
+ Slog.d(LOG_TAG, message);
+ }
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ea2385f..3dee913 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -71,11 +71,11 @@
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Slog;
+import android.util.StatsLog;
import android.view.WindowManager;
import android.view.contentcapture.ContentCaptureManager;
import com.android.internal.R;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
import com.android.internal.util.ConcurrentUtils;
@@ -443,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
@@ -555,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);
}
@@ -754,7 +758,7 @@
// note that we just booted, which might send out a rescue party if
// we're stuck in a runtime restart loop.
RescueParty.registerHealthObserver(mSystemContext);
- RescueParty.noteBoot(mSystemContext);
+ PackageWatchdog.getInstance(mSystemContext).noteBoot();
// Manages LEDs and display backlight so we need it to bring up the display.
t.traceBegin("StartLightsService");
@@ -791,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");
@@ -808,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.
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 96fedf9..3d9f11f 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -21,6 +21,7 @@
"services.core",
"services.net",
"service-jobscheduler",
+ "service-permission",
"androidx.test.runner",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 30d89d3..c3602f8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -24,6 +24,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.RescueParty.LEVEL_FACTORY_RESET;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -144,7 +145,6 @@
doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime());
- RescueParty.resetAllThresholds();
FlagNamespaceUtils.resetKnownResetNamespacesFlagCounterForTest();
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL,
@@ -160,28 +160,28 @@
@Test
public void testBootLoopDetectionWithExecutionForAllRescueLevels() {
- noteBoot(RescueParty.TRIGGER_COUNT);
+ noteBoot();
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- noteBoot(RescueParty.TRIGGER_COUNT);
+ noteBoot();
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- noteBoot(RescueParty.TRIGGER_COUNT);
+ noteBoot();
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- noteBoot(RescueParty.TRIGGER_COUNT);
+ noteBoot();
verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
- assertEquals(RescueParty.LEVEL_FACTORY_RESET,
+ assertEquals(LEVEL_FACTORY_RESET,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
}
@@ -208,48 +208,15 @@
notePersistentAppCrash();
verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
- assertEquals(RescueParty.LEVEL_FACTORY_RESET,
- SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- }
-
- @Test
- public void testBootLoopDetectionWithWrongInterval() {
- noteBoot(RescueParty.TRIGGER_COUNT - 1);
-
- // last boot is just outside of the boot loop detection window
- doReturn(CURRENT_NETWORK_TIME_MILLIS + RescueParty.BOOT_TRIGGER_WINDOW_MILLIS + 1).when(
- () -> RescueParty.getElapsedRealtime());
- noteBoot(/*numTimes=*/1);
-
- assertEquals(RescueParty.LEVEL_NONE,
- SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- }
-
- @Test
- public void testBootLoopDetectionWithProperInterval() {
- noteBoot(RescueParty.TRIGGER_COUNT - 1);
-
- // last boot is just inside of the boot loop detection window
- doReturn(CURRENT_NETWORK_TIME_MILLIS + RescueParty.BOOT_TRIGGER_WINDOW_MILLIS).when(
- () -> RescueParty.getElapsedRealtime());
- noteBoot(/*numTimes=*/1);
-
- verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
- assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
- SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- }
-
- @Test
- public void testBootLoopDetectionWithWrongTriggerCount() {
- noteBoot(RescueParty.TRIGGER_COUNT - 1);
- assertEquals(RescueParty.LEVEL_NONE,
+ assertEquals(LEVEL_FACTORY_RESET,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
}
@Test
public void testIsAttemptingFactoryReset() {
- noteBoot(RescueParty.TRIGGER_COUNT * 4);
-
+ for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+ noteBoot();
+ }
verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
assertTrue(RescueParty.isAttemptingFactoryReset());
}
@@ -306,7 +273,7 @@
// Ensure that no action is taken for cases where the failure reason is unknown
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
- RescueParty.LEVEL_FACTORY_RESET));
+ LEVEL_FACTORY_RESET));
assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN),
PackageHealthObserverImpact.USER_IMPACT_NONE);
@@ -342,7 +309,7 @@
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
- RescueParty.LEVEL_FACTORY_RESET));
+ LEVEL_FACTORY_RESET));
assertEquals(observer.onHealthCheckFailed(null,
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
PackageHealthObserverImpact.USER_IMPACT_HIGH);
@@ -366,10 +333,8 @@
eq(resetMode), anyInt()));
}
- private void noteBoot(int numTimes) {
- for (int i = 0; i < numTimes; i++) {
- RescueParty.noteBoot(mMockContext);
- }
+ private void noteBoot() {
+ RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation();
}
private void notePersistentAppCrash() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index f2e118d..e0e374b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -50,6 +50,7 @@
import android.os.BatteryManagerInternal;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import com.android.server.AppStateTracker;
@@ -95,6 +96,7 @@
.initMocks(this)
.strictness(Strictness.LENIENT)
.mockStatic(LocalServices.class)
+ .mockStatic(ServiceManager.class)
.startMocking();
// Called in JobSchedulerService constructor.
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index ace15eb..556f636 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -44,6 +44,7 @@
"servicestests-utils",
"service-appsearch",
"service-jobscheduler",
+ "service-permission",
// TODO: remove once Android migrates to JUnit 4.12,
// which provides assertThrows
"testng",
diff --git a/services/tests/servicestests/res/raw/comp_device_owner.xml b/services/tests/servicestests/res/raw/comp_device_owner.xml
new file mode 100644
index 0000000..0a10242
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_device_owner.xml
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+ <device-owner package="com.android.frameworks.servicestests"
+ name=""
+ component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"
+ userRestrictionsMigrated="true" />
+ <device-owner-context userId="0" />
+</root>
diff --git a/services/tests/servicestests/res/raw/comp_policies_primary.xml b/services/tests/servicestests/res/raw/comp_policies_primary.xml
new file mode 100644
index 0000000..1e1a0ef
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_policies_primary.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+ <policies flags="991"/>
+ <password-history-length value="33" />
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml b/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml
new file mode 100644
index 0000000..141315e
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <admin name="com.another.package.name/whatever.random.class">
+ <policies flags="991"/>
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml
new file mode 100644
index 0000000..c874dcc
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+ <policies flags="991"/>
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml b/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml
new file mode 100644
index 0000000..d65ba78
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+ <profile-owner package="com.another.package.name"
+ name="com.another.package.name"
+ component="com.another.package.name/whatever.random.class"
+ userRestrictionsMigrated="true"/>
+</root>
diff --git a/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml b/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml
new file mode 100644
index 0000000..7f98c91c
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+ <profile-owner package="com.android.frameworks.servicestests"
+ name="com.android.frameworks.servicestests"
+ component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"
+ userRestrictionsMigrated="true"/>
+</root>
diff --git a/services/tests/servicestests/src/com/android/server/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/appsearch/impl/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
new file mode 100644
index 0000000..4195679
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.appsearch.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.android.icing.proto.IndexingConfig;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AppSearchImplTest {
+ private final Context mContext = InstrumentationRegistry.getContext();
+ private final @UserIdInt int mUserId = UserHandle.getCallingUserId();
+
+ @Test
+ public void testRewriteSchemaTypes() {
+ SchemaProto inSchema = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("TestType")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ IndexingConfig.newBuilder()
+ .setTokenizerType(
+ IndexingConfig.TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ .build()
+ ).build()
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("link")
+ .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setSchemaType("RefType")
+ .build()
+ ).build()
+ ).build();
+
+ SchemaProto expectedSchema = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("com.android.server.appsearch.impl@42:TestType")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ IndexingConfig.newBuilder()
+ .setTokenizerType(
+ IndexingConfig.TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ .build()
+ ).build()
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("link")
+ .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setSchemaType("com.android.server.appsearch.impl@42:RefType")
+ .build()
+ ).build()
+ ).build();
+
+ AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
+ SchemaProto.Builder actualSchema = inSchema.toBuilder();
+ impl.rewriteSchemaTypes("com.android.server.appsearch.impl@42:", actualSchema);
+
+ assertThat(actualSchema.build()).isEqualTo(expectedSchema);
+ }
+
+ @Test
+ public void testPackageNotFound() {
+ AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
+ IllegalStateException e = expectThrows(
+ IllegalStateException.class,
+ () -> impl.setSchema(
+ /*callingUid=*/Integer.MAX_VALUE, SchemaProto.getDefaultInstance()));
+ assertThat(e).hasMessageThat().contains("Failed to look up package name");
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 5f1f308..46b8371 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -15,6 +15,10 @@
*/
package com.android.server.devicepolicy;
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile;
+
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
@@ -22,12 +26,14 @@
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import com.android.frameworks.servicestests.R;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable;
@@ -37,9 +43,13 @@
import java.util.Map;
import java.util.Set;
+// TODO (b/143516163): Fix old test cases and put into presubmit.
public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
private static final String USER_TYPE_EMPTY = "";
+ private static final int COPE_ADMIN1_APP_ID = 123;
+ private static final int COPE_ANOTHER_ADMIN_APP_ID = 125;
+ private static final int COPE_PROFILE_USER_ID = 11;
private DpmMockContext mContext;
@@ -85,7 +95,7 @@
// Set up UserManager
when(getServices().userManagerInternal.getBaseUserRestrictions(
- eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
+ eq(USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
UserManager.DISALLOW_ADD_USER,
UserManager.DISALLOW_RECORD_AUDIO));
@@ -137,7 +147,7 @@
}
assertTrue(dpms.mOwners.hasDeviceOwner());
- assertFalse(dpms.mOwners.hasProfileOwner(UserHandle.USER_SYSTEM));
+ assertFalse(dpms.mOwners.hasProfileOwner(USER_SYSTEM));
assertTrue(dpms.mOwners.hasProfileOwner(10));
assertTrue(dpms.mOwners.hasProfileOwner(11));
assertFalse(dpms.mOwners.hasProfileOwner(12));
@@ -145,7 +155,7 @@
// Now all information should be migrated.
assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(
- UserHandle.USER_SYSTEM));
+ USER_SYSTEM));
assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(10));
assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(11));
assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(12));
@@ -155,7 +165,7 @@
DpmTestUtils.newRestrictions(
UserManager.DISALLOW_RECORD_AUDIO
),
- newBaseRestrictions.get(UserHandle.USER_SYSTEM));
+ newBaseRestrictions.get(USER_SYSTEM));
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
@@ -214,7 +224,7 @@
// Set up UserManager
when(getServices().userManagerInternal.getBaseUserRestrictions(
- eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
+ eq(USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
UserManager.DISALLOW_ADD_USER,
UserManager.DISALLOW_RECORD_AUDIO,
UserManager.DISALLOW_SMS,
@@ -249,19 +259,19 @@
mContext.binder.restoreCallingIdentity(ident);
}
assertFalse(dpms.mOwners.hasDeviceOwner());
- assertTrue(dpms.mOwners.hasProfileOwner(UserHandle.USER_SYSTEM));
+ assertTrue(dpms.mOwners.hasProfileOwner(USER_SYSTEM));
// Now all information should be migrated.
assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(
- UserHandle.USER_SYSTEM));
+ USER_SYSTEM));
// Check the new base restrictions.
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
UserManager.DISALLOW_RECORD_AUDIO
),
- newBaseRestrictions.get(UserHandle.USER_SYSTEM));
+ newBaseRestrictions.get(USER_SYSTEM));
// Check the new owner restrictions.
DpmTestUtils.assertRestrictions(
@@ -270,7 +280,7 @@
UserManager.DISALLOW_SMS,
UserManager.DISALLOW_OUTGOING_CALLS
),
- dpms.getProfileOwnerAdminLocked(UserHandle.USER_SYSTEM).ensureUserRestrictions());
+ dpms.getProfileOwnerAdminLocked(USER_SYSTEM).ensureUserRestrictions());
}
// Test setting default restrictions for managed profile.
@@ -332,4 +342,92 @@
assertEquals(alreadySet.size(), 1);
assertTrue(alreadySet.contains(UserManager.DISALLOW_BLUETOOTH_SHARING));
}
+
+ public void testCompMigrationUnAffiliated_skipped() throws Exception {
+ prepareAdmin1AsDo();
+ prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID);
+
+ final DevicePolicyManagerServiceTestable dpms;
+ dpms = bootDpmsUp();
+
+ // DO should still be DO since no migration should happen.
+ assertTrue(dpms.mOwners.hasDeviceOwner());
+ }
+
+ public void testCompMigrationAffiliated() throws Exception {
+ prepareAdmin1AsDo();
+ prepareAdmin1AsPo(COPE_PROFILE_USER_ID);
+
+ // Secure lock screen is needed for password policy APIs to work.
+ when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
+
+ final DevicePolicyManagerServiceTestable dpms;
+ dpms = bootDpmsUp();
+
+ // DO should cease to be DO.
+ assertFalse(dpms.mOwners.hasDeviceOwner());
+
+ final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
+ poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);
+
+ runAsCaller(poContext, dpms, dpm -> {
+ // Check that DO policy is now set on parent instance.
+ assertEquals(33, dpm.getParentProfileInstance(admin1).getPasswordHistoryLength(admin1));
+ // And NOT set on profile instance.
+ assertEquals(0, dpm.getPasswordHistoryLength(admin1));
+
+ // TODO(b/143516163): verify more policies.
+ });
+ }
+
+ private DevicePolicyManagerServiceTestable bootDpmsUp() {
+ DevicePolicyManagerServiceTestable dpms;
+ final long ident = mContext.binder.clearCallingIdentity();
+ try {
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+ dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
+
+ dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
+ dpms.systemReady(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+ dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
+ } finally {
+ mContext.binder.restoreCallingIdentity(ident);
+ }
+ return dpms;
+ }
+
+ private void prepareAdmin1AsDo() throws Exception {
+ setUpPackageManagerForAdmin(admin1, UserHandle.getUid(USER_SYSTEM, COPE_ADMIN1_APP_ID));
+ final int xmlResource = R.raw.comp_policies_primary;
+ writeInputStreamToFile(getRawStream(xmlResource),
+ (new File(getServices().systemUserDataDir, "device_policies.xml"))
+ .getAbsoluteFile());
+ writeInputStreamToFile(getRawStream(R.raw.comp_device_owner),
+ (new File(getServices().dataDir, "device_owner_2.xml"))
+ .getAbsoluteFile());
+ }
+
+ private void prepareAdmin1AsPo(int profileUserId) throws Exception {
+ preparePo(profileUserId, admin1, R.raw.comp_profile_owner_same_package,
+ R.raw.comp_policies_profile_same_package, COPE_ADMIN1_APP_ID);
+ }
+
+ private void prepareAdminAnotherPackageAsPo(int profileUserId) throws Exception {
+ preparePo(profileUserId, adminAnotherPackage, R.raw.comp_profile_owner_another_package,
+ R.raw.comp_policies_profile_another_package, COPE_ANOTHER_ADMIN_APP_ID);
+ }
+
+ private void preparePo(int profileUserId, ComponentName admin, int profileOwnerXmlResId,
+ int policyXmlResId, int adminAppId) throws Exception {
+ final File profileDir = getServices().addUser(profileUserId, 0,
+ UserManager.USER_TYPE_PROFILE_MANAGED, USER_SYSTEM /* profile group */);
+ setUpPackageManagerForFakeAdmin(
+ admin, UserHandle.getUid(profileUserId, adminAppId), admin1);
+ writeInputStreamToFile(getRawStream(policyXmlResId),
+ (new File(profileDir, "device_policies.xml")).getAbsoluteFile());
+ writeInputStreamToFile(getRawStream(profileOwnerXmlResId),
+ (new File(profileDir, "profile_owner.xml")).getAbsoluteFile());
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index d670361..bfadeea 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -58,7 +58,6 @@
import static org.testng.Assert.assertThrows;
import android.Manifest.permission;
-import android.annotation.RawRes;
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.Notification;
@@ -112,7 +111,6 @@
import org.mockito.stubbing.Answer;
import java.io.File;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -3832,11 +3830,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());
@@ -5814,10 +5808,6 @@
return new File(parentDir, "device_policies.xml");
}
- private InputStream getRawStream(@RawRes int id) {
- return mRealTestContext.getResources().openRawResource(id);
- }
-
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
when(getServices().settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index a34c2ff..9a1a5fb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
+import android.annotation.RawRes;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
@@ -36,6 +37,7 @@
import android.os.UserHandle;
import android.test.AndroidTestCase;
+import java.io.InputStream;
import java.util.List;
public abstract class DpmTestBase extends AndroidTestCase {
@@ -256,4 +258,8 @@
invocation -> invocation.getArguments()[1]
);
}
+
+ protected InputStream getRawStream(@RawRes int id) {
+ return mRealTestContext.getResources().openRawResource(id);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 6c2c144..068daf5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -15,6 +15,7 @@
*/
package com.android.server.devicepolicy;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -236,6 +237,13 @@
return ui == null ? null : getUserInfo(ui.profileGroupId);
}
);
+ when(userManager.getProfileParent(any(UserHandle.class))).thenAnswer(
+ invocation -> {
+ final UserHandle userHandle = (UserHandle) invocation.getArguments()[0];
+ final UserInfo ui = getUserInfo(userHandle.getIdentifier());
+ return ui == null ? UserHandle.USER_NULL : UserHandle.of(ui.profileGroupId);
+ }
+ );
when(userManager.getProfiles(anyInt())).thenAnswer(
invocation -> {
final int userId12 = (int) invocation.getArguments()[0];
diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
index 5aed194..a1810b9 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
@@ -16,6 +16,8 @@
package com.android.server.integrity;
+import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.integrity.AppInstallMetadata;
@@ -135,14 +137,15 @@
Arrays.asList(packageNameRule, packageCertRule, versionCodeRule, randomRule);
mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
- AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder()
- .setPackageName(packageName)
- .setAppCertificate(packageCert)
- .setVersionCode(version)
- .setInstallerName("abc")
- .setInstallerCertificate("abc")
- .setIsPreInstalled(true)
- .build();
+ AppInstallMetadata appInstallMetadata =
+ new AppInstallMetadata.Builder()
+ .setPackageName(packageName)
+ .setAppCertificate(packageCert)
+ .setVersionCode(version)
+ .setInstallerName("abc")
+ .setInstallerCertificate("abc")
+ .setIsPreInstalled(true)
+ .build();
List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
assertThat(rulesFetched)
@@ -158,13 +161,14 @@
// Create a rule set with 2500 package name indexed, 2500 app certificate indexed and
// 500 unindexed rules.
List<Rule> rules = new ArrayList<>();
+ int unindexedRuleCount = 70;
for (int i = 0; i < 2500; i++) {
rules.add(getPackageNameIndexedRule(String.format("%s%04d", packageName, i)));
rules.add(getAppCertificateIndexedRule(String.format("%s%04d", appCertificate, i)));
}
- for (int i = 0; i < 70; i++) {
+ for (int i = 0; i < unindexedRuleCount; i++) {
rules.add(getInstallerCertificateRule(String.format("%s%04d", installerName, i)));
}
@@ -174,18 +178,20 @@
// 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();
+ 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.size())
+ .isEqualTo(INDEXING_BLOCK_SIZE * 2 + unindexedRuleCount);
assertThat(rulesFetched)
.containsAllOf(
getPackageNameIndexedRule(installedPackageName),
@@ -195,27 +201,38 @@
private Rule getPackageNameIndexedRule(String packageName) {
return new Rule(
new StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
+ AtomicFormula.PACKAGE_NAME, packageName, /* isHashedValue= */ false),
Rule.DENY);
}
private Rule getAppCertificateIndexedRule(String appCertificate) {
return new Rule(
new StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- appCertificate,
- /* isHashedValue= */ false),
+ AtomicFormula.APP_CERTIFICATE, appCertificate, /* isHashedValue= */ false),
Rule.DENY);
}
private Rule getInstallerCertificateRule(String installerCert) {
return new Rule(
new StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- installerCert,
- /* isHashedValue= */ false),
+ AtomicFormula.INSTALLER_NAME, installerCert, /* isHashedValue= */ false),
Rule.DENY);
}
+
+ @Test
+ public void testStagingDirectoryCleared() throws Exception {
+ // We must push rules two times to ensure that staging directory is empty because we cleared
+ // it, rather than because original rules directory is empty.
+ mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
+ mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
+
+ assertStagingDirectoryCleared();
+ }
+
+ private void assertStagingDirectoryCleared() {
+ File stagingDir = new File(mTmpDir, "integrity_staging");
+ assertThat(stagingDir.exists()).isTrue();
+ assertThat(stagingDir.isDirectory()).isTrue();
+ assertThat(stagingDir.listFiles()).isEmpty();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java
deleted file mode 100644
index 4fa7302..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java
+++ /dev/null
@@ -1,162 +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_canReadMoreRules() 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.canReadMoreRules(2)).isTrue();
-
- // Read until the string parameter.
- String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream);
-
- // Verify that the read bytes are counted.
- assertThat(stringValue).isEqualTo(packageName);
- assertThat(bitTrackedInputStream.canReadMoreRules(2)).isFalse();
- }
-
- @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/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 881b3d6..e0b2e22 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
@@ -44,8 +44,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -121,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(
@@ -133,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));
}
@@ -323,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(
@@ -412,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()));
}
@@ -439,7 +438,7 @@
assertExpectException(
RuleParseException.class,
- /* expectedExceptionMessageRegex */ "Invalid byte index",
+ /* expectedExceptionMessageRegex */ "",
() -> binaryParser.parse(rule.array()));
}
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/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/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 7ac45f0..6f16574 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -227,6 +227,7 @@
assertEquals(expected.getLightColor(), actual.getLightColor());
assertEquals(expected.getParentChannelId(), actual.getParentChannelId());
assertEquals(expected.getConversationId(), actual.getConversationId());
+ assertEquals(expected.isDemoted(), actual.isDemoted());
}
private void compareChannelsParentChild(NotificationChannel parent,
@@ -355,6 +356,7 @@
channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
channel2.setLightColor(Color.BLUE);
channel2.setConversationId("id1", "conversation");
+ channel2.setDemoted(true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
new file mode 100644
index 0000000..032edde
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * This file tests WM setting the priority on windows that is used in SF to determine at what
+ * frame rate the Display should run. Any changes to the algorithm should be reflected in these
+ * tests.
+ *
+ * Build/Install/Run: atest FrameRateSelectionPriority
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class FrameRateSelectionPriorityTests extends WindowTestsBase {
+
+ @Test
+ public void basicTest() {
+ final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ assertNotNull("Window state is created", appWindow);
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority doesn't change.
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ // Call the function a few times.
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+
+ // Since nothing changed in the priority state, the transaction should not be updating.
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ }
+
+ @Test
+ public void testApplicationInFocusWithoutModeId() {
+ final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+ .getPreferredModeId(appWindow), 0);
+
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority stays MAX_VALUE.
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ // Application is in focus.
+ appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority changes to 1.
+ assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+ verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), 1);
+ }
+
+ @Test
+ public void testApplicationInFocusWithModeId() {
+ final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ // Application is in focus.
+ appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority changes.
+ assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+ // Update the mode ID to a requested number.
+ appWindow.mAttrs.preferredDisplayModeId = 1;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority changes.
+ assertEquals(appWindow.mFrameRateSelectionPriority, 0);
+
+ // Remove the mode ID request.
+ appWindow.mAttrs.preferredDisplayModeId = 0;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority changes.
+ assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+
+ // Verify we called actions on Transactions correctly.
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), 0);
+ verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), 1);
+ }
+
+ @Test
+ public void testApplicationNotInFocusWithModeId() {
+ final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
+ appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
+
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // The window is not in focus.
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ // Update the mode ID to a requested number.
+ appWindow.mAttrs.preferredDisplayModeId = 1;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority changes.
+ assertEquals(appWindow.mFrameRateSelectionPriority, 2);
+
+ verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), 2);
+ }
+
+ @Test
+ public void testApplicationNotInFocusWithoutModeId() {
+ final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
+ appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
+
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // The window is not in focus.
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ // Make sure that the mode ID is not set.
+ appWindow.mAttrs.preferredDisplayModeId = 0;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority doesn't change.
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 64db897..890e4ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -391,6 +391,33 @@
assertEquals(null, compatTokens.get(0));
}
+ @Test
+ public void testShouldUseSizeCompatModeOnResizableTask() {
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
+
+ // Make the task root resizable.
+ mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mService)
+ .setTask(mTask)
+ .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .build();
+ assertTrue(activity.shouldUseSizeCompatMode());
+
+ // The non-resizable activity should not be size compat because it is on a resizable task
+ // in multi-window mode.
+ mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ assertFalse(activity.shouldUseSizeCompatMode());
+
+ // The non-resizable activity should not be size compat because the display support
+ // changing windowing mode from fullscreen to freeform.
+ mStack.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+ assertFalse(activity.shouldUseSizeCompatMode());
+ }
+
/**
* Setup {@link #mActivity} as a size-compat-mode-able activity with fixed aspect and/or
* orientation.
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index f5d08dc..eda1fb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -250,4 +250,9 @@
return this;
}
+ @Override
+ public SurfaceControl.Transaction setFrameRateSelectionPriority(SurfaceControl sc,
+ int priority) {
+ return this;
+ }
}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java b/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java
new file mode 100644
index 0000000..a532548
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usage;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageStats;
+
+/**
+ * StorageStatsManager local system service interface.
+ *
+ * Only for use within the system server.
+ */
+public abstract class StorageStatsManagerInternal {
+ /**
+ * Class used to augment {@link PackageStats} with the data stored by the system on
+ * behalf of apps in system specific directories
+ * ({@link android.os.Environment#getDataSystemDirectory},
+ * {@link android.os.Environment#getDataSystemCeDirectory}, etc).
+ */
+ public interface StorageStatsAugmenter {
+ void augmentStatsForPackage(@NonNull PackageStats stats,
+ @NonNull String packageName, @UserIdInt int userId,
+ @NonNull String callingPackage);
+ void augmentStatsForUid(@NonNull PackageStats stats, int uid,
+ @NonNull String callingPackage);
+ void augmentStatsForUser(@NonNull PackageStats stats, @UserIdInt int userId,
+ @NonNull String callingPackage);
+ }
+
+ /**
+ * Register a {@link StorageStatsAugmenter}.
+ *
+ * @param augmenter the {@link StorageStatsAugmenter} object to be registered.
+ * @param tag the identifier to be used for debugging in logs/trace.
+ */
+ public abstract void registerStorageStatsAugmenter(@NonNull StorageStatsAugmenter augmenter,
+ @NonNull String tag);
+}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 531a931..18b640f 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -18,6 +18,7 @@
import static com.android.internal.util.ArrayUtils.defeatNullable;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.usage.StorageStatsManagerInternal.StorageStatsAugmenter;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -46,6 +47,7 @@
import android.os.ParcelableException;
import android.os.StatFs;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.CrateInfo;
@@ -58,6 +60,7 @@
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.DataUnit;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseLongArray;
@@ -77,6 +80,8 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
public class StorageStatsService extends IStorageStatsManager.Stub {
private static final String TAG = "StorageStatsService";
@@ -111,6 +116,9 @@
private final Installer mInstaller;
private final H mHandler;
+ private final CopyOnWriteArrayList<Pair<String, StorageStatsAugmenter>>
+ mStorageStatsAugmenters = new CopyOnWriteArrayList<>();
+
public StorageStatsService(Context context) {
mContext = Preconditions.checkNotNull(context);
mAppOps = Preconditions.checkNotNull(context.getSystemService(AppOpsManager.class));
@@ -139,6 +147,8 @@
}
}
});
+
+ LocalServices.addService(StorageStatsManagerInternal.class, new LocalService());
}
private void invalidateMounts() {
@@ -300,6 +310,12 @@
} catch (InstallerException e) {
throw new ParcelableException(new IOException(e.getMessage()));
}
+ if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
+ forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
+ storageStatsAugmenter.augmentStatsForPackage(stats,
+ packageName, userId, callingPackage);
+ }, "queryStatsForPackage");
+ }
return translate(stats);
}
}
@@ -353,6 +369,12 @@
} catch (InstallerException e) {
throw new ParcelableException(new IOException(e.getMessage()));
}
+
+ if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
+ forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
+ storageStatsAugmenter.augmentStatsForUid(stats, uid, callingPackage);
+ }, "queryStatsForUid");
+ }
return translate(stats);
}
@@ -379,6 +401,12 @@
} catch (InstallerException e) {
throw new ParcelableException(new IOException(e.getMessage()));
}
+
+ if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
+ forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
+ storageStatsAugmenter.augmentStatsForUser(stats, userId, callingPackage);
+ }, "queryStatsForUser");
+ }
return translate(stats);
}
@@ -705,4 +733,29 @@
throw new ParcelableException(new IOException(e.getMessage()));
}
}
+
+ void forEachStorageStatsAugmenter(@NonNull Consumer<StorageStatsAugmenter> consumer,
+ @NonNull String queryTag) {
+ for (int i = 0, count = mStorageStatsAugmenters.size(); i < count; ++i) {
+ final Pair<String, StorageStatsAugmenter> pair = mStorageStatsAugmenters.get(i);
+ final String augmenterTag = pair.first;
+ final StorageStatsAugmenter storageStatsAugmenter = pair.second;
+
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, queryTag + ":" + augmenterTag);
+ try {
+ consumer.accept(storageStatsAugmenter);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+ }
+ }
+
+ private class LocalService extends StorageStatsManagerInternal {
+ @Override
+ public void registerStorageStatsAugmenter(
+ @NonNull StorageStatsAugmenter storageStatsAugmenter,
+ @NonNull String tag) {
+ mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter));
+ }
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 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/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/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index f6ce0dc..b8b203d 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -592,7 +592,7 @@
private static boolean checkCarrierPrivilegeForAnySubId(Context context, int uid) {
SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
Context.TELEPHONY_SUBSCRIPTION_SERVICE);
- int[] activeSubIds = sm.getActiveSubscriptionIdList(/* visibleOnly */ false);
+ int[] activeSubIds = sm.getActiveAndHiddenSubscriptionIdList();
for (int activeSubId : activeSubIds) {
if (getCarrierPrivilegeStatus(context, activeSubId, uid)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 2abcc76..a7ad884 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -28,6 +28,8 @@
import android.os.SystemProperties;
import java.io.PrintWriter;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
@@ -137,4 +139,12 @@
}
return ret;
}
+
+ /** Wait for latch to trigger */
+ public static void waitUntilReady(CountDownLatch latch, long timeoutMs) {
+ try {
+ latch.await(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ignored) {
+ }
+ }
}
diff --git a/telephony/framework-telephony-jarjar-rules.txt b/telephony/framework-telephony-jarjar-rules.txt
new file mode 100644
index 0000000..7cab806
--- /dev/null
+++ b/telephony/framework-telephony-jarjar-rules.txt
@@ -0,0 +1,4 @@
+rule android.telephony.Annotation* android.telephony.framework.Annotation@1
+rule com.android.i18n.phonenumbers.** com.android.telephony.framework.phonenumbers.@1
+#TODO: add jarjar rules for statically linked util classes
+
diff --git a/telephony/java/android/telephony/BarringInfo.aidl b/telephony/java/android/telephony/BarringInfo.aidl
new file mode 100644
index 0000000..50ddf6b
--- /dev/null
+++ b/telephony/java/android/telephony/BarringInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/** @hide */
+package android.telephony;
+
+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/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index ad8ac76..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;
}
@@ -1718,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/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 8ed4ee5..fee6d3f 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -25,11 +25,12 @@
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.ActivityThread;
import android.app.PendingIntent;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.database.CursorWindow;
import android.net.Uri;
import android.os.Build;
@@ -423,7 +424,7 @@
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
- true /* persistMessage*/, ActivityThread.currentPackageName());
+ true /* persistMessage*/, null);
}
/**
@@ -633,7 +634,7 @@
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
- false /* persistMessage */, ActivityThread.currentPackageName());
+ false /* persistMessage */, null);
}
private void sendTextMessageInternal(
@@ -676,7 +677,7 @@
ISms iSms = getISmsServiceOrThrow();
if (iSms != null) {
iSms.sendTextForSubscriberWithOptions(subId,
- ActivityThread.currentPackageName(), destinationAddress,
+ null, destinationAddress,
scAddress,
text, sentIntent, deliveryIntent, persistMessage, finalPriority,
expectMore, finalValidity);
@@ -698,7 +699,7 @@
ISms iSms = getISmsServiceOrThrow();
if (iSms != null) {
iSms.sendTextForSubscriberWithOptions(getSubscriptionId(),
- ActivityThread.currentPackageName(), destinationAddress,
+ null, destinationAddress,
scAddress,
text, sentIntent, deliveryIntent, persistMessage, finalPriority,
expectMore, finalValidity);
@@ -920,7 +921,7 @@
String destinationAddress, String scAddress, ArrayList<String> parts,
ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
- deliveryIntents, true /* persistMessage*/, ActivityThread.currentPackageName());
+ deliveryIntents, true /* persistMessage*/, null);
}
/**
@@ -937,8 +938,9 @@
* subscription.
* </p>
*
- * @param packageName serves as the default package name if
- * {@link ActivityThread#currentPackageName()} is null.
+ * @param packageName serves as the default package name if the package name that is
+ * associated with the user id is null.
+ *
* @hide
*/
@SystemApi
@@ -948,9 +950,7 @@
@NonNull List<String> parts, @Nullable List<PendingIntent> sentIntents,
@Nullable List<PendingIntent> deliveryIntents, @NonNull String packageName) {
sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
- deliveryIntents, true /* persistMessage*/,
- ActivityThread.currentPackageName() == null
- ? packageName : ActivityThread.currentPackageName());
+ deliveryIntents, true /* persistMessage*/, packageName);
}
private void sendMultipartTextMessageInternal(
@@ -1051,7 +1051,7 @@
String destinationAddress, String scAddress, List<String> parts,
List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
- deliveryIntents, false /* persistMessage*/, ActivityThread.currentPackageName());
+ deliveryIntents, false /* persistMessage*/, null);
}
/**
@@ -1213,7 +1213,7 @@
ISms iSms = getISmsServiceOrThrow();
if (iSms != null) {
iSms.sendMultipartTextForSubscriberWithOptions(subId,
- ActivityThread.currentPackageName(), destinationAddress,
+ null, destinationAddress,
scAddress, parts, sentIntents, deliveryIntents,
persistMessage, finalPriority, expectMore, finalValidity);
}
@@ -1235,7 +1235,7 @@
ISms iSms = getISmsServiceOrThrow();
if (iSms != null) {
iSms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
- ActivityThread.currentPackageName(), destinationAddress,
+ null, destinationAddress,
scAddress, parts, sentIntents, deliveryIntents,
persistMessage, finalPriority, expectMore, finalValidity);
}
@@ -1366,7 +1366,7 @@
public void onSuccess(int subId) {
try {
ISms iSms = getISmsServiceOrThrow();
- iSms.sendDataForSubscriber(subId, ActivityThread.currentPackageName(),
+ iSms.sendDataForSubscriber(subId, null,
destinationAddress, scAddress, destinationPort & 0xFFFF, data,
sentIntent, deliveryIntent);
} catch (RemoteException e) {
@@ -1492,7 +1492,6 @@
private void resolveSubscriptionForOperation(SubscriptionResolverResult resolverResult) {
int subId = getSubscriptionId();
boolean isSmsSimPickActivityNeeded = false;
- final Context context = ActivityThread.currentApplication().getApplicationContext();
try {
ISms iSms = getISmsService();
if (iSms != null) {
@@ -1514,14 +1513,14 @@
return;
}
// We need to ask the user pick an appropriate subid for the operation.
- Log.d(TAG, "resolveSubscriptionForOperation isSmsSimPickActivityNeeded is true for package "
- + context.getPackageName());
+ Log.d(TAG, "resolveSubscriptionForOperation isSmsSimPickActivityNeeded is true for calling"
+ + " package. ");
try {
// Create the SMS pick activity and call back once the activity is complete. Can't do
// it here because we do not have access to the activity context that is performing this
// operation.
// Requires that the calling process has the SEND_SMS permission.
- getITelephony().enqueueSmsPickResult(context.getOpPackageName(),
+ getITelephony().enqueueSmsPickResult(null,
new IIntegerConsumer.Stub() {
@Override
public void accept(int subId) {
@@ -1539,6 +1538,13 @@
}
}
+ /**
+ * To check the SDK version for SmsManager.sendResolverResult method.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
+ private static final long GET_TARGET_SDK_VERSION_CODE_CHANGE = 145147528L;
+
private void sendResolverResult(SubscriptionResolverResult resolverResult, int subId,
boolean pickActivityShown) {
if (SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -1546,7 +1552,8 @@
return;
}
- if (getTargetSdkVersion() <= Build.VERSION_CODES.P && !pickActivityShown) {
+ if (!Compatibility.isChangeEnabled(GET_TARGET_SDK_VERSION_CODE_CHANGE)
+ && !pickActivityShown) {
// Do not fail, return a success with an INVALID subid for apps targeting P or below
// that tried to perform an operation and the SMS disambiguation dialog was never shown,
// as these applications may not have been written to handle the failure case properly.
@@ -1559,19 +1566,6 @@
}
}
- private static int getTargetSdkVersion() {
- final Context context = ActivityThread.currentApplication().getApplicationContext();
- int targetSdk;
- try {
- targetSdk = context.getPackageManager().getApplicationInfo(
- context.getOpPackageName(), 0).targetSdkVersion;
- } catch (PackageManager.NameNotFoundException e) {
- // Default to old behavior if we can not find this.
- targetSdk = -1;
- }
- return targetSdk;
- }
-
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(
TelephonyFrameworkInitializer
@@ -1657,7 +1651,7 @@
ISms iSms = getISmsService();
if (iSms != null) {
success = iSms.copyMessageToIccEfForSubscriber(getSubscriptionId(),
- ActivityThread.currentPackageName(),
+ null,
status, pdu, smsc);
}
} catch (RemoteException ex) {
@@ -1698,7 +1692,7 @@
ISms iSms = getISmsService();
if (iSms != null) {
success = iSms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
- ActivityThread.currentPackageName(),
+ null,
messageIndex, STATUS_ON_ICC_FREE, null /* pdu */);
}
} catch (RemoteException ex) {
@@ -1741,7 +1735,7 @@
ISms iSms = getISmsService();
if (iSms != null) {
success = iSms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
- ActivityThread.currentPackageName(),
+ null,
messageIndex, newStatus, pdu);
}
} catch (RemoteException ex) {
@@ -1793,7 +1787,7 @@
if (iSms != null) {
records = iSms.getAllMessagesFromIccEfForSubscriber(
getSubscriptionId(),
- ActivityThread.currentPackageName());
+ null);
}
} catch (RemoteException ex) {
// ignore it
@@ -2617,7 +2611,7 @@
try {
ISms iccSms = getISmsServiceOrThrow();
return iccSms.createAppSpecificSmsToken(getSubscriptionId(),
- ActivityThread.currentPackageName(), intent);
+ null, intent);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
@@ -2737,7 +2731,7 @@
try {
ISms iccSms = getISmsServiceOrThrow();
return iccSms.createAppSpecificSmsTokenWithPackageInfo(getSubscriptionId(),
- ActivityThread.currentPackageName(), prefixes, intent);
+ null, prefixes, intent);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
@@ -2828,7 +2822,7 @@
ISms iccISms = getISmsServiceOrThrow();
if (iccISms != null) {
return iccISms.checkSmsShortCodeDestination(getSubscriptionId(),
- ActivityThread.currentPackageName(), null, destAddress, countryIso);
+ null, null, destAddress, countryIso);
}
} catch (RemoteException e) {
Log.e(TAG, "checkSmsShortCodeDestination() RemoteException", e);
@@ -2864,7 +2858,7 @@
ISms iSms = getISmsService();
if (iSms != null) {
smsc = iSms.getSmscAddressFromIccEfForSubscriber(
- getSubscriptionId(), ActivityThread.currentPackageName());
+ getSubscriptionId(), null);
}
} catch (RemoteException ex) {
// ignore it
@@ -2898,7 +2892,7 @@
ISms iSms = getISmsService();
if (iSms != null) {
return iSms.setSmscAddressOnIccEfForSubscriber(
- smsc, getSubscriptionId(), ActivityThread.currentPackageName());
+ smsc, getSubscriptionId(), null);
}
} catch (RemoteException ex) {
// ignore it
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index 40a7619..eefbd44 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -332,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
*
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 36f541f..b42ce35 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2232,6 +2232,7 @@
} else {
logd("putPhoneIdAndSubIdExtra: no valid subs");
intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
+ intent.putExtra(EXTRA_SLOT_INDEX, phoneId);
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2f0ea81..56ca8c7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -37,7 +37,6 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.WorkerThread;
-import android.app.ActivityThread;
import android.app.PendingIntent;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
@@ -380,8 +379,17 @@
// effort and get the context from the current activity thread.
if (mContext != null) {
return mContext.getOpPackageName();
+ } else {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) return null;
+ try {
+ return telephony.getCurrentPackageName();
+ } catch (RemoteException ex) {
+ return null;
+ } catch (NullPointerException ex) {
+ return null;
+ }
}
- return ActivityThread.currentOpPackageName();
}
private String getFeatureId() {
@@ -2276,6 +2284,16 @@
public static final int PHONE_TYPE_CDMA = PhoneConstants.PHONE_TYPE_CDMA;
/** Phone is via SIP. */
public static final int PHONE_TYPE_SIP = PhoneConstants.PHONE_TYPE_SIP;
+ /** Phone is via IMS. */
+ public static final int PHONE_TYPE_IMS = PhoneConstants.PHONE_TYPE_IMS;
+
+ /**
+ * Phone is via Third Party.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PHONE_TYPE_THIRD_PARTY = PhoneConstants.PHONE_TYPE_THIRD_PARTY;
/**
* Returns the current phone type.
@@ -7649,6 +7667,18 @@
RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA;
/**
+ * The default preferred network mode constant.
+ *
+ * <p> This constant is used in case of nothing is set in
+ * TelephonyProperties#default_network().
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int DEFAULT_PREFERRED_NETWORK_MODE =
+ RILConstants.DEFAULT_PREFERRED_NETWORK_MODE;
+
+ /**
* Get the preferred network type.
* Used for device configuration by some CDMA operators.
*
@@ -10685,6 +10715,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param enabled control enable or disable carrier data.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10711,6 +10742,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param enabled control enable or disable radio.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10737,6 +10769,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param report control start/stop reporting network status.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
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/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/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index cdb95a8..a8e76b9 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2087,6 +2087,11 @@
int getRadioHalVersion();
/**
+ * Get the current calling package name.
+ */
+ String getCurrentPackageName();
+
+ /**
* Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present
* on the UICC card.
* @hide
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/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 656628eb..8cc8cf4 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -18,10 +18,13 @@
import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -36,12 +39,14 @@
import android.net.ConnectivityModuleConnector;
import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener;
import android.os.Handler;
+import android.os.SystemProperties;
import android.os.test.TestLooper;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
import androidx.test.InstrumentationRegistry;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.PackageWatchdog.HealthCheckState;
import com.android.server.PackageWatchdog.MonitoredPackage;
import com.android.server.PackageWatchdog.PackageHealthObserver;
@@ -54,11 +59,15 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -88,6 +97,8 @@
private PackageManager mMockPackageManager;
@Captor
private ArgumentCaptor<ConnectivityModuleHealthListener> mConnectivityModuleCallbackCaptor;
+ private MockitoSession mSession;
+ private HashMap<String, String> mSystemSettingsMap;
@Before
public void setUp() throws Exception {
@@ -104,11 +115,47 @@
res.setLongVersionCode(VERSION_CODE);
return res;
});
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
+ .startMocking();
+ mSystemSettingsMap = new HashMap<>();
+
+
+ // Mock SystemProperties setter and various getters
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ String value = invocationOnMock.getArgument(1);
+
+ mSystemSettingsMap.put(key, value);
+ return null;
+ }
+ ).when(() -> SystemProperties.set(anyString(), anyString()));
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ int defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Integer.parseInt(storedValue);
+ }
+ ).when(() -> SystemProperties.getInt(anyString(), anyInt()));
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ long defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Long.parseLong(storedValue);
+ }
+ ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
}
@After
public void tearDown() throws Exception {
dropShellPermissions();
+ mSession.finishMocking();
}
@Test
@@ -968,6 +1015,54 @@
assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty();
}
+
+ /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
+ @Test
+ public void testBootLoopDetection_meetsThreshold() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
+ watchdog.registerHealthObserver(bootObserver);
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
+ watchdog.noteBoot();
+ }
+ assertThat(bootObserver.mitigatedBootLoop()).isTrue();
+ }
+
+
+ /**
+ * Ensure that boot loop mitigation is not done when the number of boots does not meet the
+ * threshold.
+ */
+ @Test
+ public void testBootLoopDetection_doesNotMeetThreshold() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
+ watchdog.registerHealthObserver(bootObserver);
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
+ watchdog.noteBoot();
+ }
+ assertThat(bootObserver.mitigatedBootLoop()).isFalse();
+ }
+
+ /**
+ * Ensure that boot loop mitigation is done for the observer with the lowest user impact
+ */
+ @Test
+ public void testBootLoopMitigationDoneForLowestUserImpact() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1);
+ bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW);
+ TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
+ bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+ watchdog.registerHealthObserver(bootObserver1);
+ watchdog.registerHealthObserver(bootObserver2);
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
+ watchdog.noteBoot();
+ }
+ assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
+ assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
+ }
+
private void adoptShellPermissions(String... permissions) {
InstrumentationRegistry
.getInstrumentation()
@@ -1046,6 +1141,7 @@
private int mLastFailureReason;
private boolean mIsPersistent = false;
private boolean mMayObservePackages = false;
+ private boolean mMitigatedBootLoop = false;
final List<String> mHealthCheckFailedPackages = new ArrayList<>();
final List<String> mMitigatedPackages = new ArrayList<>();
@@ -1082,6 +1178,19 @@
return mMayObservePackages;
}
+ public int onBootLoop() {
+ return mImpact;
+ }
+
+ public boolean executeBootLoopMitigation() {
+ mMitigatedBootLoop = true;
+ return true;
+ }
+
+ public boolean mitigatedBootLoop() {
+ return mMitigatedBootLoop;
+ }
+
public int getLastFailureReason() {
return mLastFailureReason;
}
@@ -1090,6 +1199,10 @@
mIsPersistent = persistent;
}
+ public void setImpact(int impact) {
+ mImpact = impact;
+ }
+
public void setMayObservePackages(boolean mayObservePackages) {
mMayObservePackages = mayObservePackages;
}
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 091edd4..98e7b4e 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -19,7 +19,10 @@
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_resources: [
+ ":com.android.apex.apkrollback.test_v2",
+ ":com.android.apex.apkrollback.test_v2Crashing"
+ ],
}
java_test_host {
@@ -79,4 +82,14 @@
key: "com.android.apex.apkrollback.test.key",
apps: ["TestAppAv2"],
installable: false,
+}
+
+apex {
+ name: "com.android.apex.apkrollback.test_v2Crashing",
+ manifest: "testdata/manifest_v2.json",
+ androidManifest: "testdata/AndroidManifest.xml",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.apkrollback.test.key",
+ apps: ["TestAppACrashingV2"],
+ installable: false,
}
\ No newline at end of file
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index f6699fa..5a92d68 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -392,9 +392,6 @@
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
Long.toString(expirationTime), false /* makeDefault*/);
- // Pull the new expiration time from DeviceConfig
- rm.reloadPersistedData();
-
// Uninstall TestApp.A
Uninstall.packages(TestApp.A);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
@@ -457,9 +454,6 @@
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
Long.toString(expirationTime), false /* makeDefault*/);
- // Pull the new expiration time from DeviceConfig
- rm.reloadPersistedData();
-
// Install app A with rollback enabled
Uninstall.packages(TestApp.A);
Install.single(TestApp.A1).commit();
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index ce20311..80491cd 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -500,8 +500,9 @@
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");
+ private static final TestApp TEST_APEX_WITH_APK_V2_CRASHING = new TestApp(
+ "TestApexWithApkV2Crashing", APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true,
+ APK_IN_APEX_TESTAPEX_NAME + "_v2Crashing.apex");
@Test
public void testRollbackApexWithApk_Phase1() throws Exception {
@@ -523,7 +524,7 @@
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));
+ Rollback.from(TestApp.A, 0).to(TestApp.A1));
RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2);
RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
@@ -531,7 +532,7 @@
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));
+ Rollback.from(TestApp.A, 0).to(TestApp.A1));
assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2);
assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
@@ -548,6 +549,41 @@
InstallUtils.processUserData(TestApp.A);
}
+ /**
+ * Installs an apex with an apk that can crash.
+ */
+ @Test
+ public void testRollbackApexWithApkCrashing_Phase1() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ int sessionId = Install.single(TEST_APEX_WITH_APK_V2_CRASHING).setStaged()
+ .setEnableRollback().commit();
+ InstallUtils.waitForSessionReady(sessionId);
+ }
+
+ /**
+ * Verifies rollback has been enabled successfully. Then makes TestApp.A crash.
+ */
+ @Test
+ public void testRollbackApexWithApkCrashing_Phase2() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+ RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME);
+ assertThat(available).isStaged();
+ assertThat(available).packagesContainsExactly(
+ Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
+ Rollback.from(TestApp.A, 0).to(TestApp.A1));
+
+ // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
+ RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
+ }
+
+ @Test
+ public void testRollbackApexWithApkCrashing_Phase3() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ }
+
private static void runShellCommand(String cmd) {
ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
.executeShellCommand(cmd);
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 181e29a..672cbb0 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -261,6 +261,34 @@
runPhase("testRollbackApexWithApk_Phase3");
}
+ /**
+ * Tests that RollbackPackageHealthObserver is observing apk-in-apex.
+ */
+ @Test
+ public void testRollbackApexWithApkCrashing() throws Exception {
+ getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A");
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+ final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
+ final File apex = buildHelper.getTestFile(fileName);
+ if (!getDevice().isAdbRoot()) {
+ getDevice().enableAdbRoot();
+ }
+ getDevice().remountSystemWritable();
+ assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+ getDevice().reboot();
+
+ // Install an apex with apk that crashes
+ runPhase("testRollbackApexWithApkCrashing_Phase1");
+ getDevice().reboot();
+ // Verify apex was installed and then crash the apk
+ runPhase("testRollbackApexWithApkCrashing_Phase2");
+ // Wait for crash to trigger rollback
+ assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+ getDevice().waitForDeviceAvailable();
+ // Verify rollback occurred due to crash of apk-in-apex
+ runPhase("testRollbackApexWithApkCrashing_Phase3");
+ }
+
private void crashProcess(String processName, int numberOfCrashes) throws Exception {
String pid = "";
String lastPid = "invalid";
diff --git a/tests/WindowInsetsTests/Android.bp b/tests/WindowInsetsTests/Android.bp
new file mode 100644
index 0000000..12395e7
--- /dev/null
+++ b/tests/WindowInsetsTests/Android.bp
@@ -0,0 +1,22 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+ name: "WindowInsetsTests",
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+ certificate: "platform",
+ platform_apis: true,
+}
+
diff --git a/tests/WindowInsetsTests/AndroidManifest.xml b/tests/WindowInsetsTests/AndroidManifest.xml
new file mode 100644
index 0000000..8d33f70
--- /dev/null
+++ b/tests/WindowInsetsTests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (018C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.test.windowinsetstests">
+
+ <application android:label="@string/activity_title">
+ <activity android:name=".WindowInsetsActivity"
+ android:theme="@android:style/Theme.Material"
+ android:windowSoftInputMode="adjustResize">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/WindowInsetsTests/res/layout/window_inset_activity.xml b/tests/WindowInsetsTests/res/layout/window_inset_activity.xml
new file mode 100644
index 0000000..38e0029
--- /dev/null
+++ b/tests/WindowInsetsTests/res/layout/window_inset_activity.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:id="@+id/root">
+
+ <Button
+ android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="Hello insets" />
+
+</LinearLayout>
+
diff --git a/tests/WindowInsetsTests/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml
new file mode 100644
index 0000000..242823d
--- /dev/null
+++ b/tests/WindowInsetsTests/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <string name="activity_title">Window Insets Tests</string>
+</resources>
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
new file mode 100644
index 0000000..b8b2de5
--- /dev/null
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.google.android.test.windowinsetstests;
+
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.graphics.Insets;
+import android.os.Bundle;
+import android.util.Property;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsetsAnimationCallback;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowInsetsAnimationController;
+
+import com.google.android.test.windowinsetstests.R;
+
+public class WindowInsetsActivity extends Activity {
+
+ private View mRoot;
+ private View mButton;
+
+ private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> {
+
+ private final View mViewToAnimate;
+ private final Insets mShowingInsets;
+
+ public InsetsProperty(View viewToAnimate, Insets showingInsets) {
+ super(Insets.class, "Insets");
+ mViewToAnimate = viewToAnimate;
+ mShowingInsets = showingInsets;
+ }
+
+ @Override
+ public Insets get(WindowInsetsAnimationController object) {
+ return object.getCurrentInsets();
+ }
+
+ @Override
+ public void set(WindowInsetsAnimationController object, Insets value) {
+ object.setInsetsAndAlpha(value, 1.0f, 0.5f);
+ if (mShowingInsets.bottom != 0) {
+ mViewToAnimate.setTranslationY(mShowingInsets.bottom - value.bottom);
+ } else if (mShowingInsets.right != 0) {
+ mViewToAnimate.setTranslationX(mShowingInsets.right - value.right);
+ } else if (mShowingInsets.left != 0) {
+ mViewToAnimate.setTranslationX(value.left - mShowingInsets.left);
+ }
+ }
+ };
+
+ float showY;
+ float hideY;
+ InsetsAnimation imeAnim;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.window_inset_activity);
+ mRoot = findViewById(R.id.root);
+ mButton = findViewById(R.id.button);
+ mButton.setOnClickListener(v -> {
+ if (!v.getRootWindowInsets().isVisible(Type.ime())) {
+ v.getWindowInsetsController().show(Type.ime());
+ } else {
+ v.getWindowInsetsController().hide(Type.ime());
+ }
+ });
+ mRoot.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ if (imeAnim == null) {
+ return;
+ }
+ if (mRoot.getRootWindowInsets().isVisible(Type.ime())) {
+ showY = mButton.getTop();
+ } else {
+ hideY = mButton.getTop();
+ }
+ });
+ mRoot.setWindowInsetsAnimationCallback(new WindowInsetsAnimationCallback() {
+
+ @Override
+ public void onPrepare(InsetsAnimation animation) {
+ if ((animation.getTypeMask() & Type.ime()) != 0) {
+ imeAnim = animation;
+ }
+ if (mRoot.getRootWindowInsets().isVisible(Type.ime())) {
+ showY = mButton.getTop();
+ } else {
+ hideY = mButton.getTop();
+ }
+ }
+
+ @Override
+ public WindowInsets onProgress(WindowInsets insets) {
+ mButton.setY(hideY + (showY - hideY) * imeAnim.getInterpolatedFraction());
+ return insets;
+ }
+
+ @Override
+ public AnimationBounds onStart(InsetsAnimation animation,
+ AnimationBounds bounds) {
+ return bounds;
+ }
+
+ @Override
+ public void onFinish(InsetsAnimation animation) {
+ imeAnim = null;
+ }
+ });
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ TypeEvaluator<Insets> evaluator = (fraction, startValue, endValue) -> Insets.of(
+ (int)(startValue.left + fraction * (endValue.left - startValue.left)),
+ (int)(startValue.top + fraction * (endValue.top - startValue.top)),
+ (int)(startValue.right + fraction * (endValue.right - startValue.right)),
+ (int)(startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
+
+ WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
+ @Override
+ public void onReady(WindowInsetsAnimationController controller, int types) {
+ ObjectAnimator animator = ObjectAnimator.ofObject(controller,
+ new InsetsProperty(findViewById(R.id.button),
+ controller.getShownStateInsets()),
+ evaluator, controller.getShownStateInsets(),
+ controller.getHiddenStateInsets());
+ animator.setRepeatCount(ValueAnimator.INFINITE);
+ animator.setRepeatMode(ValueAnimator.REVERSE);
+ animator.start();
+ }
+
+ @Override
+ public void onCancelled() {
+
+ }
+ };
+ }
+}
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 8d99ac7..8eac3ea 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -234,6 +234,7 @@
try {
mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
mTestableLooper = new TestableLooper(mLooper, false);
+ mTestableLooper.getLooper().getThread().setName(test.getClass().getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 58e232c..cbce8a5 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -59,10 +59,22 @@
dst[i] = 0;
}
+static bool cmp_style_ids(ResourceId a, ResourceId b) {
+ // If one of a and b is from the framework package (package ID 0x01), and the
+ // other is a dynamic ID (package ID 0x00), then put the dynamic ID after the
+ // framework ID. This ensures that when AssetManager resolves the dynamic IDs,
+ // they will be in sorted order as expected by AssetManager.
+ if ((a.package_id() == kFrameworkPackageId && b.package_id() == 0x00) ||
+ (a.package_id() == 0x00 && b.package_id() == kFrameworkPackageId)) {
+ return b < a;
+ }
+ return a < b;
+}
+
static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) {
if (a.key.id) {
if (b.key.id) {
- return a.key.id.value() < b.key.id.value();
+ return cmp_style_ids(a.key.id.value(), b.key.id.value());
}
return true;
} else if (!b.key.id) {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 8fbdd7f..af2293f 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -431,6 +431,47 @@
EXPECT_EQ("lib", iter->second);
}
+TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) {
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("lib", 0x00)
+ .AddValue("lib:style/Theme",
+ ResourceId(0x00030001),
+ test::StyleBuilder()
+ .AddItem("lib:attr/bar", ResourceId(0x00010002),
+ ResourceUtils::TryParseInt("2"))
+ .AddItem("lib:attr/foo", ResourceId(0x00010001),
+ ResourceUtils::TryParseInt("1"))
+ .AddItem("android:attr/bar", ResourceId(0x01010002),
+ ResourceUtils::TryParseInt("4"))
+ .AddItem("android:attr/foo", ResourceId(0x01010001),
+ ResourceUtils::TryParseInt("3"))
+ .Build())
+ .Build();
+ ResourceTable result;
+ ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
+
+ Maybe<ResourceTable::SearchResult> search_result =
+ result.FindResource(test::ParseNameOrDie("lib:style/Theme"));
+ ASSERT_TRUE(search_result);
+ EXPECT_EQ(0x00u, search_result.value().package->id.value());
+ EXPECT_EQ(0x03u, search_result.value().type->id.value());
+ EXPECT_EQ(0x01u, search_result.value().entry->id.value());
+ ASSERT_EQ(1u, search_result.value().entry->values.size());
+ Value* value = search_result.value().entry->values[0]->value.get();
+ Style* style = ValueCast<Style>(value);
+ ASSERT_TRUE(style);
+ ASSERT_EQ(4u, style->entries.size());
+ // Ensure the attributes from the shared library come after the items from
+ // android.
+ EXPECT_EQ(0x01010001, style->entries[0].key.id.value());
+ EXPECT_EQ(0x01010002, style->entries[1].key.id.value());
+ EXPECT_EQ(0x00010001, style->entries[2].key.id.value());
+ EXPECT_EQ(0x00010002, style->entries[3].key.id.value());
+}
+
TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) {
std::unique_ptr<IAaptContext> context =
test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 27960c8..954d401 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -349,6 +349,7 @@
}
return true;
});
+ manifest_action["uses-sdk"]["extension-sdk"];
// Instrumentation actions.
manifest_action["instrumentation"].Action(RequiredNameIsJavaClassName);
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 99a26dc..3c55237 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -53,36 +53,38 @@
val executor = newThreadPool()
- command.javaSourceArgs.map { path ->
- executor.submitCallable {
- val transformer = SourceTransformer(command.protoLogImplClassNameArg,
- command.protoLogCacheClassNameArg, processor)
- val file = File(path)
- val text = injector.readText(file)
- val outSrc = try {
- val code = tryParse(text, path)
- if (containsProtoLogText(text, command.protoLogClassNameArg)) {
- transformer.processClass(text, path, packagePath(file, code), code)
- } else {
+ try {
+ command.javaSourceArgs.map { path ->
+ executor.submitCallable {
+ val transformer = SourceTransformer(command.protoLogImplClassNameArg,
+ command.protoLogCacheClassNameArg, processor)
+ val file = File(path)
+ val text = injector.readText(file)
+ val outSrc = try {
+ val code = tryParse(text, path)
+ if (containsProtoLogText(text, command.protoLogClassNameArg)) {
+ transformer.processClass(text, path, packagePath(file, code), code)
+ } else {
+ text
+ }
+ } catch (ex: ParsingException) {
+ // If we cannot parse this file, skip it (and log why). Compilation will
+ // fail in a subsequent build step.
+ injector.reportParseError(ex)
text
}
- } catch (ex: ParsingException) {
- // If we cannot parse this file, skip it (and log why). Compilation will fail
- // in a subsequent build step.
- injector.reportParseError(ex)
- text
+ path to outSrc
}
- path to outSrc
+ }.map { future ->
+ val (path, outSrc) = future.get()
+ outJar.putNextEntry(ZipEntry(path))
+ outJar.write(outSrc.toByteArray())
+ outJar.closeEntry()
}
- }.map { future ->
- val (path, outSrc) = future.get()
- outJar.putNextEntry(ZipEntry(path))
- outJar.write(outSrc.toByteArray())
- outJar.closeEntry()
+ } finally {
+ executor.shutdown()
}
- executor.shutdown()
-
val cacheSplit = command.protoLogCacheClassNameArg.split(".")
val cacheName = cacheSplit.last()
val cachePackage = cacheSplit.dropLast(1).joinToString(".")
@@ -153,30 +155,32 @@
val executor = newThreadPool()
- command.javaSourceArgs.map { path ->
- executor.submitCallable {
- val file = File(path)
- val text = injector.readText(file)
- if (containsProtoLogText(text, command.protoLogClassNameArg)) {
- try {
- val code = tryParse(text, path)
- builder.findLogCalls(code, path, packagePath(file, code))
- } catch (ex: ParsingException) {
- // If we cannot parse this file, skip it (and log why). Compilation will fail
- // in a subsequent build step.
- injector.reportParseError(ex)
+ try {
+ command.javaSourceArgs.map { path ->
+ executor.submitCallable {
+ val file = File(path)
+ val text = injector.readText(file)
+ if (containsProtoLogText(text, command.protoLogClassNameArg)) {
+ try {
+ val code = tryParse(text, path)
+ builder.findLogCalls(code, path, packagePath(file, code))
+ } catch (ex: ParsingException) {
+ // If we cannot parse this file, skip it (and log why). Compilation will
+ // fail in a subsequent build step.
+ injector.reportParseError(ex)
+ null
+ }
+ } else {
null
}
- } else {
- null
}
+ }.forEach { future ->
+ builder.addLogCalls(future.get() ?: return@forEach)
}
- }.forEach { future ->
- builder.addLogCalls(future.get() ?: return@forEach)
+ } finally {
+ executor.shutdown()
}
- executor.shutdown()
-
val out = injector.fileOutputStream(command.viewerConfigJsonArg)
out.write(builder.build().toByteArray())
out.close()
diff --git a/tools/stats_log_api_gen/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 d14a726..4c9ee85 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -70,6 +70,7 @@
"framework-annotations-lib",
"unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
"unsupportedappusage-annotation", // for dalvik.annotation.compat.UnsupportedAppUsage
+ "framework-telephony-stubs",
],
srcs: [
":framework-wifi-updatable-sources",
@@ -113,8 +114,6 @@
":framework-annotations",
":framework-wifi-updatable-sources",
],
- // This is needed as IOnWifiActivityEnergyInfoListener.aidl in framework-wifi depends on
- // WifiActivityEnergyInfo.aidl in framework-minus-apex
aidl: {
include_dirs: ["frameworks/base/core/java"],
},
@@ -126,6 +125,11 @@
java_library {
name: "framework-wifi-stubs",
srcs: [":framework-wifi-stubs-srcs"],
+ aidl: {
+ export_include_dirs: [
+ "java",
+ ],
+ },
sdk_version: "core_current",
libs: ["android_system_stubs_current"],
installable: false,
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index f490766..67f1663 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -92,6 +92,8 @@
void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin);
+ void setMacRandomizationSettingPasspointEnabled(String fqdn, boolean enable);
+
boolean startScan(String packageName, String featureId);
List<ScanResult> getScanResults(String callingPackage, String callingFeatureId);
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index d4fd903..b2fbb40 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -373,7 +373,6 @@
* ECDHE_ECDSA
* ECDHE_RSA
* </pre>
- * @hide
*/
public static class SuiteBCipher {
private SuiteBCipher() { }
@@ -857,18 +856,6 @@
/**
* @hide
- * For debug: date at which the config was last updated
- */
- public String updateTime;
-
- /**
- * @hide
- * For debug: date at which the config was last updated
- */
- public String creationTime;
-
- /**
- * @hide
* The WiFi configuration is considered to have no internet access for purpose of autojoining
* if there has been a report of it having no internet access, and, it never have had
* internet access in the past.
@@ -1494,12 +1481,6 @@
private String mConnectChoice;
/**
- * The system timestamp when we records the connectChoice. This value is obtained from
- * System.currentTimeMillis
- */
- private long mConnectChoiceTimestamp = INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
-
- /**
* Used to cache the temporary candidate during the network selection procedure. It will be
* kept updating once a new scan result has a higher score than current one
*/
@@ -1602,25 +1583,6 @@
mConnectChoice = newConnectChoice;
}
- /**
- * get the timeStamp when user select a choice over this configuration
- * @return returns when current connectChoice is set (time from System.currentTimeMillis)
- * @hide
- */
- public long getConnectChoiceTimestamp() {
- return mConnectChoiceTimestamp;
- }
-
- /**
- * set the timeStamp when user select a choice over this configuration
- * @param timeStamp, the timestamp set to connectChoiceTimestamp, expected timestamp should
- * be obtained from System.currentTimeMillis
- * @hide
- */
- public void setConnectChoiceTimestamp(long timeStamp) {
- mConnectChoiceTimestamp = timeStamp;
- }
-
/** Get the current Quality network selection status as a String (for debugging). */
@NonNull
public String getNetworkStatusString() {
@@ -1903,7 +1865,6 @@
setCandidate(source.getCandidate());
setCandidateScore(source.getCandidateScore());
setConnectChoice(source.getConnectChoice());
- setConnectChoiceTimestamp(source.getConnectChoiceTimestamp());
setHasEverConnected(source.getHasEverConnected());
}
@@ -1920,7 +1881,6 @@
if (getConnectChoice() != null) {
dest.writeInt(CONNECT_CHOICE_EXISTS);
dest.writeString(getConnectChoice());
- dest.writeLong(getConnectChoiceTimestamp());
} else {
dest.writeInt(CONNECT_CHOICE_NOT_EXISTS);
}
@@ -1939,10 +1899,8 @@
setNetworkSelectionBSSID(in.readString());
if (in.readInt() == CONNECT_CHOICE_EXISTS) {
setConnectChoice(in.readString());
- setConnectChoiceTimestamp(in.readLong());
} else {
setConnectChoice(null);
- setConnectChoiceTimestamp(INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
}
setHasEverConnected(in.readInt() != 0);
}
@@ -2166,9 +2124,6 @@
}
if (mNetworkSelectionStatus.getConnectChoice() != null) {
sbuf.append(" connect choice: ").append(mNetworkSelectionStatus.getConnectChoice());
- sbuf.append(" connect choice set time: ")
- .append(logTimeOfDay(
- mNetworkSelectionStatus.getConnectChoiceTimestamp()));
}
sbuf.append(" hasEverConnected: ")
.append(mNetworkSelectionStatus.getHasEverConnected()).append("\n");
@@ -2180,12 +2135,6 @@
sbuf.append(" numNoInternetAccessReports ");
sbuf.append(this.numNoInternetAccessReports).append("\n");
}
- if (this.updateTime != null) {
- sbuf.append(" update ").append(this.updateTime).append("\n");
- }
- if (this.creationTime != null) {
- sbuf.append(" creation ").append(this.creationTime).append("\n");
- }
if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess");
if (this.ephemeral) sbuf.append(" ephemeral");
if (this.osu) sbuf.append(" osu");
@@ -2732,8 +2681,6 @@
allowAutojoin = source.allowAutojoin;
numNoInternetAccessReports = source.numNoInternetAccessReports;
noInternetAccessExpected = source.noInternetAccessExpected;
- creationTime = source.creationTime;
- updateTime = source.updateTime;
shared = source.shared;
recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus());
mRandomizedMacAddress = source.mRandomizedMacAddress;
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 51b15af..7cd00b9 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -700,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;
@@ -712,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 a8a31eb..ec3de43 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2369,6 +2369,15 @@
}
/**
+ * Query whether the device supports Station (STA) + Access point (AP) concurrency or not.
+ *
+ * @return true if this device supports STA + AP concurrency, false otherwise.
+ */
+ public boolean isStaApConcurrencySupported() {
+ return isFeatureSupported(WIFI_FEATURE_AP_STA);
+ }
+
+ /**
* @deprecated Please use {@link android.content.pm.PackageManager#hasSystemFeature(String)}
* with {@link android.content.pm.PackageManager#FEATURE_WIFI_RTT} and
* {@link android.content.pm.PackageManager#FEATURE_WIFI_AWARE}.
@@ -2606,6 +2615,8 @@
* the same permissions as {@link #getScanResults}. If such access is not allowed,
* {@link WifiInfo#getSSID} will return {@link #UNKNOWN_SSID} and
* {@link WifiInfo#getBSSID} will return {@code "02:00:00:00:00:00"}.
+ * {@link WifiInfo#getPasspointFqdn()} will return null.
+ * {@link WifiInfo#getPasspointProviderFriendlyName()} will return null.
*
* @return the Wi-Fi information, contained in {@link WifiInfo}.
*/
@@ -4232,6 +4243,24 @@
}
/**
+ * Configure MAC randomization setting for a Passpoint profile.
+ * MAC randomization is enabled by default.
+ *
+ * @param fqdn the FQDN (fully qualified domain name) of the passpoint profile.
+ * @param enable true to enable MAC randomization, false to disable MAC randomization.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void setMacRandomizationSettingPasspointEnabled(@NonNull String fqdn, boolean enable) {
+ try {
+ mService.setMacRandomizationSettingPasspointEnabled(fqdn, enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Disable an ephemeral network.
*
* @param ssid in the format of WifiConfiguration's SSID.
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index c0e0890..2fba5a3 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -122,6 +122,12 @@
* Whether the setCredentialSharedWithUser have been called.
*/
private boolean mIsSharedWithUserSet;
+
+ /**
+ * Whether this network is initialized with auto-join enabled (the default) or not.
+ */
+ private boolean mIsInitialAutoJoinEnabled;
+
/**
* Pre-shared key for use with WAPI-PSK networks.
*/
@@ -148,6 +154,7 @@
mIsMetered = false;
mIsSharedWithUser = true;
mIsSharedWithUserSet = false;
+ mIsInitialAutoJoinEnabled = true;
mPriority = UNASSIGNED_PRIORITY;
mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
mWapiPskPassphrase = null;
@@ -440,6 +447,27 @@
return this;
}
+ /**
+ * Specifies whether the suggestion is created with auto-join enabled or disabled. The
+ * user may modify the auto-join configuration of a suggestion directly once the device
+ * associates to the network.
+ * <p>
+ * If auto-join is initialized as disabled the user may still be able to manually connect
+ * to the network. Therefore, disabling auto-join only makes sense if
+ * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which
+ * itself implies a secure (non-open) network.
+ * <p>
+ * If not set, defaults to true (i.e. auto-join is initialized as enabled).
+ *
+ * @param enabled true for initializing with auto-join enabled (the default), false to
+ * initializing with auto-join disabled.
+ * @return Instance of (@link {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setIsInitialAutoJoinEnabled(boolean enabled) {
+ mIsInitialAutoJoinEnabled = enabled;
+ return this;
+ }
+
private void setSecurityParamsInWifiConfiguration(
@NonNull WifiConfiguration configuration) {
if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
@@ -587,7 +615,6 @@
+ "suggestion with Passpoint configuration");
}
wifiConfiguration = buildWifiConfigurationForPasspoint();
-
} else {
if (mSsid == null) {
throw new IllegalStateException("setSsid should be invoked for suggestion");
@@ -608,6 +635,12 @@
}
mIsSharedWithUser = false;
}
+
+ if (!mIsSharedWithUser && !mIsInitialAutoJoinEnabled) {
+ throw new IllegalStateException("Should have not a network with both "
+ + "setIsUserAllowedToManuallyConnect and "
+ + "setIsAutoJoinEnabled set to false");
+ }
}
return new WifiNetworkSuggestion(
@@ -615,7 +648,8 @@
mPasspointConfiguration,
mIsAppInteractionRequired,
mIsUserInteractionRequired,
- mIsSharedWithUser);
+ mIsSharedWithUser,
+ mIsInitialAutoJoinEnabled);
}
}
@@ -642,6 +676,7 @@
* @hide
*/
public final boolean isUserInteractionRequired;
+
/**
* Whether app share credential with the user, allow user use provided credential to
* connect network manually.
@@ -649,6 +684,12 @@
*/
public final boolean isUserAllowedToManuallyConnect;
+ /**
+ * Whether the suggestion will be initialized as auto-joined or not.
+ * @hide
+ */
+ public final boolean isInitialAutoJoinEnabled;
+
/** @hide */
public WifiNetworkSuggestion() {
this.wifiConfiguration = null;
@@ -656,6 +697,7 @@
this.isAppInteractionRequired = false;
this.isUserInteractionRequired = false;
this.isUserAllowedToManuallyConnect = true;
+ this.isInitialAutoJoinEnabled = true;
}
/** @hide */
@@ -663,7 +705,8 @@
@Nullable PasspointConfiguration passpointConfiguration,
boolean isAppInteractionRequired,
boolean isUserInteractionRequired,
- boolean isUserAllowedToManuallyConnect) {
+ boolean isUserAllowedToManuallyConnect,
+ boolean isInitialAutoJoinEnabled) {
checkNotNull(networkConfiguration);
this.wifiConfiguration = networkConfiguration;
this.passpointConfiguration = passpointConfiguration;
@@ -671,6 +714,7 @@
this.isAppInteractionRequired = isAppInteractionRequired;
this.isUserInteractionRequired = isUserInteractionRequired;
this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect;
+ this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled;
}
public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
@@ -682,7 +726,8 @@
in.readParcelable(null), // PasspointConfiguration
in.readBoolean(), // isAppInteractionRequired
in.readBoolean(), // isUserInteractionRequired
- in.readBoolean() // isSharedCredentialWithUser
+ in.readBoolean(), // isSharedCredentialWithUser
+ in.readBoolean() // isAutoJoinEnabled
);
}
@@ -704,6 +749,7 @@
dest.writeBoolean(isAppInteractionRequired);
dest.writeBoolean(isUserInteractionRequired);
dest.writeBoolean(isUserAllowedToManuallyConnect);
+ dest.writeBoolean(isInitialAutoJoinEnabled);
}
@Override
@@ -744,6 +790,7 @@
.append(", isAppInteractionRequired=").append(isAppInteractionRequired)
.append(", isUserInteractionRequired=").append(isUserInteractionRequired)
.append(", isUserAllowedToManuallyConnect=").append(isUserAllowedToManuallyConnect)
+ .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
.append(" ]");
return sb.toString();
}
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 1822e84..7c335fc 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -431,6 +431,13 @@
private boolean mIsAutoJoinEnabled = true;
/**
+ * The mac randomization setting specifies whether a randomized or device MAC address will
+ * be used to connect to the passpoint network. If true, a randomized MAC will be used.
+ * Otherwise, the device MAC address will be used.
+ */
+ private boolean mIsMacRandomizationEnabled = true;
+
+ /**
* Configures the auto-association status of this Passpoint configuration. A value of true
* indicates that the configuration will be considered for auto-connection, a value of false
* indicates that only manual connection will work - the framework will not auto-associate to
@@ -444,6 +451,18 @@
}
/**
+ * Configures the MAC randomization setting for this Passpoint configuration.
+ * If set to true, the framework will use a randomized MAC address to connect to this Passpoint
+ * network. Otherwise, the framework will use the device MAC address.
+ *
+ * @param enabled true to use randomized MAC address, false to use device MAC address.
+ * @hide
+ */
+ public void setMacRandomizationEnabled(boolean enabled) {
+ mIsMacRandomizationEnabled = enabled;
+ }
+
+ /**
* Indicates whether the Passpoint configuration may be auto-connected to by the framework. A
* value of true indicates that auto-connection can happen, a value of false indicates that it
* cannot. However, even when auto-connection is not possible manual connection by the user is
@@ -459,6 +478,19 @@
}
/**
+ * Indicates whether a randomized MAC address or device MAC address will be used for
+ * connections to this Passpoint network. If true, a randomized MAC address will be used.
+ * Otherwise, the device MAC address will be used.
+ *
+ * @return true for MAC randomization enabled. False for disabled.
+ * @hide
+ */
+ @SystemApi
+ public boolean isMacRandomizationEnabled() {
+ return mIsMacRandomizationEnabled;
+ }
+
+ /**
* Constructor for creating PasspointConfiguration with default values.
*/
public PasspointConfiguration() {}
@@ -501,6 +533,7 @@
mAaaServerTrustedNames = source.mAaaServerTrustedNames;
mCarrierId = source.mCarrierId;
mIsAutoJoinEnabled = source.mIsAutoJoinEnabled;
+ mIsMacRandomizationEnabled = source.mIsMacRandomizationEnabled;
}
@Override
@@ -531,6 +564,7 @@
dest.writeBundle(bundle);
dest.writeInt(mCarrierId);
dest.writeBoolean(mIsAutoJoinEnabled);
+ dest.writeBoolean(mIsMacRandomizationEnabled);
}
@Override
@@ -562,6 +596,7 @@
&& mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes
&& mCarrierId == that.mCarrierId
&& mIsAutoJoinEnabled == that.mIsAutoJoinEnabled
+ && mIsMacRandomizationEnabled == that.mIsMacRandomizationEnabled
&& (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null
: mServiceFriendlyNames.equals(that.mServiceFriendlyNames));
}
@@ -572,7 +607,7 @@
mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis,
mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes,
mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes,
- mServiceFriendlyNames, mCarrierId, mIsAutoJoinEnabled);
+ mServiceFriendlyNames, mCarrierId, mIsAutoJoinEnabled, mIsMacRandomizationEnabled);
}
@Override
@@ -627,6 +662,7 @@
}
builder.append("CarrierId:" + mCarrierId);
builder.append("IsAutoJoinEnabled:" + mIsAutoJoinEnabled);
+ builder.append("mIsMacRandomizationEnabled:" + mIsMacRandomizationEnabled);
return builder.toString();
}
@@ -733,6 +769,7 @@
config.setServiceFriendlyNames(friendlyNamesMap);
config.mCarrierId = in.readInt();
config.mIsAutoJoinEnabled = in.readBoolean();
+ config.mIsMacRandomizationEnabled = in.readBoolean();
return config;
}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
index 0de7ba6..dad431c 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 3c13562d..1cf3825 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -187,6 +187,11 @@
}
@Override
+ public void setMacRandomizationSettingPasspointEnabled(String fqdn, boolean enable) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public boolean startScan(String packageName, String featureId) {
throw new UnsupportedOperationException();
}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 5bdc344..983ac82 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -1706,6 +1706,17 @@
verify(mWifiService).allowAutojoinPasspoint(fqdn, true);
}
+ /**
+ * Test behavior of
+ * {@link WifiManager#setMacRandomizationSettingPasspointEnabled(String, boolean)}
+ */
+ @Test
+ public void testSetMacRandomizationSettingPasspointEnabled() throws Exception {
+ final String fqdn = "FullyQualifiedDomainName";
+ mWifiManager.setMacRandomizationSettingPasspointEnabled(fqdn, true);
+ verify(mWifiService).setMacRandomizationSettingPasspointEnabled(fqdn, true);
+ }
+
/**
* Test behavior of {@link WifiManager#disconnect()}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index ac91544..cb1b774 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -32,8 +32,6 @@
*/
@SmallTest
public class WifiNetworkSuggestionTest {
- private static final int TEST_UID = 45677;
- private static final int TEST_UID_OTHER = 45673;
private static final String TEST_SSID = "\"Test123\"";
private static final String TEST_BSSID = "12:12:12:12:12:12";
private static final String TEST_SSID_1 = "\"Test1234\"";
@@ -61,7 +59,8 @@
assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE,
suggestion.wifiConfiguration.meteredOverride);
assertEquals(-1, suggestion.wifiConfiguration.priority);
- assertEquals(false, suggestion.isUserAllowedToManuallyConnect);
+ assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ assertTrue(suggestion.isInitialAutoJoinEnabled);
}
/**
@@ -90,7 +89,8 @@
assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE,
suggestion.wifiConfiguration.meteredOverride);
assertEquals(0, suggestion.wifiConfiguration.priority);
- assertEquals(false, suggestion.isUserAllowedToManuallyConnect);
+ assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ assertTrue(suggestion.isInitialAutoJoinEnabled);
}
/**
@@ -105,6 +105,7 @@
.setSsid(TEST_SSID)
.setWpa2Passphrase(TEST_PRESHARED_KEY)
.setIsUserInteractionRequired(true)
+ .setIsInitialAutoJoinEnabled(false)
.setIsMetered(true)
.build();
@@ -119,6 +120,7 @@
suggestion.wifiConfiguration.meteredOverride);
assertEquals(-1, suggestion.wifiConfiguration.priority);
assertTrue(suggestion.isUserAllowedToManuallyConnect);
+ assertFalse(suggestion.isInitialAutoJoinEnabled);
}
/**
@@ -140,6 +142,7 @@
assertNull(suggestion.wifiConfiguration.preSharedKey);
assertTrue(suggestion.wifiConfiguration.requirePMF);
assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ assertTrue(suggestion.isInitialAutoJoinEnabled);
}
/**
@@ -152,6 +155,7 @@
.setSsid(TEST_SSID)
.setWpa3Passphrase(TEST_PRESHARED_KEY)
.setCredentialSharedWithUser(true)
+ .setIsInitialAutoJoinEnabled(false)
.build();
assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
@@ -161,6 +165,7 @@
suggestion.wifiConfiguration.preSharedKey);
assertTrue(suggestion.wifiConfiguration.requirePMF);
assertTrue(suggestion.isUserAllowedToManuallyConnect);
+ assertFalse(suggestion.isInitialAutoJoinEnabled);
}
@@ -191,6 +196,7 @@
// allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
// here.
assertTrue(suggestion.isUserAllowedToManuallyConnect);
+ assertTrue(suggestion.isInitialAutoJoinEnabled);
}
/**
@@ -526,7 +532,7 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion(
- configuration, null, false, true, true);
+ configuration, null, false, true, true, false);
Parcel parcelW = Parcel.obtain();
suggestion.writeToParcel(parcelW, 0);
@@ -548,6 +554,8 @@
parcelSuggestion.isAppInteractionRequired);
assertEquals(suggestion.isUserInteractionRequired,
parcelSuggestion.isUserInteractionRequired);
+ assertEquals(suggestion.isInitialAutoJoinEnabled,
+ parcelSuggestion.isInitialAutoJoinEnabled);
}
/**
@@ -580,6 +588,8 @@
parcelSuggestion.isAppInteractionRequired);
assertEquals(suggestion.isUserInteractionRequired,
parcelSuggestion.isUserInteractionRequired);
+ assertEquals(suggestion.isInitialAutoJoinEnabled,
+ parcelSuggestion.isInitialAutoJoinEnabled);
}
/**
@@ -593,14 +603,14 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, true, false, true);
+ new WifiNetworkSuggestion(configuration, null, true, false, true, true);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.BSSID = TEST_BSSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, true, true);
+ new WifiNetworkSuggestion(configuration1, null, false, true, true, false);
assertEquals(suggestion, suggestion1);
assertEquals(suggestion.hashCode(), suggestion1.hashCode());
@@ -616,13 +626,13 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, false, false, true);
+ new WifiNetworkSuggestion(configuration, null, false, false, true, false);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID_1;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, false, true);
+ new WifiNetworkSuggestion(configuration1, null, false, false, true, false);
assertNotEquals(suggestion, suggestion1);
}
@@ -638,13 +648,13 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, false, false, true);
+ new WifiNetworkSuggestion(configuration, null, false, false, true, true);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, false, true);
+ new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
assertNotEquals(suggestion, suggestion1);
}
@@ -659,13 +669,13 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, false, false, true);
+ new WifiNetworkSuggestion(configuration, null, false, false, true, true);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, false, true);
+ new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
assertNotEquals(suggestion, suggestion1);
}
@@ -714,9 +724,38 @@
*/
@Test(expected = IllegalStateException.class)
public void testSetIsUserAllowedToManuallyConnectToWithOpenNetwork() {
- WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+ new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID)
.setCredentialSharedWithUser(true)
.build();
}
+
+ /**
+ * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+ * when {@link WifiNetworkSuggestion.Builder#setIsInitialAutoJoinEnabled(boolean)} to
+ * false on a open network suggestion.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testSetIsAutoJoinDisabledWithOpenNetwork() {
+ new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setIsInitialAutoJoinEnabled(false)
+ .build();
+ }
+
+ /**
+ * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+ * when set both {@link WifiNetworkSuggestion.Builder#setIsInitialAutoJoinEnabled(boolean)}
+ * and {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} (boolean)}
+ * to false on a network suggestion.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testSetIsAutoJoinDisabledWithSecureNetworkNotSharedWithUser() {
+ new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setWpa2Passphrase(TEST_PRESHARED_KEY)
+ .setCredentialSharedWithUser(false)
+ .setIsInitialAutoJoinEnabled(false)
+ .build();
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 94054fd..603e78b 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -172,6 +172,7 @@
assertFalse(config.validate());
assertFalse(config.validateForR2());
assertTrue(config.isAutoJoinEnabled());
+ assertTrue(config.isMacRandomizationEnabled());
}
/**