Merge "Delete the staging directory after rule push finishes."
diff --git a/Android.bp b/Android.bp
index a7ac094..4c983be 100644
--- a/Android.bp
+++ b/Android.bp
@@ -268,6 +268,7 @@
":framework-tethering-srcs",
":updatable-media-srcs",
":framework-mediaprovider-sources",
+ ":framework-permission-sources",
":framework-wifi-updatable-sources",
":ike-srcs",
]
@@ -409,6 +410,7 @@
filegroup {
name: "libincident_aidl",
srcs: [
+ "core/java/android/os/IIncidentDumpCallback.aidl",
"core/java/android/os/IIncidentManager.aidl",
"core/java/android/os/IIncidentReportStatusListener.aidl",
],
@@ -437,8 +439,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,6 +467,7 @@
"//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/wifi",
"//frameworks/opt/net/wifi/service",
@@ -488,6 +492,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",
@@ -637,6 +642,7 @@
name: "framework-ike-shared-srcs",
visibility: ["//frameworks/opt/net/ike"],
srcs: [
+ "core/java/android/annotation/StringDef.java",
"core/java/android/net/annotations/PolicyDirection.java",
"core/java/com/android/internal/util/IState.java",
"core/java/com/android/internal/util/State.java",
@@ -1124,6 +1130,7 @@
"core/java/android/util/TimeUtils.java",
"core/java/com/android/internal/os/SomeArgs.java",
"core/java/com/android/internal/util/AsyncChannel.java",
+ "core/java/com/android/internal/util/AsyncService.java",
"core/java/com/android/internal/util/BitwiseInputStream.java",
"core/java/com/android/internal/util/FastXmlSerializer.java",
"core/java/com/android/internal/util/HexDump.java",
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 5945fb3..d746ea6 100644
--- a/apex/permission/Android.bp
+++ b/apex/permission/Android.bp
@@ -22,6 +22,11 @@
name: "com.android.permission-defaults",
key: "com.android.permission.key",
certificate: ":com.android.permission.certificate",
+ java_libs: [
+ "framework-permission",
+ "service-permission",
+ ],
+ apps: ["PermissionController"],
}
apex_key {
diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp
new file mode 100644
index 0000000..8b03da3
--- /dev/null
+++ b/apex/permission/framework/Android.bp
@@ -0,0 +1,60 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+filegroup {
+ name: "framework-permission-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ],
+ path: "java",
+}
+
+java_library {
+ name: "framework-permission",
+ srcs: [
+ ":framework-permission-sources",
+ ],
+ sdk_version: "system_current",
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ hostdex: true,
+ installable: true,
+ visibility: [
+ "//frameworks/base/apex/permission:__subpackages__",
+ ],
+}
+
+droidstubs {
+ name: "framework-permission-stubs-sources",
+ srcs: [
+ ":framework-annotations",
+ ":framework-permission-sources",
+ ],
+ sdk_version: "system_current",
+ defaults: [
+ "framework-module-stubs-defaults-systemapi",
+ ],
+}
+
+java_library {
+ name: "framework-permission-stubs",
+ srcs: [
+ ":framework-permission-stubs-sources",
+ ],
+ sdk_version: "system_current",
+ installable: false,
+}
diff --git a/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 adc16d2..6444b4e 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -744,23 +744,6 @@
return null;
}
- private void pullKernelWakelock(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- final KernelWakelockStats wakelockStats =
- mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
- for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
- String name = ent.getKey();
- KernelWakelockStats.Entry kws = ent.getValue();
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeString(name);
- e.writeInt(kws.mCount);
- e.writeInt(kws.mVersion);
- e.writeLong(kws.mTotalTime);
- pulledData.add(e);
- }
- }
-
private void pullWifiActivityInfo(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
@@ -1421,21 +1404,6 @@
}
}
- private void pullBuildInformation(int tagId,
- long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeString(Build.FINGERPRINT);
- e.writeString(Build.BRAND);
- e.writeString(Build.PRODUCT);
- e.writeString(Build.DEVICE);
- e.writeString(Build.VERSION.RELEASE);
- e.writeString(Build.ID);
- e.writeString(Build.VERSION.INCREMENTAL);
- e.writeString(Build.TYPE);
- e.writeString(Build.TAGS);
- pulledData.add(e);
- }
-
private BatteryStatsHelper getBatteryStatsHelper() {
if (mBatteryStatsHelper == null) {
final long callingToken = Binder.clearCallingIdentity();
@@ -2027,11 +1995,6 @@
long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
switch (tagId) {
- case StatsLog.KERNEL_WAKELOCK: {
- pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
case StatsLog.WIFI_ACTIVITY_INFO: {
pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
break;
@@ -2135,11 +2098,6 @@
break;
}
- case StatsLog.BUILD_INFORMATION: {
- pullBuildInformation(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
case StatsLog.PROCESS_CPU_TIME: {
pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
break;
diff --git a/api/current.txt b/api/current.txt
index 16786ff..d5ece2e 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";
@@ -11788,6 +11791,7 @@
method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo);
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo);
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public CharSequence getBackgroundPermissionButtonLabel();
method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
@@ -29651,7 +29655,7 @@
method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
method public android.net.NetworkRequest.Builder removeCapability(int);
method public android.net.NetworkRequest.Builder removeTransportType(int);
- method public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
+ method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
}
@@ -29750,6 +29754,19 @@
method public void onStopped();
}
+ public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getSubscriptionId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.TelephonyNetworkSpecifier> CREATOR;
+ }
+
+ public static final class TelephonyNetworkSpecifier.Builder {
+ ctor public TelephonyNetworkSpecifier.Builder();
+ method @NonNull public android.net.TelephonyNetworkSpecifier build();
+ method @NonNull public android.net.TelephonyNetworkSpecifier.Builder setSubscriptionId(int);
+ }
+
public class TrafficStats {
ctor public TrafficStats();
method public static void clearThreadStatsTag();
@@ -30507,6 +30524,11 @@
field @Deprecated public static final String[] strings;
}
+ @Deprecated public static class WifiConfiguration.SuiteBCipher {
+ field @Deprecated public static final int ECDHE_ECDSA = 0; // 0x0
+ field @Deprecated public static final int ECDHE_RSA = 1; // 0x1
+ }
+
public class WifiEnterpriseConfig implements android.os.Parcelable {
ctor public WifiEnterpriseConfig();
ctor public WifiEnterpriseConfig(android.net.wifi.WifiEnterpriseConfig);
@@ -30632,6 +30654,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();
@@ -39044,7 +39067,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
@@ -44061,6 +44084,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();
@@ -44144,6 +44168,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();
@@ -45153,6 +45178,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);
@@ -45846,6 +45904,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);
@@ -45863,6 +45922,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
@@ -46461,6 +46521,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/system-current.txt b/api/system-current.txt
index 056fb08..d9d9a4d 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -242,8 +242,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 +809,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);
@@ -5194,6 +5195,10 @@
field @NonNull public final String specifier;
}
+ public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
+ }
+
public class TrafficStats {
method public static void setThreadStatsTagApp();
method public static void setThreadStatsTagBackup();
@@ -8011,6 +8016,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";
@@ -8068,6 +8075,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();
@@ -8093,6 +8101,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";
@@ -8105,13 +8114,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";
}
@@ -9463,6 +9480,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();
@@ -10344,7 +10366,9 @@
public class ServiceState implements android.os.Parcelable {
method @NonNull public android.telephony.ServiceState createLocationInfoSanitizedCopy(boolean);
method public void fillInNotifierBundle(@NonNull android.os.Bundle);
+ method public int getDataNetworkType();
method public int getDataRegistrationState();
+ method public boolean getDataRoamingFromRegistration();
method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoList();
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int);
@@ -10502,6 +10526,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);
}
@@ -10722,6 +10747,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";
@@ -10761,6 +10787,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
@@ -10784,6 +10811,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);
@@ -12116,7 +12144,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);
}
@@ -12278,6 +12327,12 @@
method public void onJsResultComplete(android.webkit.JsResult);
}
+ public interface PacProcessor {
+ method @NonNull public static android.webkit.PacProcessor getInstance();
+ method @Nullable public String makeProxyRequest(@NonNull String);
+ method public boolean setProxyScript(@NonNull String);
+ }
+
public class SslErrorHandler extends android.os.Handler {
ctor public SslErrorHandler();
}
@@ -12412,6 +12467,7 @@
method public android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess);
method public android.webkit.CookieManager getCookieManager();
method public android.webkit.GeolocationPermissions getGeolocationPermissions();
+ method @NonNull public default android.webkit.PacProcessor getPacProcessor();
method public android.webkit.ServiceWorkerController getServiceWorkerController();
method public android.webkit.WebViewFactoryProvider.Statics getStatics();
method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
diff --git a/api/test-current.txt b/api/test-current.txt
index 10028f2..4e94ef2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3137,6 +3137,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);
@@ -3267,6 +3276,7 @@
public class ServiceState implements android.os.Parcelable {
method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
+ method public int getDataNetworkType();
method public void setCdmaSystemAndNetworkId(int, int);
method public void setCellBandwidths(int[]);
method public void setChannelNumber(int);
@@ -4662,6 +4672,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/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index cfd77c2..62312d1 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -123,14 +123,17 @@
// ================================================================================
ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
- const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
- const sp<Throttler>& throttler)
+ const sp<Broadcaster>& broadcaster,
+ const sp<Looper>& handlerLooper,
+ const sp<Throttler>& throttler,
+ const vector<BringYourOwnSection*>& registeredSections)
:mLock(),
mWorkDirectory(workDirectory),
mBroadcaster(broadcaster),
mHandlerLooper(handlerLooper),
mBacklogDelay(DEFAULT_DELAY_NS),
mThrottler(throttler),
+ mRegisteredSections(registeredSections),
mBatch(new ReportBatch()) {
}
@@ -185,7 +188,7 @@
return;
}
- sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);
+ sp<Reporter> reporter = new Reporter(mWorkDirectory, batch, mRegisteredSections);
// Take the report, which might take a while. More requests might queue
// up while we're doing this, and we'll handle them in their next batch.
@@ -237,7 +240,7 @@
mWorkDirectory = new WorkDirectory();
mBroadcaster = new Broadcaster(mWorkDirectory);
mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
- mThrottler);
+ mThrottler, mRegisteredSections);
mBroadcaster->setHandler(mHandler);
}
@@ -327,6 +330,11 @@
incidentArgs.addSection(id);
}
}
+ for (const Section* section : mRegisteredSections) {
+ if (!section_requires_specific_mention(section->id)) {
+ incidentArgs.addSection(section->id);
+ }
+ }
// The ReportRequest takes ownership of the fd, so we need to dup it.
int fd = dup(stream.get());
@@ -339,6 +347,45 @@
return Status::ok();
}
+Status IncidentService::registerSection(const int id, const String16& name16,
+ const sp<IIncidentDumpCallback>& callback) {
+ const char* name = String8(name16).c_str();
+ ALOGI("Register section %d: %s", id, name);
+ if (callback == nullptr) {
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+ }
+ const uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ for (int i = 0; i < mRegisteredSections.size(); i++) {
+ if (mRegisteredSections.at(i)->id == id) {
+ if (mRegisteredSections.at(i)->uid != callingUid) {
+ ALOGW("Error registering section %d: calling uid does not match", id);
+ return Status::fromExceptionCode(Status::EX_SECURITY);
+ }
+ mRegisteredSections.at(i) = new BringYourOwnSection(id, name, callingUid, callback);
+ return Status::ok();
+ }
+ }
+ mRegisteredSections.push_back(new BringYourOwnSection(id, name, callingUid, callback));
+ return Status::ok();
+}
+
+Status IncidentService::unregisterSection(const int id) {
+ ALOGI("Unregister section %d", id);
+ uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) {
+ if ((*it)->id == id) {
+ if ((*it)->uid != callingUid) {
+ ALOGW("Error unregistering section %d: calling uid does not match", id);
+ return Status::fromExceptionCode(Status::EX_SECURITY);
+ }
+ mRegisteredSections.erase(it);
+ return Status::ok();
+ }
+ }
+ ALOGW("Section %d not found", id);
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+}
+
Status IncidentService::systemRunning() {
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index b2c7f23..49fc566 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -40,12 +40,16 @@
using namespace android::binder;
using namespace android::os;
+class BringYourOwnSection;
+
// ================================================================================
class ReportHandler : public MessageHandler {
public:
ReportHandler(const sp<WorkDirectory>& workDirectory,
- const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
- const sp<Throttler>& throttler);
+ const sp<Broadcaster>& broadcaster,
+ const sp<Looper>& handlerLooper,
+ const sp<Throttler>& throttler,
+ const vector<BringYourOwnSection*>& registeredSections);
virtual ~ReportHandler();
virtual void handleMessage(const Message& message);
@@ -79,6 +83,8 @@
nsecs_t mBacklogDelay;
sp<Throttler> mThrottler;
+ const vector<BringYourOwnSection*>& mRegisteredSections;
+
sp<ReportBatch> mBatch;
/**
@@ -126,6 +132,11 @@
virtual Status reportIncidentToDumpstate(unique_fd stream,
const sp<IIncidentReportStatusListener>& listener);
+ virtual Status registerSection(int id, const String16& name,
+ const sp<IIncidentDumpCallback>& callback);
+
+ virtual Status unregisterSection(int id);
+
virtual Status systemRunning();
virtual Status getIncidentReportList(const String16& pkg, const String16& cls,
@@ -149,6 +160,7 @@
sp<Broadcaster> mBroadcaster;
sp<ReportHandler> mHandler;
sp<Throttler> mThrottler;
+ vector<BringYourOwnSection*> mRegisteredSections;
/**
* Commands print out help.
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 02b6bbe..aa40f85 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -364,7 +364,6 @@
mSectionBufferSuccess = false;
mHadError = false;
mSectionErrors.clear();
-
}
void ReportWriter::setSectionStats(const FdBuffer& buffer) {
@@ -470,10 +469,13 @@
// ================================================================================
-Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch)
+Reporter::Reporter(const sp<WorkDirectory>& workDirectory,
+ const sp<ReportBatch>& batch,
+ const vector<BringYourOwnSection*>& registeredSections)
:mWorkDirectory(workDirectory),
mWriter(batch),
- mBatch(batch) {
+ mBatch(batch),
+ mRegisteredSections(registeredSections) {
}
Reporter::~Reporter() {
@@ -580,50 +582,15 @@
// For each of the report fields, see if we need it, and if so, execute the command
// and report to those that care that we're doing it.
for (const Section** section = SECTION_LIST; *section; section++) {
- const int sectionId = (*section)->id;
-
- // If nobody wants this section, skip it.
- if (!mBatch->containsSection(sectionId)) {
- continue;
- }
-
- ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string());
- IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections();
-
- // Notify listener of starting
- mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
- listener->onReportSectionStatus(
- sectionId, IIncidentReportStatusListener::STATUS_STARTING);
- });
-
- // Go get the data and write it into the file descriptors.
- mWriter.startSection(sectionId);
- err = (*section)->Execute(&mWriter);
- mWriter.endSection(sectionMetadata);
-
- // Sections returning errors are fatal. Most errors should not be fatal.
- if (err != NO_ERROR) {
- mWriter.error((*section), err, "Section failed. Stopping report.");
+ if (execute_section(*section, &metadata, reportByteSize) != NO_ERROR) {
goto DONE;
}
+ }
- // The returned max data size is used for throttling too many incident reports.
- (*reportByteSize) += sectionMetadata->report_size_bytes();
-
- // For any requests that failed during this section, remove them now. We do this
- // before calling back about section finished, so listeners do not erroniously get the
- // impression that the section succeeded. But we do it here instead of inside
- // writeSection so that the callback is done from a known context and not from the
- // bowels of a section, where changing the batch could cause odd errors.
- cancel_and_remove_failed_requests();
-
- // Notify listener of finishing
- mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
- listener->onReportSectionStatus(
- sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
- });
-
- ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string());
+ for (const Section* section : mRegisteredSections) {
+ if (execute_section(section, &metadata, reportByteSize) != NO_ERROR) {
+ goto DONE;
+ }
}
DONE:
@@ -681,6 +648,55 @@
ALOGI("Done taking incident report err=%s", strerror(-err));
}
+status_t Reporter::execute_section(const Section* section, IncidentMetadata* metadata,
+ size_t* reportByteSize) {
+ const int sectionId = section->id;
+
+ // If nobody wants this section, skip it.
+ if (!mBatch->containsSection(sectionId)) {
+ return NO_ERROR;
+ }
+
+ ALOGD("Start incident report section %d '%s'", sectionId, section->name.string());
+ IncidentMetadata::SectionStats* sectionMetadata = metadata->add_sections();
+
+ // Notify listener of starting
+ mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+ listener->onReportSectionStatus(
+ sectionId, IIncidentReportStatusListener::STATUS_STARTING);
+ });
+
+ // Go get the data and write it into the file descriptors.
+ mWriter.startSection(sectionId);
+ status_t err = section->Execute(&mWriter);
+ mWriter.endSection(sectionMetadata);
+
+ // Sections returning errors are fatal. Most errors should not be fatal.
+ if (err != NO_ERROR) {
+ mWriter.error(section, err, "Section failed. Stopping report.");
+ return err;
+ }
+
+ // The returned max data size is used for throttling too many incident reports.
+ (*reportByteSize) += sectionMetadata->report_size_bytes();
+
+ // For any requests that failed during this section, remove them now. We do this
+ // before calling back about section finished, so listeners do not erroniously get the
+ // impression that the section succeeded. But we do it here instead of inside
+ // writeSection so that the callback is done from a known context and not from the
+ // bowels of a section, where changing the batch could cause odd errors.
+ cancel_and_remove_failed_requests();
+
+ // Notify listener of finishing
+ mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+ listener->onReportSectionStatus(
+ sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
+ });
+
+ ALOGD("Finish incident report section %d '%s'", sectionId, section->name.string());
+ return NO_ERROR;
+}
+
void Reporter::cancel_and_remove_failed_requests() {
// Handle a failure in the persisted file
if (mPersistedFile != nullptr) {
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index fb3961a..cbc8b13 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -21,6 +21,7 @@
#include "frameworks/base/core/proto/android/os/metadata.pb.h"
#include <android/content/ComponentName.h>
#include <android/os/IIncidentReportStatusListener.h>
+#include <android/os/IIncidentDumpCallback.h>
#include <android/os/IncidentReportArgs.h>
#include <android/util/protobuf.h>
@@ -39,6 +40,7 @@
using namespace android::content;
using namespace android::os;
+class BringYourOwnSection;
class Section;
// ================================================================================
@@ -122,7 +124,7 @@
void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func);
/**
- * Call func(request) for each file descriptor that has
+ * Call func(request) for each file descriptor.
*/
void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func);
@@ -251,7 +253,9 @@
// ================================================================================
class Reporter : public virtual RefBase {
public:
- Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch);
+ Reporter(const sp<WorkDirectory>& workDirectory,
+ const sp<ReportBatch>& batch,
+ const vector<BringYourOwnSection*>& registeredSections);
virtual ~Reporter();
@@ -263,6 +267,10 @@
ReportWriter mWriter;
sp<ReportBatch> mBatch;
sp<ReportFile> mPersistedFile;
+ const vector<BringYourOwnSection*>& mRegisteredSections;
+
+ status_t execute_section(const Section* section, IncidentMetadata* metadata,
+ size_t* reportByteSize);
void cancel_and_remove_failed_requests();
};
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index c9277a5..2229e1c 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -267,7 +267,7 @@
signal(SIGPIPE, sigpipe_handler);
WorkerThreadData* data = (WorkerThreadData*)cookie;
- status_t err = data->section->BlockingCall(data->pipe.writeFd().get());
+ status_t err = data->section->BlockingCall(data->pipe.writeFd());
{
unique_lock<mutex> lock(data->lock);
@@ -458,7 +458,7 @@
DumpsysSection::~DumpsysSection() {}
-status_t DumpsysSection::BlockingCall(int pipeWriteFd) const {
+status_t DumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const {
// checkService won't wait for the service to show up like getService will.
sp<IBinder> service = defaultServiceManager()->checkService(mService);
@@ -467,7 +467,7 @@
return NAME_NOT_FOUND;
}
- service->dump(pipeWriteFd, mArgs);
+ service->dump(pipeWriteFd.get(), mArgs);
return NO_ERROR;
}
@@ -526,7 +526,7 @@
return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
}
-status_t LogSection::BlockingCall(int pipeWriteFd) const {
+status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const {
// Open log buffer and getting logs since last retrieved time if any.
unique_ptr<logger_list, void (*)(logger_list*)> loggers(
gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end()
@@ -643,7 +643,7 @@
}
}
gLastLogsRetrieved[mLogID] = lastTimestamp;
- if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+ if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
return EPIPE;
}
@@ -660,7 +660,7 @@
TombstoneSection::~TombstoneSection() {}
-status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
+status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const {
std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
if (proc.get() == nullptr) {
ALOGE("opendir /proc failed: %s\n", strerror(errno));
@@ -768,7 +768,7 @@
dumpPipe.readFd().reset();
}
- if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+ if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
if (err != NO_ERROR) {
return EPIPE;
@@ -778,6 +778,22 @@
return err;
}
+// ================================================================================
+BringYourOwnSection::BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+ const sp<IIncidentDumpCallback>& callback)
+ : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), uid(callingUid), mCallback(callback) {
+ name = "registered ";
+ name += customName;
+}
+
+BringYourOwnSection::~BringYourOwnSection() {}
+
+status_t BringYourOwnSection::BlockingCall(unique_fd& pipeWriteFd) const {
+ android::os::ParcelFileDescriptor pfd(std::move(pipeWriteFd));
+ mCallback->onDumpSection(pfd);
+ return NO_ERROR;
+}
+
} // namespace incidentd
} // namespace os
} // namespace android
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index fcf12f7..0bb9da9 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -23,6 +23,8 @@
#include <stdarg.h>
#include <map>
+#include <android/os/IIncidentDumpCallback.h>
+
#include <utils/String16.h>
#include <utils/String8.h>
#include <utils/Vector.h>
@@ -89,7 +91,7 @@
virtual status_t Execute(ReportWriter* writer) const;
- virtual status_t BlockingCall(int pipeWriteFd) const = 0;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const = 0;
};
/**
@@ -117,7 +119,7 @@
DumpsysSection(int id, const char* service, ...);
virtual ~DumpsysSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
String16 mService;
@@ -132,7 +134,7 @@
SystemPropertyDumpsysSection(int id, const char* service, ...);
virtual ~SystemPropertyDumpsysSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
String16 mService;
@@ -153,7 +155,7 @@
LogSection(int id, const char* logID, ...);
virtual ~LogSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
log_id_t mLogID;
@@ -169,12 +171,29 @@
TombstoneSection(int id, const char* type, int64_t timeoutMs = 120000 /* 2 minutes */);
virtual ~TombstoneSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
std::string mType;
};
+/**
+ * Section that gets data from a registered dump callback.
+ */
+class BringYourOwnSection : public WorkerThreadSection {
+public:
+ const uid_t uid;
+
+ BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+ const sp<IIncidentDumpCallback>& callback);
+ virtual ~BringYourOwnSection();
+
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
+
+private:
+ const sp<IIncidentDumpCallback>& mCallback;
+};
+
/**
* These sections will not be generated when doing an 'all' report, either
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 6033655..c2ee6dc 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -103,6 +103,10 @@
runSetVirtualDisk();
} else if ("set-isolated-storage".equals(op)) {
runIsolatedStorage();
+ } else if ("start-checkpoint".equals(op)) {
+ runStartCheckpoint();
+ } else if ("supports-checkpoint".equals(op)) {
+ runSupportsCheckpoint();
} else {
throw new IllegalArgumentException();
}
@@ -313,6 +317,27 @@
}
}
+ private void runStartCheckpoint() throws RemoteException {
+ final String numRetriesString = nextArg();
+ if (numRetriesString == null) {
+ throw new IllegalArgumentException("Expected <num-retries>");
+ }
+ int numRetries;
+ try {
+ numRetries = Integer.parseInt(numRetriesString);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("<num-retries> must be a positive integer");
+ }
+ if (numRetries <= 0) {
+ throw new IllegalArgumentException("<num-retries> must be a positive integer");
+ }
+ mSm.startCheckpoint(numRetries);
+ }
+
+ private void runSupportsCheckpoint() throws RemoteException {
+ System.out.println(mSm.supportsCheckpoint());
+ }
+
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
@@ -344,6 +369,10 @@
System.err.println("");
System.err.println(" sm set-isolated-storage [on|off|default]");
System.err.println("");
+ System.err.println(" sm start-checkpoint <num-retries>");
+ System.err.println("");
+ System.err.println(" sm supports-checkpoint");
+ System.err.println("");
return 1;
}
}
diff --git a/cmds/statsd/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..0255961 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -342,7 +342,7 @@
}
// 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 +399,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 +413,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.
@@ -6946,11 +6947,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 37fbf39..b5920cb 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -60,10 +60,6 @@
std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
- // kernel_wakelock
- {{.atomTag = android::util::KERNEL_WAKELOCK},
- {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
-
// subsystem_sleep_state
{{.atomTag = android::util::SUBSYSTEM_SLEEP_STATE},
{.puller = new SubsystemSleepStatePuller()}},
@@ -221,10 +217,6 @@
{.additiveFields = {1, 2, 3, 4},
.puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
- // BuildInformation.
- {{.atomTag = android::util::BUILD_INFORMATION},
- {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
-
// RoleHolder.
{{.atomTag = android::util::ROLE_HOLDER},
{.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}},
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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 1c6a561..d39a8c4 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -133,6 +133,10 @@
// Default flags to use with PackageManager when no flags are given.
private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES;
+ // Name of the resource which provides background permission button string
+ public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS =
+ "app_permission_button_allow_always";
+
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -807,6 +811,26 @@
}
@Override
+ public CharSequence getBackgroundPermissionButtonLabel() {
+ try {
+
+ String permissionController = getPermissionControllerPackageName();
+ Context context =
+ mContext.createPackageContext(permissionController, 0);
+
+ int textId = context.getResources().getIdentifier(APP_PERMISSION_BUTTON_ALLOW_ALWAYS,
+ "string", "com.android.permissioncontroller");
+// permissionController); STOPSHIP b/147434671
+ if (textId != 0) {
+ return context.getText(textId);
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Permission controller not found.", e);
+ }
+ return "";
+ }
+
+ @Override
public int checkSignatures(String pkg1, String pkg2) {
try {
return mPM.checkSignatures(pkg1, pkg2);
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 5185941..1f323c3 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -41,8 +41,7 @@
// Don't need to worry about synchronization
private static DisabledWallpaperManager sInstance;
- // TODO(b/138939803): STOPSHIP changed to false and/or remove it
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
@NonNull
static DisabledWallpaperManager getInstance() {
@@ -53,7 +52,6 @@
}
private DisabledWallpaperManager() {
- super(null, null, null);
}
@Override
@@ -66,10 +64,6 @@
return false;
}
- // TODO(b/138939803): STOPSHIP methods below should not be necessary,
- // callers should check if isWallpaperSupported(), consider removing them to keep this class
- // simpler
-
private static <T> T unsupported() {
if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
return null;
@@ -343,4 +337,9 @@
public boolean isWallpaperBackupEligible(int which) {
return unsupportedBoolean();
}
+
+ @Override
+ public boolean wallpaperSupportsWcg(int which) {
+ return unsupportedBoolean();
+ }
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index fcdb7cc..7af7a4a 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -94,7 +94,7 @@
void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group);
void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId);
- NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, String conversationId);
+ NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, boolean returnParentIfNoConversationChannel, String conversationId);
void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId);
NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, String channelId);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index a33c2c1..bdc7b99 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -31,6 +31,7 @@
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
@@ -71,7 +72,7 @@
* Conversation id to use for apps that aren't providing them yet.
* @hide
*/
- public static final String PLACEHOLDER_CONVERSATION_ID = "placeholder_id";
+ public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id";
/**
* The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
@@ -826,8 +827,8 @@
setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
- setConversationId(parser.getAttributeValue(ATT_PARENT_CHANNEL, null),
- parser.getAttributeValue(ATT_CONVERSATION_ID, null));
+ setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL),
+ parser.getAttributeValue(null, ATT_CONVERSATION_ID));
}
@Nullable
@@ -1175,7 +1176,7 @@
+ ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
+ ", mOriginalImp=" + mOriginalImportance
+ ", mParent=" + mParentId
- + ", mConversationId" + mConversationId;
+ + ", mConversationId=" + mConversationId;
}
/** @hide */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 61c1098..1a8e15c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -855,7 +855,8 @@
INotificationManager service = getService();
try {
return service.getConversationNotificationChannel(mContext.getOpPackageName(),
- mContext.getUserId(), mContext.getPackageName(), channelId, conversationId);
+ mContext.getUserId(), mContext.getPackageName(), channelId, true,
+ conversationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a9be9ea..7b21f12 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -54,6 +54,7 @@
import android.content.integrity.IAppIntegrityManager;
import android.content.om.IOverlayManager;
import android.content.om.OverlayManager;
+import android.content.pm.ApplicationInfo;
import android.content.pm.CrossProfileApps;
import android.content.pm.DataLoaderManager;
import android.content.pm.ICrossProfileApps;
@@ -693,20 +694,20 @@
throws ServiceNotFoundException {
final IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
if (b == null) {
- // There are 2 reason service can be null:
- // 1.Device doesn't support it - that's fine
- // 2.App is running on instant mode - should fail
- final boolean enabled = Resources.getSystem()
- .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
- if (!enabled) {
- // Life moves on...
- return DisabledWallpaperManager.getInstance();
- }
- if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+ ApplicationInfo appInfo = ctx.getApplicationInfo();
+ if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P
+ && appInfo.isInstantApp()) {
// Instant app
throw new ServiceNotFoundException(Context.WALLPAPER_SERVICE);
}
+ final boolean enabled = Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
+ if (!enabled) {
+ // Device doesn't support wallpaper, return a limited manager
+ return DisabledWallpaperManager.getInstance();
+ }
// Bad state - WallpaperManager methods will throw exception
+ Log.e(TAG, "No wallpaper service");
}
IWallpaperManager service = IWallpaperManager.Stub.asInterface(b);
return new WallpaperManager(service, ctx.getOuterContext(),
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 8eee73c..9ad3d38 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -543,6 +543,13 @@
mCmProxy = new ColorManagementProxy(context);
}
+ // no-op constructor called just by DisabledWallpaperManager
+ /*package*/ WallpaperManager() {
+ mContext = null;
+ mCmProxy = null;
+ mWcgEnabled = false;
+ }
+
/**
* Retrieve a WallpaperManager associated with the given Context.
*/
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/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 4f06561..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
*/
@@ -4389,6 +4393,18 @@
public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permName);
/**
+ * Gets the string that is displayed on the button which corresponds to granting background
+ * location in settings. The intended use for this is to help apps instruct users how to
+ * grant a background permission by providing the string that users will see.
+ *
+ * @return the string shown on the button for granting background location
+ */
+ @NonNull
+ public CharSequence getBackgroundPermissionButtonLabel() {
+ return "";
+ }
+
+ /**
* Returns an {@link android.content.Intent} suitable for passing to
* {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
* which prompts the user to grant permissions to this application.
@@ -4733,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/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/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 7fd86f6..301d203 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -300,22 +300,34 @@
* this without a single transport set will generate an exception, as will
* subsequently adding or removing transports after this is set.
* </p>
- * The interpretation of this {@code String} is bearer specific and bearers that use
- * it should document their particulars. For example, Bluetooth may use some sort of
- * device id while WiFi could used ssid and/or bssid. Cellular may use carrier spn.
+ * If the {@code networkSpecifier} is provided, it shall be interpreted as follows:
+ * <ul>
+ * <li>If the specifier can be parsed as an integer, it will be treated as a
+ * {@link android.net TelephonyNetworkSpecifier}, and the provided integer will be
+ * interpreted as a SubscriptionId.
+ * <li>If the value is an ethernet interface name, it will be treated as such.
+ * <li>For all other cases, the behavior is undefined.
+ * </ul>
*
- * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
- * specific network specifier where the bearer has a choice of
- * networks.
+ * @param networkSpecifier A {@code String} of either a SubscriptionId in cellular
+ * network request or an ethernet interface name in ethernet
+ * network request.
+ *
+ * @deprecated Use {@link #setNetworkSpecifier(NetworkSpecifier)} instead.
*/
+ @Deprecated
public Builder setNetworkSpecifier(String networkSpecifier) {
- /*
- * A StringNetworkSpecifier does not accept null or empty ("") strings. When network
- * specifiers were strings a null string and an empty string were considered equivalent.
- * Hence no meaning is attached to a null or empty ("") string.
- */
- return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
- : new StringNetworkSpecifier(networkSpecifier));
+ try {
+ int subId = Integer.parseInt(networkSpecifier);
+ return setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(subId).build());
+ } catch (NumberFormatException nfe) {
+ // A StringNetworkSpecifier does not accept null or empty ("") strings. When network
+ // specifiers were strings a null string and an empty string were considered
+ // equivalent. Hence no meaning is attached to a null or empty ("") string.
+ return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
+ : new StringNetworkSpecifier(networkSpecifier));
+ }
}
/**
diff --git a/core/java/android/net/TelephonyNetworkSpecifier.java b/core/java/android/net/TelephonyNetworkSpecifier.java
new file mode 100644
index 0000000..726f770
--- /dev/null
+++ b/core/java/android/net/TelephonyNetworkSpecifier.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * NetworkSpecifier object for cellular network request. Apps should use the
+ * {@link TelephonyNetworkSpecifier.Builder} class to create an instance.
+ */
+public final class TelephonyNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+
+ private final int mSubId;
+
+ /**
+ * Return the subscription Id of current TelephonyNetworkSpecifier object.
+ *
+ * @return The subscription id.
+ */
+ public int getSubscriptionId() {
+ return mSubId;
+ }
+
+ /**
+ * @hide
+ */
+ public TelephonyNetworkSpecifier(int subId) {
+ this.mSubId = subId;
+ }
+
+ public static final @NonNull Creator<TelephonyNetworkSpecifier> CREATOR =
+ new Creator<TelephonyNetworkSpecifier>() {
+ @Override
+ public TelephonyNetworkSpecifier createFromParcel(Parcel in) {
+ int subId = in.readInt();
+ return new TelephonyNetworkSpecifier(subId);
+ }
+
+ @Override
+ public TelephonyNetworkSpecifier[] newArray(int size) {
+ return new TelephonyNetworkSpecifier[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSubId);
+ }
+
+ @Override
+ public int hashCode() {
+ return mSubId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof TelephonyNetworkSpecifier)) {
+ return false;
+ }
+
+ TelephonyNetworkSpecifier lhs = (TelephonyNetworkSpecifier) obj;
+ return mSubId == lhs.mSubId;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("TelephonyNetworkSpecifier [")
+ .append("mSubId = ").append(mSubId)
+ .append("]")
+ .toString();
+ }
+
+ /** @hide */
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ // Any generic requests should be satisfied by a specific telephony network.
+ // For simplicity, we treat null same as MatchAllNetworkSpecifier
+ return equals(other) || other == null || other instanceof MatchAllNetworkSpecifier;
+ }
+
+
+ /**
+ * Builder to create {@link TelephonyNetworkSpecifier} object.
+ */
+ public static final class Builder {
+ // Integer.MIN_VALUE which is not a valid subId, services as the sentinel to check if
+ // subId was set
+ private static final int SENTINEL_SUB_ID = Integer.MIN_VALUE;
+
+ private int mSubId;
+
+ public Builder() {
+ mSubId = SENTINEL_SUB_ID;
+ }
+
+ /**
+ * Set the subscription id.
+ *
+ * @param subId The subscription Id.
+ * @return Instance of {@link Builder} to enable the chaining of the builder method.
+ */
+ public @NonNull Builder setSubscriptionId(int subId) {
+ mSubId = subId;
+ return this;
+ }
+
+ /**
+ * Create a NetworkSpecifier for the cellular network request.
+ *
+ * @return TelephonyNetworkSpecifier object.
+ * @throws IllegalArgumentException when subscription Id is not provided through
+ * {@link #setSubscriptionId(int)}.
+ */
+ public @NonNull TelephonyNetworkSpecifier build() {
+ if (mSubId == SENTINEL_SUB_ID) {
+ throw new IllegalArgumentException("Subscription Id is not provided.");
+ }
+ return new TelephonyNetworkSpecifier(mSubId);
+ }
+ }
+}
diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java
deleted file mode 100644
index e4a91c5..0000000
--- a/core/java/android/net/nsd/DnsSdTxtRecord.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/* -*- Mode: Java; tab-width: 4 -*-
- *
- * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
-
- To do:
- - implement remove()
- - fix set() to replace existing values
- */
-
-package android.net.nsd;
-
-import android.os.Parcelable;
-import android.os.Parcel;
-
-import java.util.Arrays;
-
-/**
- * This class handles TXT record data for DNS based service discovery as specified at
- * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
- *
- * DNS-SD specifies that a TXT record corresponding to an SRV record consist of
- * a packed array of bytes, each preceded by a length byte. Each string
- * is an attribute-value pair.
- *
- * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it
- * as need be to implement its various methods.
- * @hide
- *
- */
-public class DnsSdTxtRecord implements Parcelable {
- private static final byte mSeperator = '=';
-
- private byte[] mData;
-
- /** Constructs a new, empty TXT record. */
- public DnsSdTxtRecord() {
- mData = new byte[0];
- }
-
- /** Constructs a new TXT record from a byte array in the standard format. */
- public DnsSdTxtRecord(byte[] data) {
- mData = (byte[]) data.clone();
- }
-
- /** Copy constructor */
- public DnsSdTxtRecord(DnsSdTxtRecord src) {
- if (src != null && src.mData != null) {
- mData = (byte[]) src.mData.clone();
- }
- }
-
- /**
- * Set a key/value pair. Setting an existing key will replace its value.
- * @param key Must be ascii with no '='
- * @param value matching value to key
- */
- public void set(String key, String value) {
- byte[] keyBytes;
- byte[] valBytes;
- int valLen;
-
- if (value != null) {
- valBytes = value.getBytes();
- valLen = valBytes.length;
- } else {
- valBytes = null;
- valLen = 0;
- }
-
- try {
- keyBytes = key.getBytes("US-ASCII");
- }
- catch (java.io.UnsupportedEncodingException e) {
- throw new IllegalArgumentException("key should be US-ASCII");
- }
-
- for (int i = 0; i < keyBytes.length; i++) {
- if (keyBytes[i] == '=') {
- throw new IllegalArgumentException("= is not a valid character in key");
- }
- }
-
- if (keyBytes.length + valLen >= 255) {
- throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes");
- }
-
- int currentLoc = remove(key);
- if (currentLoc == -1)
- currentLoc = keyCount();
-
- insert(keyBytes, valBytes, currentLoc);
- }
-
- /**
- * Get a value for a key
- *
- * @param key
- * @return The value associated with the key
- */
- public String get(String key) {
- byte[] val = this.getValue(key);
- return val != null ? new String(val) : null;
- }
-
- /** Remove a key/value pair. If found, returns the index or -1 if not found */
- public int remove(String key) {
- int avStart = 0;
-
- for (int i=0; avStart < mData.length; i++) {
- int avLen = mData[avStart];
- if (key.length() <= avLen &&
- (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) {
- String s = new String(mData, avStart + 1, key.length());
- if (0 == key.compareToIgnoreCase(s)) {
- byte[] oldBytes = mData;
- mData = new byte[oldBytes.length - avLen - 1];
- System.arraycopy(oldBytes, 0, mData, 0, avStart);
- System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart,
- oldBytes.length - avStart - avLen - 1);
- return i;
- }
- }
- avStart += (0xFF & (avLen + 1));
- }
- return -1;
- }
-
- /** Return the count of keys */
- public int keyCount() {
- int count = 0, nextKey;
- for (nextKey = 0; nextKey < mData.length; count++) {
- nextKey += (0xFF & (mData[nextKey] + 1));
- }
- return count;
- }
-
- /** Return true if key is present, false if not. */
- public boolean contains(String key) {
- String s = null;
- for (int i = 0; null != (s = this.getKey(i)); i++) {
- if (0 == key.compareToIgnoreCase(s)) return true;
- }
- return false;
- }
-
- /* Gets the size in bytes */
- public int size() {
- return mData.length;
- }
-
- /* Gets the raw data in bytes */
- public byte[] getRawData() {
- return (byte[]) mData.clone();
- }
-
- private void insert(byte[] keyBytes, byte[] value, int index) {
- byte[] oldBytes = mData;
- int valLen = (value != null) ? value.length : 0;
- int insertion = 0;
- int newLen, avLen;
-
- for (int i = 0; i < index && insertion < mData.length; i++) {
- insertion += (0xFF & (mData[insertion] + 1));
- }
-
- avLen = keyBytes.length + valLen + (value != null ? 1 : 0);
- newLen = avLen + oldBytes.length + 1;
-
- mData = new byte[newLen];
- System.arraycopy(oldBytes, 0, mData, 0, insertion);
- int secondHalfLen = oldBytes.length - insertion;
- System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen);
- mData[insertion] = (byte) avLen;
- System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length);
- if (value != null) {
- mData[insertion + 1 + keyBytes.length] = mSeperator;
- System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen);
- }
- }
-
- /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */
- private String getKey(int index) {
- int avStart = 0;
-
- for (int i=0; i < index && avStart < mData.length; i++) {
- avStart += mData[avStart] + 1;
- }
-
- if (avStart < mData.length) {
- int avLen = mData[avStart];
- int aLen = 0;
-
- for (aLen=0; aLen < avLen; aLen++) {
- if (mData[avStart + aLen + 1] == mSeperator) break;
- }
- return new String(mData, avStart + 1, aLen);
- }
- return null;
- }
-
- /**
- * Look up a key in the TXT record by zero-based index and return its value.
- * Returns null if index exceeds the total number of keys.
- * Returns null if the key is present with no value.
- */
- private byte[] getValue(int index) {
- int avStart = 0;
- byte[] value = null;
-
- for (int i=0; i < index && avStart < mData.length; i++) {
- avStart += mData[avStart] + 1;
- }
-
- if (avStart < mData.length) {
- int avLen = mData[avStart];
- int aLen = 0;
-
- for (aLen=0; aLen < avLen; aLen++) {
- if (mData[avStart + aLen + 1] == mSeperator) {
- value = new byte[avLen - aLen - 1];
- System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1);
- break;
- }
- }
- }
- return value;
- }
-
- private String getValueAsString(int index) {
- byte[] value = this.getValue(index);
- return value != null ? new String(value) : null;
- }
-
- private byte[] getValue(String forKey) {
- String s = null;
- int i;
-
- for (i = 0; null != (s = this.getKey(i)); i++) {
- if (0 == forKey.compareToIgnoreCase(s)) {
- return this.getValue(i);
- }
- }
-
- return null;
- }
-
- /**
- * Return a string representation.
- * Example : {key1=value1},{key2=value2}..
- *
- * For a key say like "key3" with null value
- * {key1=value1},{key2=value2}{key3}
- */
- public String toString() {
- String a, result = null;
-
- for (int i = 0; null != (a = this.getKey(i)); i++) {
- String av = "{" + a;
- String val = this.getValueAsString(i);
- if (val != null)
- av += "=" + val + "}";
- else
- av += "}";
- if (result == null)
- result = av;
- else
- result = result + ", " + av;
- }
- return result != null ? result : "";
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (!(o instanceof DnsSdTxtRecord)) {
- return false;
- }
-
- DnsSdTxtRecord record = (DnsSdTxtRecord)o;
- return Arrays.equals(record.mData, mData);
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(mData);
- }
-
- /** Implement the Parcelable interface */
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface */
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeByteArray(mData);
- }
-
- /** Implement the Parcelable interface */
- public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR =
- new Creator<DnsSdTxtRecord>() {
- public DnsSdTxtRecord createFromParcel(Parcel in) {
- DnsSdTxtRecord info = new DnsSdTxtRecord();
- in.readByteArray(info.mData);
- return info;
- }
-
- public DnsSdTxtRecord[] newArray(int size) {
- return new DnsSdTxtRecord[size];
- }
- };
-}
diff --git a/core/java/android/os/IIncidentDumpCallback.aidl b/core/java/android/os/IIncidentDumpCallback.aidl
new file mode 100644
index 0000000..09b5b01
--- /dev/null
+++ b/core/java/android/os/IIncidentDumpCallback.aidl
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Callback from IIncidentManager to dump an extended section.
+ *
+ * @hide
+ */
+oneway interface IIncidentDumpCallback {
+ /**
+ * Dumps section data to the given ParcelFileDescriptor.
+ */
+ void onDumpSection(in ParcelFileDescriptor fd);
+}
diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl
index 7e1b1e0..923234e 100644
--- a/core/java/android/os/IIncidentManager.aidl
+++ b/core/java/android/os/IIncidentManager.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.os.IIncidentReportStatusListener;
+import android.os.IIncidentDumpCallback;
import android.os.IncidentManager;
import android.os.IncidentReportArgs;
@@ -52,6 +53,19 @@
@nullable IIncidentReportStatusListener listener);
/**
+ * Register a section callback with the given id and name. The callback function
+ * will be invoked when an incident report with all sections or sections matching
+ * the given id is being taken.
+ */
+ oneway void registerSection(int id, String name, IIncidentDumpCallback callback);
+
+ /**
+ * Unregister a section callback associated with the given id. The section must be
+ * previously registered with registerSection(int, String, IIncidentDumpCallback).
+ */
+ oneway void unregisterSection(int id);
+
+ /**
* Tell the incident daemon that the android system server is up and running.
*/
oneway void systemRunning();
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index 09e1c0f..f6563eb 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -31,6 +31,7 @@
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -421,6 +422,39 @@
}
/**
+ * Callback for dumping an extended (usually vendor-supplied) incident report section
+ *
+ * @see #registerSection
+ * @see #unregisterSection
+ *
+ * @hide
+ */
+ public static class DumpCallback {
+ private Executor mExecutor;
+
+ IIncidentDumpCallback.Stub mBinder = new IIncidentDumpCallback.Stub() {
+ @Override
+ public void onDumpSection(ParcelFileDescriptor pfd) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ DumpCallback.this.onDumpSection(
+ new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+ });
+ } else {
+ DumpCallback.this.onDumpSection(
+ new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+ }
+ }
+ };
+
+ /**
+ * Called when incidentd requests to dump this section.
+ */
+ public void onDumpSection(OutputStream out) {
+ }
+ }
+
+ /**
* @hide
*/
public IncidentManager(Context context) {
@@ -528,6 +562,61 @@
}
/**
+ * Register a callback to dump an extended incident report section with the given id and name.
+ * The callback function will be invoked when an incident report with all sections or sections
+ * matching the given id is being taken.
+ *
+ * @hide
+ */
+ public void registerSection(int id, String name, @NonNull DumpCallback callback) {
+ registerSection(id, name, mContext.getMainExecutor(), callback);
+ }
+
+ /**
+ * Register a callback to dump an extended incident report section with the given id and name,
+ * running on the supplied executor.
+ *
+ * @hide
+ */
+ public void registerSection(int id, String name, @NonNull @CallbackExecutor Executor executor,
+ @NonNull DumpCallback callback) {
+ try {
+ if (callback.mExecutor != null) {
+ throw new RuntimeException("Do not reuse DumpCallback objects when calling"
+ + " registerSection");
+ }
+ callback.mExecutor = executor;
+ final IIncidentManager service = getIIncidentManagerLocked();
+ if (service == null) {
+ Slog.e(TAG, "registerSection can't find incident binder service");
+ return;
+ }
+ service.registerSection(id, name, callback.mBinder);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "registerSection failed", ex);
+ }
+ }
+
+ /**
+ * Unregister an extended section dump function. The section must be previously registered with
+ * {@link #registerSection(int, String, DumpCallback)}
+ *
+ * @hide
+ */
+ public void unregisterSection(int id) {
+ try {
+ final IIncidentManager service = getIIncidentManagerLocked();
+ if (service == null) {
+ Slog.e(TAG, "unregisterSection can't find incident binder service");
+ return;
+ }
+ service.unregisterSection(id);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unregisterSection failed", ex);
+ }
+ }
+
+ /**
* Get the incident reports that are available for upload for the supplied
* broadcast recevier.
*
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f84e483..2eaefca 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2453,6 +2453,13 @@
* by {@link #createUser(String, String, int)} or {@link #createGuest(Context, String)}), it
* takes less time.
*
+ * <p>This method completes the majority of work necessary for user creation: it
+ * creates user data, CE and DE encryption keys, app data directories, initializes the user and
+ * grants default permissions. When pre-created users become "real" users, only then are
+ * components notified of new user creation by firing user creation broadcasts.
+ *
+ * <p>All pre-created users are removed during system upgrade.
+ *
* <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
*
* @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
@@ -2464,6 +2471,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public @Nullable UserInfo preCreateUser(@NonNull String userType) {
try {
return mService.preCreateUser(userType);
diff --git a/core/java/android/os/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/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..4f84183 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5137,7 +5137,6 @@
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_P2P_DEVICE_NAME);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SAVED_STATE);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS);
- MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_ENHANCED_AUTO_JOIN);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NETWORK_SHOW_RSSI);
@@ -8820,9 +8819,9 @@
* added to both AIRPLANE_MODE_RADIOS and AIRPLANE_MODE_TOGGLEABLE_RADIOS, then Wifi
* will be turned off when entering airplane mode, but the user will be able to reenable
* Wifi in the Settings app.
- *
- * {@hide}
+ * @hide
*/
+ @SystemApi
public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
/**
@@ -9994,24 +9993,17 @@
* Setting to allow scans to be enabled even wifi is turned off for connectivity.
* @hide
*/
+ @SystemApi
public static final String WIFI_SCAN_ALWAYS_AVAILABLE =
"wifi_scan_always_enabled";
/**
- * The interval in milliseconds at which wifi rtt ranging requests will be throttled when
- * they are coming from the background.
- *
- * @hide
- */
- public static final String WIFI_RTT_BACKGROUND_EXEC_GAP_MS =
- "wifi_rtt_background_exec_gap_ms";
-
- /**
* Indicate whether factory reset request is pending.
*
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String WIFI_P2P_PENDING_FACTORY_RESET =
"wifi_p2p_pending_factory_reset";
@@ -10021,6 +10013,7 @@
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
/**
@@ -10064,10 +10057,10 @@
* enabled state.
* @hide
*/
+ @SystemApi
public static final String NETWORK_RECOMMENDATIONS_ENABLED =
"network_recommendations_enabled";
-
/**
* Which package name to use for network recommendations. If null, network recommendations
* will neither be requested nor accepted.
@@ -10092,17 +10085,6 @@
public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
/**
- * The number of milliseconds the {@link com.android.server.NetworkScoreService}
- * will give a recommendation request to complete before returning a default response.
- *
- * Type: long
- * @hide
- * @deprecated to be removed
- */
- public static final String NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS =
- "network_recommendation_request_timeout_ms";
-
- /**
* The expiration time in milliseconds for the {@link android.net.WifiKey} request cache in
* {@link com.android.server.wifi.RecommendedNetworkEvaluator}.
*
@@ -10120,6 +10102,7 @@
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
/**
@@ -10228,18 +10211,11 @@
"wifi_watchdog_poor_network_test_enabled";
/**
- * Setting to turn on suspend optimizations at screen off on Wi-Fi. Enabled by default and
- * needs to be set to 0 to disable it.
- * @hide
- */
- public static final String WIFI_SUSPEND_OPTIMIZATIONS_ENABLED =
- "wifi_suspend_optimizations_enabled";
-
- /**
* Setting to enable verbose logging in Wi-Fi; disabled by default, and setting to 1
* will enable it. In the future, additional values may be supported.
* @hide
*/
+ @SystemApi
public static final String WIFI_VERBOSE_LOGGING_ENABLED =
"wifi_verbose_logging_enabled";
@@ -10265,69 +10241,10 @@
* Errors in the parameters will cause the entire setting to be ignored.
* @hide
*/
+ @SystemApi
public static final String WIFI_SCORE_PARAMS =
"wifi_score_params";
- /**
- * Setting to enable logging WifiIsUnusableEvent in metrics
- * which gets triggered when wifi becomes unusable.
- * Disabled by default, and setting it to 1 will enable it.
- * @hide
- */
- public static final String WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED =
- "wifi_is_unusable_event_metrics_enabled";
-
- /**
- * The minimum number of txBad the framework has to observe
- * to trigger a wifi data stall.
- * @hide
- */
- public static final String WIFI_DATA_STALL_MIN_TX_BAD =
- "wifi_data_stall_min_tx_bad";
-
- /**
- * The minimum number of txSuccess the framework has to observe
- * to trigger a wifi data stall when rxSuccess is 0.
- * @hide
- */
- public static final String WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX =
- "wifi_data_stall_min_tx_success_without_rx";
-
- /**
- * Setting to enable logging Wifi LinkSpeedCounts in metrics.
- * Disabled by default, and setting it to 1 will enable it.
- * @hide
- */
- public static final String WIFI_LINK_SPEED_METRICS_ENABLED =
- "wifi_link_speed_metrics_enabled";
-
- /**
- * Setting to enable the PNO frequency culling optimization.
- * Disabled by default, and setting it to 1 will enable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_PNO_FREQUENCY_CULLING_ENABLED =
- "wifi_pno_frequency_culling_enabled";
-
- /**
- * Setting to enable including recency information when determining pno network priorities.
- * Disabled by default, and setting it to 1 will enable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_PNO_RECENCY_SORTING_ENABLED =
- "wifi_pno_recency_sorting_enabled";
-
- /**
- * Setting to enable the Wi-Fi link probing.
- * Enabled by default, and setting it to 0 will disable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_LINK_PROBING_ENABLED =
- "wifi_link_probing_enabled";
-
/**
* The maximum number of times we will retry a connection to an access
* point for which we have failed in acquiring an IP address from DHCP.
@@ -10367,6 +10284,7 @@
* The Wi-Fi peer-to-peer device name
* @hide
*/
+ @SystemApi
public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
/**
diff --git a/core/java/android/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..c9d3b92 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -384,6 +384,17 @@
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
public static final int LISTEN_REGISTRATION_FAILURE = 0x40000000;
+ /**
+ * Listen for Barring Information for the current registered / camped cell.
+ *
+ * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
+ * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+ *
+ * @see #onBarringInfoChanged()
+ */
+ @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+ public static final int LISTEN_BARRING_INFO = 0x80000000;
+
/*
* Subscription used to listen to the phone state changes
* @hide
@@ -979,6 +990,20 @@
}
/**
+ * Report updated barring information for the current camped/registered cell.
+ *
+ * <p>Barring info is provided for all services applicable to the current camped/registered
+ * cell, for the registered PLMN and current access class/access category.
+ *
+ * @param barringInfo for all services on the current cell.
+ *
+ * @see android.telephony.BarringInfo
+ */
+ public void onBarringInfoChanged(@NonNull BarringInfo barringInfo) {
+ // default implementation empty
+ }
+
+ /**
* The callback methods need to be called on the handler thread where
* this object was created. If the binder did that for us it'd be nice.
*
@@ -1262,6 +1287,14 @@
cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode)));
// default implementation empty
}
+
+ public void onBarringInfoChanged(BarringInfo barringInfo) {
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onBarringInfoChanged(barringInfo)));
+ }
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 9387a2c..4dffa62 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -708,4 +708,21 @@
} catch (RemoteException ex) {
}
}
+
+ /**
+ * Notify {@link BarringInfo} has changed for a specific subscription.
+ *
+ * @param slotIndex for the phone object that got updated barring info.
+ * @param subId for which the BarringInfo changed.
+ * @param barringInfo updated BarringInfo.
+ */
+ public void notifyBarringInfoChanged(
+ int slotIndex, int subId, @NonNull BarringInfo barringInfo) {
+ try {
+ sRegistry.notifyBarringInfoChanged(slotIndex, subId, barringInfo);
+ } catch (RemoteException ex) {
+ // system server crash
+ }
+ }
+
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 9d22d30..c191a0d 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -59,7 +59,6 @@
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");
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/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/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java
index 253794f..c55caa3 100644
--- a/core/java/android/view/WindowContainerTransaction.java
+++ b/core/java/android/view/WindowContainerTransaction.java
@@ -74,6 +74,18 @@
return this;
}
+ /**
+ * Sets whether a container or any of its children can be focusable. When {@code false}, no
+ * child can be focused; however, when {@code true}, it is still possible for children to be
+ * non-focusable due to WM policy.
+ */
+ public WindowContainerTransaction setFocusable(IWindowContainer container, boolean focusable) {
+ Change chg = getOrCreateChange(container.asBinder());
+ chg.mFocusable = focusable;
+ chg.mChangeMask |= Change.CHANGE_FOCUSABLE;
+ return this;
+ }
+
public Map<IBinder, Change> getChanges() {
return mChanges;
}
@@ -112,7 +124,11 @@
* @hide
*/
public static class Change implements Parcelable {
+ public static final int CHANGE_FOCUSABLE = 1;
+
private final Configuration mConfiguration = new Configuration();
+ private boolean mFocusable = true;
+ private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
@@ -123,6 +139,8 @@
protected Change(Parcel in) {
mConfiguration.readFromParcel(in);
+ mFocusable = in.readBoolean();
+ mChangeMask = in.readInt();
mConfigSetMask = in.readInt();
mWindowSetMask = in.readInt();
mSchedulePipCallback = (in.readInt() != 0);
@@ -136,6 +154,18 @@
return mConfiguration;
}
+ /** Gets the requested focusable value */
+ public boolean getFocusable() {
+ if ((mChangeMask & CHANGE_FOCUSABLE) == 0) {
+ throw new RuntimeException("Focusable not set. check CHANGE_FOCUSABLE first");
+ }
+ return mFocusable;
+ }
+
+ public int getChangeMask() {
+ return mChangeMask;
+ }
+
@ActivityInfo.Config
public int getConfigSetMask() {
return mConfigSetMask;
@@ -170,6 +200,9 @@
if (changesSss) {
sb.append("ssw:" + mConfiguration.smallestScreenWidthDp + ",");
}
+ if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
+ sb.append("focusable:" + mFocusable + ",");
+ }
sb.append("}");
return sb.toString();
}
@@ -177,6 +210,8 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
mConfiguration.writeToParcel(dest, flags);
+ dest.writeBoolean(mFocusable);
+ dest.writeInt(mChangeMask);
dest.writeInt(mConfigSetMask);
dest.writeInt(mWindowSetMask);
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/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java
new file mode 100644
index 0000000..fe8bbb8
--- /dev/null
+++ b/core/java/android/webkit/PacProcessor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+
+/**
+ * Class to evaluate PAC scripts.
+ * @hide
+ */
+
+@SystemApi
+public interface PacProcessor {
+
+ /**
+ * Returns the default PacProcessor instance.
+ *
+ * @return the default PacProcessor instance.
+ */
+ @NonNull
+ static PacProcessor getInstance() {
+ return WebViewFactory.getProvider().getPacProcessor();
+ }
+
+ /**
+ * Set PAC script to use.
+ *
+ * @param script PAC script.
+ * @return true if PAC script is successfully set.
+ */
+ boolean setProxyScript(@NonNull String script);
+
+ /**
+ * Gets a list of proxy servers to use.
+ * @param url The URL being accessed.
+ * @return a PAC-style semicolon-separated list of valid proxy servers.
+ * For example: "PROXY xxx.xxx.xxx.xxx:xx; SOCKS yyy.yyy.yyy:yy".
+ */
+ @Nullable
+ String makeProxyRequest(@NonNull String url);
+}
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 6a1ed39..f7c3ec0 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -175,6 +175,15 @@
WebViewDatabase getWebViewDatabase(Context context);
/**
+ * Gets the singleton PacProcessor instance.
+ * @return the PacProcessor instance
+ */
+ @NonNull
+ default PacProcessor getPacProcessor() {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ /**
* Gets the classloader used to load internal WebView implementation classes. This interface
* should only be used by the WebView Support Library.
*/
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f851e10..b891af5 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -152,7 +152,7 @@
// Specifies whether to allow starting a cursor drag by dragging anywhere over the text.
@VisibleForTesting
- public static boolean FLAG_ENABLE_CURSOR_DRAG = false;
+ public static boolean FLAG_ENABLE_CURSOR_DRAG = true;
// Specifies whether to use the magnifier when pressing the insertion or selection handles.
private static final boolean FLAG_USE_MAGNIFIER = true;
@@ -5741,10 +5741,10 @@
return;
}
switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mIsDraggingCursor = false;
- break;
case MotionEvent.ACTION_MOVE:
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ break;
+ }
if (mIsDraggingCursor) {
performCursorDrag(event);
} else if (FLAG_ENABLE_CURSOR_DRAG
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index 6277afe..b13ca42 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -173,6 +173,13 @@
mIsDragCloseToVertical = (4 * deltaXSquared) <= distanceSquared;
}
}
+ } else if (action == MotionEvent.ACTION_CANCEL) {
+ mLastDownMillis = 0;
+ mLastUpMillis = 0;
+ mMultiTapStatus = MultiTapStatus.NONE;
+ mMultiTapInSameArea = false;
+ mMovedEnoughForDrag = false;
+ mIsDragCloseToVertical = false;
}
}
diff --git a/core/java/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 b670316..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(
@@ -325,11 +333,11 @@
return false;
}
- private void disableWhiteListingService(String componentId) {
+ private void setWhiteListingServiceEnabled(String componentId, int settingsValue) {
for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
Settings.Secure.putInt(getContentResolver(),
- WHITE_LISTING_FEATURES[i][SETTINGS_KEY], /* settingsValueOn */ 1);
+ WHITE_LISTING_FEATURES[i][SETTINGS_KEY], settingsValue);
return;
}
}
@@ -339,7 +347,8 @@
final String componentId = componentName.flattenToString();
if (isWhiteListingService(componentId)) {
- disableWhiteListingService(componentName.flattenToString());
+ setWhiteListingServiceEnabled(componentName.flattenToString(),
+ /* settingsValueOff */ 0);
} else {
setAccessibilityServiceState(this, componentName, /* enabled= */ false);
}
@@ -356,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;
}
@@ -440,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,
@@ -545,26 +561,78 @@
}
private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
- Settings.Secure.putString(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
- mTargets.get(position).getId());
- // TODO(b/146969684): notify accessibility button clicked.
+ final AccessibilityButtonTarget target = mTargets.get(position);
+ switch (target.getFragmentType()) {
+ case AccessibilityServiceFragmentType.LEGACY:
+ onLegacyTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.INVISIBLE:
+ onInvisibleTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.INTUITIVE:
+ onIntuitiveTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.BOUNCE:
+ // Do nothing
+ break;
+ default:
+ throw new IllegalStateException("Unexpected fragment type");
+ }
+
mAlertDialog.dismiss();
}
+ private void onLegacyTargetSelected(AccessibilityButtonTarget target) {
+ if (mShortcutType == ACCESSIBILITY_BUTTON) {
+ final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+ } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ switchServiceState(target);
+ }
+ }
+
+ private void onInvisibleTargetSelected(AccessibilityButtonTarget target) {
+ final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+ }
+
+ private void onIntuitiveTargetSelected(AccessibilityButtonTarget target) {
+ switchServiceState(target);
+ }
+
+ private void switchServiceState(AccessibilityButtonTarget target) {
+ final ComponentName componentName =
+ ComponentName.unflattenFromString(target.getId());
+ final String componentId = componentName.flattenToString();
+
+ if (isWhiteListingService(componentId)) {
+ setWhiteListingServiceEnabled(componentId,
+ isWhiteListingServiceEnabled(this, target)
+ ? /* settingsValueOff */ 0
+ : /* settingsValueOn */ 1);
+ } else {
+ setAccessibilityServiceState(this, componentName,
+ /* enabled= */!isAccessibilityServiceEnabled(this, target));
+ }
+ }
+
private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) {
final AccessibilityButtonTarget target = mTargets.get(position);
final ComponentName targetComponentName =
ComponentName.unflattenFromString(target.getId());
switch (target.getFragmentType()) {
+ case AccessibilityServiceFragmentType.LEGACY:
+ onLegacyTargetDeleted(targetComponentName);
+ break;
case AccessibilityServiceFragmentType.INVISIBLE:
onInvisibleTargetDeleted(targetComponentName);
break;
case AccessibilityServiceFragmentType.INTUITIVE:
onIntuitiveTargetDeleted(targetComponentName);
break;
- case AccessibilityServiceFragmentType.LEGACY:
case AccessibilityServiceFragmentType.BOUNCE:
// Do nothing
break;
@@ -580,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);
@@ -595,8 +669,6 @@
ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) {
disableService(componentName);
}
- } else {
- throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
}
}
@@ -605,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);
}
}
@@ -719,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_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 53327bc..c4ee195 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2627,7 +2627,7 @@
gMidAudioRecordRoutingProxy_release =
android::GetMethodIDOrDie(env, gClsAudioRecordRoutingProxy, "native_release", "()V");
- AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback);
+ AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
return RegisterMethodsOrDie(env, kEventHandlerClassPathName, gEventHandlerMethods,
diff --git a/core/jni/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/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/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index 8c9b4d0..a602fa3 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -32,6 +32,7 @@
import android.app.Activity;
import android.app.Instrumentation;
+import android.view.InputDevice;
import android.view.MotionEvent;
import androidx.test.InstrumentationRegistry;
@@ -282,6 +283,35 @@
}
@Test
+ public void testEditor_onTouchEvent_mouseDrag() throws Throwable {
+ String text = "testEditor_onTouchEvent_mouseDrag";
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ TextView tv = mActivity.findViewById(R.id.textview);
+ Editor editor = tv.getEditorForTesting();
+
+ // Simulate a mouse click and drag. This should NOT trigger a cursor drag.
+ long event1Time = 1001;
+ MotionEvent event1 = mouseDownEvent(event1Time, event1Time, 20f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event2Time = 1002;
+ MotionEvent event2 = mouseMoveEvent(event1Time, event2Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+ long event3Time = 1003;
+ MotionEvent event3 = mouseUpEvent(event1Time, event3Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+ }
+
+ @Test
public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
String text = "testEditor_onTouchEvent_cursorDrag";
onView(withId(R.id.textview)).perform(replaceText(text));
@@ -385,4 +415,25 @@
private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) {
return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
}
+
+ private static MotionEvent mouseDownEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = downEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+ return event;
+ }
+
+ private static MotionEvent mouseUpEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = upEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(0);
+ return event;
+ }
+
+ private static MotionEvent mouseMoveEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = moveEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+ return event;
+ }
}
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 215d0b8..3dc001d 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -48,24 +48,32 @@
}
@Test
+ public void testIsDistanceWithin() throws Exception {
+ assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
+ assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
+ assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
+ assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+ }
+
+ @Test
public void testUpdate_singleTap() throws Exception {
// Simulate an ACTION_DOWN event.
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is after the double-tap timeout.
long event3Time = event2Time + ViewConfiguration.getDoubleTapTimeout() + 1;
MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
}
@Test
@@ -74,13 +82,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -96,13 +104,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -125,13 +133,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout when
// calculated from the last ACTION_UP event time. Even though the time between the last up
@@ -140,7 +148,7 @@
long event3Time = event2Time + 1;
MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
}
@Test
@@ -149,19 +157,19 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_MOVE event.
long event2Time = 1001;
MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
// Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
long event3Time = 5000;
MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 200f, 31f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 200f, 31f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout when
// calculated from the last ACTION_UP event time. Even though the time between the last up
@@ -170,7 +178,7 @@
long event4Time = event3Time + 1;
MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f);
mTouchState.update(event4, mConfig);
- assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false);
+ assertSingleTap(mTouchState, 200f, 31f, 200f, 31f);
}
@Test
@@ -180,14 +188,14 @@
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
event1.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
event2.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -220,13 +228,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -246,7 +254,7 @@
long event5Time = 1004;
MotionEvent event5 = downEvent(event5Time, event5Time, 22f, 32f);
mTouchState.update(event5, mConfig);
- assertSingleTap(mTouchState, 22f, 32f, 21f, 31f, false);
+ assertSingleTap(mTouchState, 22f, 32f, 21f, 31f);
}
@Test
@@ -255,13 +263,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_MOVE event whose location is not far enough to start a drag.
long event2Time = 1001;
MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate another ACTION_MOVE event whose location is far enough to start a drag.
int touchSlop = mConfig.getScaledTouchSlop();
@@ -270,21 +278,135 @@
long event3Time = 1002;
MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
// Simulate an ACTION_UP event.
long event4Time = 1003;
MotionEvent event4 = upEvent(event3Time, event4Time, 200f, 300f);
mTouchState.update(event4, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 200f, 300f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 200f, 300f);
}
@Test
- public void testIsDistanceWithin() throws Exception {
- assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
- assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
- assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
- assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+ public void testUpdate_drag_startsCloseToVerticalThenHorizontal() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+ // Simulate an ACTION_MOVE event that is < 30 deg from vertical.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 174f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+ // Simulate another ACTION_MOVE event that is horizontal from the original down event.
+ // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+ // initial direction of movement.
+ long event3Time = 1003;
+ MotionEvent event3 = moveEvent(event1Time, event3Time, 200f, 0f);
+ mTouchState.update(event3, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+ // Simulate an ACTION_UP event.
+ long event4Time = 1004;
+ MotionEvent event4 = upEvent(event1Time, event4Time, 200f, 0f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 200f, 0f);
+ }
+
+ @Test
+ public void testUpdate_drag_startsHorizontalThenVertical() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+ // Simulate an ACTION_MOVE event that is > 30 deg from vertical.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 173f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+ // Simulate another ACTION_MOVE event that is vertical from the original down event.
+ // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+ // initial direction of movement.
+ long event3Time = 1003;
+ MotionEvent event3 = moveEvent(event1Time, event3Time, 0f, 200f);
+ mTouchState.update(event3, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+ // Simulate an ACTION_UP event.
+ long event4Time = 1004;
+ MotionEvent event4 = upEvent(event1Time, event4Time, 0f, 200f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0f, 200f);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterDown() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate an ACTION_CANCEL event.
+ long event2Time = 1002;
+ MotionEvent event2 = cancelEvent(event1Time, event2Time, 20f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterDrag() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate another ACTION_MOVE event whose location is far enough to start a drag.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event2Time, event2Time, 200f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+
+ // Simulate an ACTION_CANCEL event.
+ long event3Time = 1003;
+ MotionEvent event3 = cancelEvent(event1Time, event3Time, 200f, 30f);
+ mTouchState.update(event3, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterMultitap() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate an ACTION_UP event.
+ long event2Time = 1002;
+ MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
+
+ // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
+ long event3Time = 1003;
+ MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+ mTouchState.update(event3, mConfig);
+ assertMultiTap(mTouchState, 22f, 33f, 20f, 30f,
+ MultiTapStatus.DOUBLE_TAP, true);
+
+ // Simulate an ACTION_CANCEL event.
+ long event4Time = 1004;
+ MotionEvent event4 = cancelEvent(event3Time, event4Time, 20f, 30f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
}
private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
@@ -299,8 +421,12 @@
return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
}
+ private static MotionEvent cancelEvent(long downTime, long eventTime, float x, float y) {
+ return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL, x, y, 0);
+ }
+
private static void assertSingleTap(EditorTouchState touchState, float lastDownX,
- float lastDownY, float lastUpX, float lastUpY, boolean isMovedEnoughForDrag) {
+ float lastDownY, float lastUpX, float lastUpY) {
assertThat(touchState.getLastDownX(), is(lastDownX));
assertThat(touchState.getLastDownY(), is(lastDownY));
assertThat(touchState.getLastUpX(), is(lastUpX));
@@ -309,7 +435,21 @@
assertThat(touchState.isTripleClick(), is(false));
assertThat(touchState.isMultiTap(), is(false));
assertThat(touchState.isMultiTapInSameArea(), is(false));
- assertThat(touchState.isMovedEnoughForDrag(), is(isMovedEnoughForDrag));
+ assertThat(touchState.isMovedEnoughForDrag(), is(false));
+ }
+
+ private static void assertDrag(EditorTouchState touchState, float lastDownX,
+ float lastDownY, float lastUpX, float lastUpY, boolean isDragCloseToVertical) {
+ assertThat(touchState.getLastDownX(), is(lastDownX));
+ assertThat(touchState.getLastDownY(), is(lastDownY));
+ assertThat(touchState.getLastUpX(), is(lastUpX));
+ assertThat(touchState.getLastUpY(), is(lastUpY));
+ assertThat(touchState.isDoubleTap(), is(false));
+ assertThat(touchState.isTripleClick(), is(false));
+ assertThat(touchState.isMultiTap(), is(false));
+ assertThat(touchState.isMultiTapInSameArea(), is(false));
+ assertThat(touchState.isMovedEnoughForDrag(), is(true));
+ assertThat(touchState.isDragCloseToVertical(), is(isDragCloseToVertical));
}
private static void assertMultiTap(EditorTouchState touchState,
@@ -325,5 +465,6 @@
|| multiTapStatus == MultiTapStatus.TRIPLE_CLICK));
assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea));
assertThat(touchState.isMovedEnoughForDrag(), is(false));
+ assertThat(touchState.isDragCloseToVertical(), is(false));
}
}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index aa55e08..a0cfb31 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -19,6 +19,7 @@
import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemDisabled;
import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemEnabled;
import static android.widget.espresso.ContextMenuUtils.assertContextMenuIsNotDisplayed;
+import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
import static android.widget.espresso.DragHandleUtils.onHandleView;
import static android.widget.espresso.TextViewActions.mouseClick;
import static android.widget.espresso.TextViewActions.mouseClickOnTextAtIndex;
@@ -47,7 +48,6 @@
import android.view.textclassifier.TextClassifier;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.Suppress;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -88,11 +88,6 @@
mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
onView(withId(R.id.textview)).check(hasSelection("llo wor"));
- onHandleView(com.android.internal.R.id.selection_start_handle)
- .check(matches(isDisplayed()));
- onHandleView(com.android.internal.R.id.selection_end_handle)
- .check(matches(isDisplayed()));
-
onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
onView(withId(R.id.textview)).check(hasSelection(""));
}
@@ -193,7 +188,6 @@
}
@Test
- @Suppress // Consistently failing. b/29591177
public void testDragAndDrop_longClick() {
final String text = "abc def ghi.";
onView(withId(R.id.textview)).perform(mouseClick());
@@ -395,4 +389,29 @@
mouseTripleClickAndDragOnText(text.indexOf("ird"), text.indexOf("First")));
onView(withId(R.id.textview)).check(hasSelection(text));
}
+
+ @Test
+ public void testSelectionHandlesDisplay() {
+ final String helloWorld = "Hello world!";
+ onView(withId(R.id.textview)).perform(mouseClick());
+ onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+
+ onView(withId(R.id.textview)).perform(
+ mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
+ onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+
+ // Confirm that selection handles are shown when there is a selection.
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .check(matches(isDisplayed()));
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .check(matches(isDisplayed()));
+
+ onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
+ onView(withId(R.id.textview)).check(hasSelection(""));
+
+ // Confirm that the selection handles are not shown when there is no selection. This
+ // assertion is slow so we only do it in this one test case. The rest of the tests just
+ // assert via `hasSelection("")`.
+ assertNoSelectionHandles();
+ }
}
diff --git a/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/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/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..5a72b22 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -119,7 +119,7 @@
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);
@@ -297,11 +297,11 @@
*
* @param statusTypes an array of status type which the caller request.
*
- * @return statuses an array of statuses which response the caller's
- * request.
+ * @return statuses which response the caller's requests.
* @hide
*/
- public FrontendStatus[] getFrontendStatus(int[] statusTypes) {
+ @Nullable
+ public FrontendStatus getFrontendStatus(int[] statusTypes) {
return nativeGetFrontendStatus(statusTypes);
}
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index 4532122..19cfa32 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;
@@ -317,156 +322,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 +520,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/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/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/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
index 61e47f8..6e7a429 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -87,8 +87,10 @@
if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) {
return;
}
- final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
- mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+ if (mCreationTimestamp != 0L) {
+ final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
+ mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+ }
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 4ab9a9a..b07fc2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -61,6 +61,8 @@
"com.android.settings.category.ia.my_device_info";
public static final String CATEGORY_BATTERY_SAVER_SETTINGS =
"com.android.settings.category.ia.battery_saver_settings";
+ public static final String CATEGORY_SMART_BATTERY_SETTINGS =
+ "com.android.settings.category.ia.smart_battery_settings";
public static final Map<String, String> KEY_COMPAT_MAP;
diff --git a/packages/SettingsLib/src/com/android/settingslib/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/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index 605c861..340a6c7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -59,8 +59,9 @@
allKeys.add(CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
allKeys.add(CategoryKey.CATEGORY_GESTURES);
allKeys.add(CategoryKey.CATEGORY_NIGHT_DISPLAY);
+ allKeys.add(CategoryKey.CATEGORY_SMART_BATTERY_SETTINGS);
// DO NOT REMOVE ANYTHING ABOVE
- assertThat(allKeys.size()).isEqualTo(18);
+ assertThat(allKeys.size()).isEqualTo(19);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/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/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/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/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 8db0d02..02c4c5e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -46,6 +46,8 @@
*/
public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
+ public void snooze(StatusBarNotification sbn, int hours);
+
public float getMinDismissVelocity();
public boolean isDismissGesture(MotionEvent ev);
diff --git a/packages/SystemUI/res/drawable/ic_add_to_home.xml b/packages/SystemUI/res/drawable/ic_add_to_home.xml
new file mode 100644
index 0000000..2b40c62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_add_to_home.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,1.01L8,1c-1.1,0 -2,0.9 -2,2v3h2V5h10v14H8v-1H6v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM10,15h2V8H5v2h3.59L3,15.59 4.41,17 10,11.41V15z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_demote_conversation.xml b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
new file mode 100644
index 0000000..5a88160
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,2L4.83,2l2,2L20,4v12h-1.17l1.87,1.87c0.75,-0.29 1.3,-1.02 1.3,-1.87L22,4c0,-1.1 -0.9,-2 -2,-2zM6,12h2v2L6,14zM18,11L18,9h-6.17l2,2zM18,6h-8v1.17l0.83,0.83L18,8zM0.69,3.51l1.32,1.32L2,22l4,-4h9.17l5.31,5.31 1.41,-1.41L2.1,2.1 0.69,3.51zM6,16h-0.83l-0.59,0.59 -0.58,0.58L4,6.83l2,2L6,11h2.17l5,5L6,16z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
new file mode 100644
index 0000000..687c9c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,10.48L18,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4.48l4,3.98v-11l-4,3.98zM16,9.69L16,18L4,18L4,6h12v3.69z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_snooze.xml b/packages/SystemUI/res/drawable/ic_snooze.xml
index b0b03a9..f4c074d 100644
--- a/packages/SystemUI/res/drawable/ic_snooze.xml
+++ b/packages/SystemUI/res/drawable/ic_snooze.xml
@@ -1,12 +1,24 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
- android:fillColor="#757575"/>
- <path
- android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
- android:fillColor="#757575"/>
+ android:fillColor="@android:color/white"
+ android:pathData="M9,11h3.63L9,15.2L9,17h6v-2h-3.63L15,10.8L15,9L9,9v2zM16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM3.336,7.19l-1.28,-1.536L6.662,1.81l1.28,1.536zM12,6c3.86,0 7,3.14 7,7s-3.14,7 -7,7 -7,-3.14 -7,-7 3.14,-7 7,-7m0,-2c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml
new file mode 100644
index 0000000..4a731b3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml
new file mode 100644
index 0000000..9ede40b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star_border.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/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/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
new file mode 100644
index 0000000..8749b1a
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.systemui.statusbar.notification.row.NotificationConversationInfo
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/notification_guts"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:clipChildren="false"
+ android:clipToPadding="true"
+ android:orientation="vertical"
+ android:background="@color/notification_material_background_color"
+ android:paddingStart="@*android:dimen/notification_content_margin_start">
+
+ <!-- Package Info -->
+ <RelativeLayout
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_guts_conversation_header_height"
+ android:gravity="center_vertical"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+ <ImageView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/notification_guts_conversation_icon_size"
+ android:layout_height="@dimen/notification_guts_conversation_icon_size"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="6dp" />
+ <LinearLayout
+ android:id="@+id/names"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_guts_conversation_icon_size"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ android:layout_toEndOf="@id/conversation_icon">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/parent_channel_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/pkg_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.NotificationImportanceChannelGroup"
+ android:ellipsize="end"
+ android:maxLines="1"/>
+ <TextView
+ android:id="@+id/group_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/group_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/pkg_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:layout_toEndOf="@id/name"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/delegate_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:ellipsize="end"
+ android:text="@string/notification_delegate_header"
+ android:layout_toEndOf="@id/pkg_divider"
+ android:maxLines="1" />
+
+ <!-- end aligned fields -->
+ <ImageButton
+ android:id="@+id/demote"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/demote"
+ android:src="@drawable/ic_demote_conversation"
+ android:layout_toStartOf="@id/app_settings"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ <!-- Optional link to app. Only appears if the channel is not disabled and the app
+asked for it -->
+ <ImageButton
+ android:id="@+id/app_settings"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:visibility="gone"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/notification_app_settings"
+ android:src="@drawable/ic_info"
+ android:layout_toStartOf="@id/info"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ <ImageButton
+ android:id="@+id/info"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/notification_more_settings"
+ android:src="@drawable/ic_settings"
+ android:layout_alignParentEnd="true"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ </RelativeLayout>
+
+ <LinearLayout
+ android:id="@+id/actions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/bubble"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_favorite"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_create_bubble"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/home"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_home_screen"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_add_to_home"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/fave"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:gravity="left|center_vertical"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/snooze"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_menu_snooze_action"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_snooze"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/mute"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_mute"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_notifications_silence"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ </LinearLayout>
+
+</com.android.systemui.statusbar.notification.row.NotificationConversationInfo>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
deleted file mode 100644
index 3d63b7d..0000000
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:gravity="top"
- android:orientation="vertical"
- android:padding="@dimen/global_actions_padding"
- android:background="@drawable/rounded_bg_full">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <CheckBox
- android:id="@+id/checkbox_mic"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@string/screenrecord_mic_label"/>
- <CheckBox
- android:id="@+id/checkbox_taps"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@string/screenrecord_taps_label"/>
- <Button
- android:id="@+id/record_button"
- android:layout_width="match_parent"
- android:layout_height="50dp"
- android:text="@string/screenrecord_start_label"
- />
- </LinearLayout>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 640f31b..8963157 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -109,7 +109,7 @@
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
- wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
+ wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord
</string>
<!-- The minimum number of tiles to display in QuickSettings -->
@@ -117,7 +117,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls,screenrecord
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c26cb9a..53df025 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -213,6 +213,11 @@
<!-- The horizontal space around the buttons in the inline settings -->
<dimen name="notification_guts_button_horizontal_spacing">8dp</dimen>
+ <dimen name="notification_guts_conversation_header_height">84dp</dimen>
+ <dimen name="notification_guts_conversation_icon_size">52dp</dimen>
+ <dimen name="notification_guts_conversation_action_height">56dp</dimen>
+ <dimen name="notification_guts_conversation_action_text_padding_start">32dp</dimen>
+
<!-- The height of the header in inline settings -->
<dimen name="notification_guts_header_height">24dp</dimen>
@@ -1160,4 +1165,5 @@
<dimen name="magnifier_up_down_controls_width">45dp</dimen>
<dimen name="magnifier_up_down_controls_height">40dp</dimen>
+ <dimen name="app_icon_size">32dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4f532b7..9129938 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -928,6 +928,13 @@
<!-- QuickSettings: NFC (on) [CHAR LIMIT=NONE] -->
<string name="quick_settings_nfc_on">NFC is enabled</string>
+ <!-- QuickSettings: Screen record tile [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_screen_record_label">Screen Record</string>
+ <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] -->
+ <string name="quick_settings_screen_record_start">Start</string>
+ <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
+ <string name="quick_settings_screen_record_stop">Stop</string>
+
<!-- Recents: Text that shows above the navigation bar after launching a few apps. [CHAR LIMIT=NONE] -->
<string name="recents_swipe_up_onboarding">Swipe up to switch apps</string>
<!-- Recents: Text that shows above the navigation bar after launching several apps. [CHAR LIMIT=NONE] -->
@@ -1791,6 +1798,29 @@
<string name="notification_done">Done</string>
<!-- Notification: inline controls: undo block button -->
<string name="inline_undo">Undo</string>
+ <!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification -->
+ <string name="demote">Mark this notification as not a conversation</string>
+
+ <!-- [CHAR LIMIT=100] Mark this conversation as a favorite -->
+ <string name="notification_conversation_favorite">Favorite</string>
+
+ <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite -->
+ <string name="notification_conversation_unfavorite">Unfavorite</string>
+
+ <!-- [CHAR LIMIT=100] Mute this conversation -->
+ <string name="notification_conversation_mute">Mute</string>
+
+ <!-- [CHAR LIMIT=100] Umute this conversation -->
+ <string name="notification_conversation_unmute">Unmute</string>
+
+ <!-- [CHAR LIMIT=100] Show notification as bubble -->
+ <string name="notification_conversation_bubble">Show as bubble</string>
+
+ <!-- [CHAR LIMIT=100] Turn off bubbles for notification -->
+ <string name="notification_conversation_unbubble">Turn off bubbles</string>
+
+ <!-- [CHAR LIMIT=100] Add this conversation to home screen -->
+ <string name="notification_conversation_home_screen">Add to home screen</string>
<!-- Notification: Menu row: Content description for menu items. [CHAR LIMIT=NONE] -->
<string name="notification_menu_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> <xliff:g id="menu_description" example="notification controls">%2$s</xliff:g></string>
diff --git a/packages/SystemUI/src/com/android/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 73bfe25..eaa9d78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -16,42 +16,135 @@
package com.android.systemui.statusbar.notification.collection;
+import java.util.Arrays;
import java.util.List;
-
/**
* Utility class for dumping the results of a {@link ShadeListBuilder} to a debug string.
*/
public class ListDumper {
- /** See class description */
- public static String dumpList(List<ListEntry> entries) {
+ /**
+ * Creates a debug string for a list of grouped notifications that will be printed
+ * in the order given in a tiered/tree structure.
+ * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+ * entry to be in its current state (ie: filter, lifeExtender)
+ */
+ public static String dumpTree(
+ List<ListEntry> entries,
+ boolean includeRecordKeeping,
+ String indent) {
StringBuilder sb = new StringBuilder();
- for (int i = 0; i < entries.size(); i++) {
- ListEntry entry = entries.get(i);
- dumpEntry(entry, Integer.toString(i), "", sb);
+ final String childEntryIndent = indent + INDENT;
+ for (int topEntryIndex = 0; topEntryIndex < entries.size(); topEntryIndex++) {
+ ListEntry entry = entries.get(topEntryIndex);
+ dumpEntry(entry,
+ Integer.toString(topEntryIndex),
+ indent,
+ sb,
+ true,
+ includeRecordKeeping);
if (entry instanceof GroupEntry) {
GroupEntry ge = (GroupEntry) entry;
- for (int j = 0; j < ge.getChildren().size(); j++) {
- dumpEntry(
- ge.getChildren().get(j),
- Integer.toString(j),
- INDENT,
- sb);
+ List<NotificationEntry> children = ge.getChildren();
+ for (int childIndex = 0; childIndex < children.size(); childIndex++) {
+ dumpEntry(children.get(childIndex),
+ Integer.toString(topEntryIndex) + "." + Integer.toString(childIndex),
+ childEntryIndent,
+ sb,
+ true,
+ includeRecordKeeping);
}
}
}
return sb.toString();
}
+ /**
+ * Creates a debug string for a flat list of notifications
+ * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+ * entry to be in its current state (ie: filter, lifeExtender)
+ */
+ public static String dumpList(
+ List<NotificationEntry> entries,
+ boolean includeRecordKeeping,
+ String indent) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < entries.size(); j++) {
+ dumpEntry(
+ entries.get(j),
+ Integer.toString(j),
+ indent,
+ sb,
+ false,
+ includeRecordKeeping);
+ }
+ return sb.toString();
+ }
+
private static void dumpEntry(
- ListEntry entry, String index, String indent, StringBuilder sb) {
+ ListEntry entry,
+ String index,
+ String indent,
+ StringBuilder sb,
+ boolean includeParent,
+ boolean includeRecordKeeping) {
sb.append(indent)
.append("[").append(index).append("] ")
- .append(entry.getKey())
- .append(" (parent=")
- .append(entry.getParent() != null ? entry.getParent().getKey() : null)
- .append(")\n");
+ .append(entry.getKey());
+
+ if (includeParent) {
+ sb.append(" (parent=")
+ .append(entry.getParent() != null ? entry.getParent().getKey() : null)
+ .append(")");
+ }
+
+ if (entry.mNotifSection != null) {
+ sb.append(" sectionIndex=")
+ .append(entry.getSection())
+ .append(" sectionName=")
+ .append(entry.mNotifSection.getName());
+ }
+
+ if (includeRecordKeeping) {
+ NotificationEntry notifEntry = entry.getRepresentativeEntry();
+ StringBuilder rksb = new StringBuilder();
+
+ if (!notifEntry.mLifetimeExtenders.isEmpty()) {
+ String[] lifetimeExtenderNames = new String[notifEntry.mLifetimeExtenders.size()];
+ for (int i = 0; i < lifetimeExtenderNames.length; i++) {
+ lifetimeExtenderNames[i] = notifEntry.mLifetimeExtenders.get(i).getName();
+ }
+ rksb.append("lifetimeExtenders=")
+ .append(Arrays.toString(lifetimeExtenderNames))
+ .append(" ");
+ }
+
+ if (notifEntry.mExcludingFilter != null) {
+ rksb.append("filter=")
+ .append(notifEntry.mExcludingFilter)
+ .append(" ");
+ }
+
+ if (notifEntry.mNotifPromoter != null) {
+ rksb.append("promoter=")
+ .append(notifEntry.mNotifPromoter)
+ .append(" ");
+ }
+
+ if (notifEntry.hasInflationError()) {
+ rksb.append("hasInflationError ");
+ }
+
+ String rkString = rksb.toString();
+ if (!rkString.isEmpty()) {
+ sb.append("\n\t")
+ .append(indent)
+ .append(rkString);
+ }
+ }
+
+ sb.append("\n");
}
private static final String INDENT = " ";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index dc68c4b..56ad0e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -18,6 +18,8 @@
import android.annotation.Nullable;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+
/**
* Abstract superclass for top-level entries, i.e. things that can appear in the final notification
* list shown to users. In practice, this means either GroupEntries or NotificationEntries.
@@ -27,7 +29,9 @@
@Nullable private GroupEntry mParent;
@Nullable private GroupEntry mPreviousParent;
- private int mSection;
+ @Nullable NotifSection mNotifSection;
+
+ private int mSection = -1;
int mFirstAddedIteration = -1;
ListEntry(String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 4b15b7f..c488c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -47,6 +47,8 @@
import android.util.Log;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
@@ -56,6 +58,8 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.util.Assert;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -92,7 +96,7 @@
*/
@MainThread
@Singleton
-public class NotifCollection {
+public class NotifCollection implements Dumpable {
private final IStatusBarService mStatusBarService;
private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
@@ -107,9 +111,10 @@
private boolean mAmDispatchingToOtherCode;
@Inject
- public NotifCollection(IStatusBarService statusBarService) {
+ public NotifCollection(IStatusBarService statusBarService, DumpController dumpController) {
Assert.isMainThread();
mStatusBarService = statusBarService;
+ dumpController.registerDumpable(TAG, this);
}
/** Initializes the NotifCollection and registers it to receive notification events. */
@@ -442,4 +447,19 @@
public @interface CancellationReason {}
public static final int REASON_UNKNOWN = 0;
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
+
+ pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
+ if (entries.size() == 0) {
+ pw.println("\t\t None");
+ }
+ pw.println(
+ ListDumper.dumpList(
+ entries,
+ true,
+ "\t\t"));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/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/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 76c524b..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
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.collection;
import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
@@ -31,7 +30,10 @@
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;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
@@ -39,13 +41,15 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.logging.NotifEvent;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.util.Assert;
import com.android.systemui.util.time.SystemClock;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -63,7 +67,7 @@
*/
@MainThread
@Singleton
-public class ShadeListBuilder {
+public class ShadeListBuilder implements Dumpable {
private final SystemClock mSystemClock;
private final NotifLog mNotifLog;
@@ -79,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<>();
@@ -92,10 +96,14 @@
private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
@Inject
- public ShadeListBuilder(SystemClock systemClock, NotifLog notifLog) {
+ public ShadeListBuilder(
+ SystemClock systemClock,
+ NotifLog notifLog,
+ DumpController dumpController) {
Assert.isMainThread();
mSystemClock = systemClock;
mNotifLog = notifLog;
+ dumpController.registerDumpable(TAG, this);
}
/**
@@ -163,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) {
@@ -223,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);
@@ -311,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();
@@ -324,7 +335,7 @@
// Step 6: Dispatch the new list, first to any listeners and then to the view layer
mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n"
- + dumpList(mNotifList));
+ + ListDumper.dumpTree(mNotifList, false, "\t\t"));
dispatchOnBeforeRenderList(mReadOnlyNotifList);
if (mOnRenderListListener != null) {
mOnRenderListListener.onRenderList(mReadOnlyNotifList);
@@ -573,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;
@@ -582,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);
}
@@ -747,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)) {
@@ -772,6 +825,19 @@
}
}
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("\t" + TAG + " shade notifications:");
+ if (getShadeList().size() == 0) {
+ pw.println("\t\t None");
+ }
+
+ pw.println(ListDumper.dumpTree(
+ getShadeList(),
+ true,
+ "\t\t"));
+ }
+
/** See {@link #setOnRenderListListener(OnRenderListListener)} */
public interface OnRenderListListener {
/**
@@ -783,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/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/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 1ab20a9..5cd3e94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -41,6 +41,9 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.people.NotificationPersonExtractorPluginBoundary;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifierImpl;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
@@ -49,6 +52,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.Objects;
@@ -66,7 +70,6 @@
private final NotificationGroupManager mGroupManager;
private final NotificationGutsManager mGutsManager;
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
-
private final Context mContext;
private final NotificationRowContentBinder mRowContentBinder;
private final NotificationMessagingUtil mMessagingUtil;
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/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index 02acc81..2374cde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -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",
@@ -170,13 +171,14 @@
public static final int LIST_BUILD_COMPLETE = 4;
public static final int PRE_GROUP_FILTER_INVALIDATED = 5;
public static final int PROMOTER_INVALIDATED = 6;
- public static final int SECTIONS_PROVIDER_INVALIDATED = 7;
+ public static final int SECTION_INVALIDATED = 7;
public static final int COMPARATOR_INVALIDATED = 8;
public static final int PARENT_CHANGED = 9;
public static final int FILTER_CHANGED = 10;
public static final int PROMOTER_CHANGED = 11;
public static final int PRE_RENDER_FILTER_INVALIDATED = 12;
- private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 13;
+ public static final int SECTION_CHANGED = 13;
+ private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14;
/**
* Events related to {@link NotificationEntryManager}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a8a35d0..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
@@ -1150,6 +1150,7 @@
mMenuRow = plugin;
if (mMenuRow.shouldUseDefaultMenuItems()) {
ArrayList<MenuItem> items = new ArrayList<>();
+ items.add(NotificationMenuRow.createConversationItem(mContext));
items.add(NotificationMenuRow.createInfoItem(mContext));
items.add(NotificationMenuRow.createSnoozeItem(mContext));
items.add(NotificationMenuRow.createAppOpsItem(mContext));
@@ -1163,7 +1164,7 @@
@Override
public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
boolean existed = mMenuRow.getMenuView() != null;
- mMenuRow = new NotificationMenuRow(mContext); // Back to default
+ mMenuRow = new NotificationMenuRow(mContext);
if (existed) {
createMenu();
}
@@ -1720,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
new file mode 100644
index 0000000..ec420f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
+import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.lang.annotation.Retention;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The guts of a conversation notification revealed when performing a long press.
+ */
+public class NotificationConversationInfo extends LinearLayout implements
+ NotificationGuts.GutsContent {
+ private static final String TAG = "ConversationGuts";
+
+
+ private INotificationManager mINotificationManager;
+ private LauncherApps mLauncherApps;
+ ShortcutManager mShortcutManager;
+ private PackageManager mPm;
+ private VisualStabilityManager mVisualStabilityManager;
+
+ private String mPackageName;
+ private String mAppName;
+ private int mAppUid;
+ private String mDelegatePkg;
+ private NotificationChannel mNotificationChannel;
+ private ShortcutInfo mShortcutInfo;
+ private String mConversationId;
+ private NotificationEntry mEntry;
+ private StatusBarNotification mSbn;
+ private boolean mIsDeviceProvisioned;
+
+ private int mStartingChannelImportance;
+ private boolean mStartedAsBubble;
+ private boolean mIsBubbleable;
+ // TODO: remove when launcher api works
+ @VisibleForTesting
+ boolean mShowHomeScreen = false;
+
+ private @UpdateChannelRunnable.Action int mSelectedAction = -1;
+
+ private OnSnoozeClickListener mOnSnoozeClickListener;
+ private OnSettingsClickListener mOnSettingsClickListener;
+ private OnAppSettingsClickListener mAppSettingsClickListener;
+ private NotificationGuts mGutsContainer;
+ private BubbleController mBubbleController;
+
+ @VisibleForTesting
+ boolean mSkipPost = false;
+
+ private OnClickListener mOnBubbleClick = v -> {
+ mSelectedAction = ACTION_BUBBLE;
+ if (mStartedAsBubble) {
+ mBubbleController.onUserDemotedBubbleFromNotification(mEntry);
+ } else {
+ mBubbleController.onUserCreatedBubbleFromNotification(mEntry);
+ }
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnHomeClick = v -> {
+ mSelectedAction = ACTION_HOME;
+ mShortcutManager.requestPinShortcut(mShortcutInfo, null);
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnFavoriteClick = v -> {
+ mSelectedAction = ACTION_FAVORITE;
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnSnoozeClick = v -> {
+ mSelectedAction = ACTION_SNOOZE;
+ mOnSnoozeClickListener.onClick(v, 1);
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnMuteClick = v -> {
+ mSelectedAction = ACTION_MUTE;
+ closeControls(v, true);
+ };
+
+ public NotificationConversationInfo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public interface OnSettingsClickListener {
+ void onClick(View v, NotificationChannel channel, int appUid);
+ }
+
+ public interface OnAppSettingsClickListener {
+ void onClick(View v, Intent intent);
+ }
+
+ public interface OnSnoozeClickListener {
+ void onClick(View v, int hoursToSnooze);
+ }
+
+ public void bindNotification(
+ ShortcutManager shortcutManager,
+ LauncherApps launcherApps,
+ PackageManager pm,
+ INotificationManager iNotificationManager,
+ VisualStabilityManager visualStabilityManager,
+ String pkg,
+ NotificationChannel notificationChannel,
+ NotificationEntry entry,
+ OnSettingsClickListener onSettingsClick,
+ OnAppSettingsClickListener onAppSettingsClick,
+ OnSnoozeClickListener onSnoozeClickListener,
+ boolean isDeviceProvisioned) {
+ mSelectedAction = -1;
+ mINotificationManager = iNotificationManager;
+ mVisualStabilityManager = visualStabilityManager;
+ mBubbleController = Dependency.get(BubbleController.class);
+ mPackageName = pkg;
+ mEntry = entry;
+ mSbn = entry.getSbn();
+ mPm = pm;
+ mAppSettingsClickListener = onAppSettingsClick;
+ mAppName = mPackageName;
+ mOnSettingsClickListener = onSettingsClick;
+ mNotificationChannel = notificationChannel;
+ mStartingChannelImportance = mNotificationChannel.getImportance();
+ mAppUid = mSbn.getUid();
+ mDelegatePkg = mSbn.getOpPkg();
+ mIsDeviceProvisioned = isDeviceProvisioned;
+ mOnSnoozeClickListener = onSnoozeClickListener;
+
+ mShortcutManager = shortcutManager;
+ mLauncherApps = launcherApps;
+ mConversationId = mNotificationChannel.getConversationId();
+ if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+ mConversationId = mSbn.getNotification().getShortcutId();
+ }
+ // TODO: flag this when flag exists
+ if (TextUtils.isEmpty(mConversationId)) {
+ mConversationId = mSbn.getId() + mSbn.getTag() + PLACEHOLDER_CONVERSATION_ID;
+ }
+ // TODO: consider querying this earlier in the notification pipeline and passing it in
+ LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
+ .setPackage(mPackageName)
+ .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
+ .setShortcutIds(Arrays.asList(mConversationId));
+ List<ShortcutInfo> shortcuts = mLauncherApps.getShortcuts(query, mSbn.getUser());
+ if (shortcuts != null && !shortcuts.isEmpty()) {
+ mShortcutInfo = shortcuts.get(0);
+ }
+
+ mIsBubbleable = mEntry.getBubbleMetadata() != null;
+ mStartedAsBubble = mEntry.isBubble();
+
+ createConversationChannelIfNeeded();
+
+ bindHeader();
+ bindActions();
+
+ }
+
+ void createConversationChannelIfNeeded() {
+ // If this channel is not already a customized conversation channel, create
+ // a custom channel
+ if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+ try {
+ // TODO: associate this key with this channel service side so the customization
+ // isn't forgotten on the next update
+ mINotificationManager.createConversationNotificationChannelForPackage(
+ mPackageName, mAppUid, mNotificationChannel, mConversationId);
+ mNotificationChannel = mINotificationManager.getConversationNotificationChannel(
+ mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName,
+ mNotificationChannel.getId(), false, mConversationId);
+
+ // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a
+ // time
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not create conversation channel", e);
+ }
+ }
+ }
+
+ private void bindActions() {
+ // TODO: figure out what should happen for non-configurable channels
+
+ Button bubble = findViewById(R.id.bubble);
+ bubble.setVisibility(mIsBubbleable ? VISIBLE : GONE);
+ bubble.setOnClickListener(mOnBubbleClick);
+ if (mStartedAsBubble) {
+ bubble.setText(R.string.notification_conversation_unbubble);
+ } else {
+ bubble.setText(R.string.notification_conversation_bubble);
+ }
+
+ Button home = findViewById(R.id.home);
+ home.setOnClickListener(mOnHomeClick);
+ home.setVisibility(mShowHomeScreen && mShortcutInfo != null
+ && mShortcutManager.isRequestPinShortcutSupported()
+ ? VISIBLE : GONE);
+
+ Button favorite = findViewById(R.id.fave);
+ favorite.setOnClickListener(mOnFavoriteClick);
+ if (mNotificationChannel.canBypassDnd()) {
+ favorite.setText(R.string.notification_conversation_unfavorite);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_star), null, null, null);
+ } else {
+ favorite.setText(R.string.notification_conversation_favorite);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_star_border), null, null, null);
+ }
+
+ Button snooze = findViewById(R.id.snooze);
+ snooze.setOnClickListener(mOnSnoozeClick);
+
+ Button mute = findViewById(R.id.mute);
+ mute.setOnClickListener(mOnMuteClick);
+ if (mStartingChannelImportance >= IMPORTANCE_DEFAULT
+ || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) {
+ mute.setText(R.string.notification_conversation_mute);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null);
+ } else {
+ mute.setText(R.string.notification_conversation_unmute);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null);
+ }
+
+ }
+
+ private void bindHeader() {
+ bindConversationDetails();
+
+ // Delegate
+ bindDelegate();
+
+ // Set up app settings link (i.e. Customize)
+ View settingsLinkView = findViewById(R.id.app_settings);
+ Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
+ mNotificationChannel,
+ mSbn.getId(), mSbn.getTag());
+ if (settingsIntent != null
+ && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
+ settingsLinkView.setVisibility(VISIBLE);
+ settingsLinkView.setOnClickListener((View view) -> {
+ mAppSettingsClickListener.onClick(view, settingsIntent);
+ });
+ } else {
+ settingsLinkView.setVisibility(View.GONE);
+ }
+
+ // System Settings button.
+ final View settingsButton = findViewById(R.id.info);
+ settingsButton.setOnClickListener(getSettingsOnClickListener());
+ settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
+ }
+
+ private OnClickListener getSettingsOnClickListener() {
+ if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
+ final int appUidF = mAppUid;
+ return ((View view) -> {
+ mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF);
+ });
+ }
+ return null;
+ }
+
+ private void bindConversationDetails() {
+ final TextView channelName = findViewById(R.id.parent_channel_name);
+ channelName.setText(mNotificationChannel.getName());
+
+ bindGroup();
+ bindName();
+ bindPackage();
+ bindIcon();
+
+ }
+
+ private void bindIcon() {
+ ImageView image = findViewById(R.id.conversation_icon);
+ if (mShortcutInfo != null) {
+ image.setImageDrawable(mLauncherApps.getShortcutBadgedIconDrawable(mShortcutInfo,
+ mContext.getResources().getDisplayMetrics().densityDpi));
+ } else {
+ // TODO: flag this behavior
+ if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
+ // TODO: maybe use a generic group icon, or a composite of recent senders
+ image.setImageDrawable(mPm.getDefaultActivityIcon());
+ } else {
+ final List<Notification.MessagingStyle.Message> messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ (Parcelable[]) mSbn.getNotification().extras.get(
+ Notification.EXTRA_MESSAGES));
+
+ final Notification.MessagingStyle.Message latestMessage =
+ Notification.MessagingStyle.findLatestIncomingMessage(messages);
+ Icon personIcon = latestMessage.getSenderPerson().getIcon();
+ if (personIcon != null) {
+ image.setImageIcon(latestMessage.getSenderPerson().getIcon());
+ } else {
+ // TODO: choose something better
+ image.setImageDrawable(mPm.getDefaultActivityIcon());
+ }
+ }
+ }
+ }
+
+ private void bindName() {
+ TextView name = findViewById(R.id.name);
+ if (mShortcutInfo != null) {
+ name.setText(mShortcutInfo.getShortLabel());
+ } else {
+ // TODO: flag this behavior
+ Bundle extras = mSbn.getNotification().extras;
+ String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
+ if (TextUtils.isEmpty(nameString)) {
+ nameString = extras.getString(Notification.EXTRA_TITLE);
+ }
+ name.setText(nameString);
+ }
+ }
+
+ private void bindPackage() {
+ ApplicationInfo info;
+ try {
+ info = mPm.getApplicationInfo(
+ mPackageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (info != null) {
+ mAppName = String.valueOf(mPm.getApplicationLabel(info));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
+ }
+
+ private void bindDelegate() {
+ TextView delegateView = findViewById(R.id.delegate_name);
+ TextView dividerView = findViewById(R.id.pkg_divider);
+
+ if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
+ // this notification was posted by a delegate!
+ delegateView.setVisibility(View.VISIBLE);
+ dividerView.setVisibility(View.VISIBLE);
+ } else {
+ delegateView.setVisibility(View.GONE);
+ dividerView.setVisibility(View.GONE);
+ }
+ }
+
+ private void bindGroup() {
+ // Set group information if this channel has an associated group.
+ CharSequence groupName = null;
+ if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) {
+ try {
+ final NotificationChannelGroup notificationChannelGroup =
+ mINotificationManager.getNotificationChannelGroupForPackage(
+ mNotificationChannel.getGroup(), mPackageName, mAppUid);
+ if (notificationChannelGroup != null) {
+ groupName = notificationChannelGroup.getName();
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ TextView groupNameView = findViewById(R.id.group_name);
+ View groupDivider = findViewById(R.id.group_divider);
+ if (groupName != null) {
+ groupNameView.setText(groupName);
+ groupNameView.setVisibility(VISIBLE);
+ groupDivider.setVisibility(VISIBLE);
+ } else {
+ groupNameView.setVisibility(GONE);
+ groupDivider.setVisibility(GONE);
+ }
+ }
+
+ @Override
+ public boolean post(Runnable action) {
+ if (mSkipPost) {
+ action.run();
+ return true;
+ } else {
+ return super.post(action);
+ }
+ }
+
+ @Override
+ public void onFinishedClosing() {
+ // TODO: do we need to do anything here?
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (mGutsContainer != null &&
+ event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ if (mGutsContainer.isExposed()) {
+ event.getText().add(mContext.getString(
+ R.string.notification_channel_controls_opened_accessibility, mAppName));
+ } else {
+ event.getText().add(mContext.getString(
+ R.string.notification_channel_controls_closed_accessibility, mAppName));
+ }
+ }
+ }
+
+ private Intent getAppSettingsIntent(PackageManager pm, String packageName,
+ NotificationChannel channel, int id, String tag) {
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
+ .setPackage(packageName);
+ final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
+ intent,
+ PackageManager.MATCH_DEFAULT_ONLY
+ );
+ if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
+ return null;
+ }
+ final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
+ intent.setClassName(activityInfo.packageName, activityInfo.name);
+ if (channel != null) {
+ intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
+ }
+ intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
+ intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
+ return intent;
+ }
+
+ /**
+ * Closes the controls and commits the updated importance values (indirectly).
+ *
+ * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
+ * user does not have the ability to undo the action anymore.
+ */
+ @VisibleForTesting
+ void closeControls(View v, boolean save) {
+ int[] parentLoc = new int[2];
+ int[] targetLoc = new int[2];
+ mGutsContainer.getLocationOnScreen(parentLoc);
+ v.getLocationOnScreen(targetLoc);
+ final int centerX = v.getWidth() / 2;
+ final int centerY = v.getHeight() / 2;
+ final int x = targetLoc[0] - parentLoc[0] + centerX;
+ final int y = targetLoc[1] - parentLoc[1] + centerY;
+ mGutsContainer.closeControls(x, y, save, false /* force */);
+ }
+
+ @Override
+ public void setGutsParent(NotificationGuts guts) {
+ mGutsContainer = guts;
+ }
+
+ @Override
+ public boolean willBeRemoved() {
+ return false;
+ }
+
+ @Override
+ public boolean shouldBeSaved() {
+ return mSelectedAction > -1;
+ }
+
+ @Override
+ public View getContentView() {
+ return this;
+ }
+
+ @Override
+ public boolean handleCloseControls(boolean save, boolean force) {
+ if (save && mSelectedAction > -1) {
+ Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+ bgHandler.post(
+ new UpdateChannelRunnable(mINotificationManager, mPackageName,
+ mAppUid, mSelectedAction, mNotificationChannel));
+ mVisualStabilityManager.temporarilyAllowReordering();
+ }
+ return false;
+ }
+
+ @Override
+ public int getActualHeight() {
+ return getHeight();
+ }
+
+ @VisibleForTesting
+ public boolean isAnimating() {
+ return false;
+ }
+
+ static class UpdateChannelRunnable implements Runnable {
+
+ @Retention(SOURCE)
+ @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE,
+ ACTION_DEMOTE})
+ private @interface Action {}
+ static final int ACTION_BUBBLE = 0;
+ static final int ACTION_HOME = 1;
+ static final int ACTION_FAVORITE = 2;
+ static final int ACTION_SNOOZE = 3;
+ static final int ACTION_MUTE = 4;
+ static final int ACTION_DEMOTE = 5;
+
+ private final INotificationManager mINotificationManager;
+ private final String mAppPkg;
+ private final int mAppUid;
+ private NotificationChannel mChannelToUpdate;
+ private final @Action int mAction;
+
+ public UpdateChannelRunnable(INotificationManager notificationManager,
+ String packageName, int appUid, @Action int action,
+ @NonNull NotificationChannel channelToUpdate) {
+ mINotificationManager = notificationManager;
+ mAppPkg = packageName;
+ mAppUid = appUid;
+ mChannelToUpdate = channelToUpdate;
+ mAction = action;
+ }
+
+ @Override
+ public void run() {
+ try {
+ switch (mAction) {
+ case ACTION_BUBBLE:
+ mChannelToUpdate.setAllowBubbles(!mChannelToUpdate.canBubble());
+ break;
+ case ACTION_FAVORITE:
+ // TODO: extend beyond DND
+ mChannelToUpdate.setBypassDnd(!mChannelToUpdate.canBypassDnd());
+ break;
+ case ACTION_MUTE:
+ if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED
+ || mChannelToUpdate.getImportance() >= IMPORTANCE_DEFAULT) {
+ mChannelToUpdate.setImportance(IMPORTANCE_LOW);
+ } else {
+ mChannelToUpdate.setImportance(Math.max(
+ mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
+ }
+ break;
+ case ACTION_DEMOTE:
+ // TODO: when demotion status field exists on notificationchannel
+ break;
+
+ }
+
+ if (mAction != ACTION_HOME && mAction != ACTION_SNOOZE) {
+ mINotificationManager.updateNotificationChannelForPackage(
+ mAppPkg, mAppUid, mChannelToUpdate);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to update notification channel", e);
+ }
+ }
+ }
+
+ @Retention(SOURCE)
+ @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE})
+ private @interface AlertingBehavior {}
+ private static final int BEHAVIOR_ALERTING = 0;
+ private static final int BEHAVIOR_SILENT = 1;
+ private static final int BEHAVIOR_BUBBLE = 2;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 779a224..6789c81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -23,7 +23,9 @@
import android.app.NotificationChannel;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.pm.ShortcutManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -228,6 +230,9 @@
initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
} else if (gutsView instanceof NotificationInfo) {
initializeNotificationInfo(row, (NotificationInfo) gutsView);
+ } else if (gutsView instanceof NotificationConversationInfo) {
+ initializeConversationNotificationInfo(
+ row, (NotificationConversationInfo) gutsView);
}
return true;
} catch (Exception e) {
@@ -339,6 +344,66 @@
}
/**
+ * Sets up the {@link NotificationConversationInfo} inside the notification row's guts.
+ * @param row view to set up the guts for
+ * @param notificationInfoView view to set up/bind within {@code row}
+ */
+ @VisibleForTesting
+ void initializeConversationNotificationInfo(
+ final ExpandableNotificationRow row,
+ NotificationConversationInfo notificationInfoView) throws Exception {
+ NotificationGuts guts = row.getGuts();
+ StatusBarNotification sbn = row.getEntry().getSbn();
+ String packageName = sbn.getPackageName();
+ // Settings link is only valid for notifications that specify a non-system user
+ NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null;
+ UserHandle userHandle = sbn.getUser();
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(
+ mContext, userHandle.getIdentifier());
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
+ INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ final NotificationConversationInfo.OnAppSettingsClickListener onAppSettingsClick =
+ (View v, Intent intent) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
+ guts.resetFalsingCheck();
+ mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(),
+ row);
+ };
+
+ final NotificationConversationInfo.OnSnoozeClickListener onSnoozeClickListener =
+ (View v, int hours) -> {
+ mListContainer.getSwipeActionHelper().snooze(sbn, hours);
+ };
+
+ if (!userHandle.equals(UserHandle.ALL)
+ || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+ onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ mOnSettingsClickListener.onSettingsClick(sbn.getKey());
+ startAppNotificationSettingsActivity(packageName, appUid, channel, row);
+ };
+ }
+
+ notificationInfoView.bindNotification(
+ shortcutManager,
+ launcherApps,
+ pmUser,
+ iNotificationManager,
+ mVisualStabilityManager,
+ packageName,
+ row.getEntry().getChannel(),
+ row.getEntry(),
+ onSettingsClick,
+ onAppSettingsClick,
+ onSnoozeClickListener,
+ mDeviceProvisionedController.isDeviceProvisioned());
+
+ }
+
+ /**
* Closes guts or notification menus that might be visible and saves any changes.
*
* @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index edfd1b4..212cba6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -83,7 +83,6 @@
private OnMenuEventListener mMenuListener;
private boolean mDismissRtl;
private boolean mIsForeground;
- private final boolean mIsUsingBidirectionalSwipe;
private ValueAnimator mFadeAnimator;
private boolean mAnimating;
@@ -116,19 +115,11 @@
private boolean mIsUserTouching;
public NotificationMenuRow(Context context) {
- //TODO: (b/131242807) not using bidirectional swipe for now
- this(context, false);
- }
-
- // Only needed for testing until we want to turn bidirectional swipe back on
- @VisibleForTesting
- NotificationMenuRow(Context context, boolean isUsingBidirectionalSwipe) {
mContext = context;
mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);
mHandler = new Handler(Looper.getMainLooper());
mLeftMenuItems = new ArrayList<>();
mRightMenuItems = new ArrayList<>();
- mIsUsingBidirectionalSwipe = isUsingBidirectionalSwipe;
}
@Override
@@ -269,24 +260,18 @@
mSnoozeItem = createSnoozeItem(mContext);
}
mAppOpsItem = createAppOpsItem(mContext);
- if (mIsUsingBidirectionalSwipe) {
- mInfoItem = createInfoItem(mContext,
- mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_SILENT);
+ if (mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_PEOPLE) {
+ mInfoItem = createConversationItem(mContext);
} else {
mInfoItem = createInfoItem(mContext);
}
- if (!mIsUsingBidirectionalSwipe) {
- if (!isForeground && showSnooze) {
- mRightMenuItems.add(mSnoozeItem);
- }
- mRightMenuItems.add(mInfoItem);
- mRightMenuItems.add(mAppOpsItem);
- mLeftMenuItems.addAll(mRightMenuItems);
- } else {
- ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems;
- menuItems.add(mInfoItem);
+ if (!isForeground && showSnooze) {
+ mRightMenuItems.add(mSnoozeItem);
}
+ mRightMenuItems.add(mInfoItem);
+ mRightMenuItems.add(mAppOpsItem);
+ mLeftMenuItems.addAll(mRightMenuItems);
populateMenuViews();
if (resetState) {
@@ -633,12 +618,12 @@
@Override
public boolean shouldShowGutsOnSnapOpen() {
- return mIsUsingBidirectionalSwipe;
+ return false;
}
@Override
public MenuItem menuItemToExposeOnSnap() {
- return mIsUsingBidirectionalSwipe ? mInfoItem : null;
+ return null;
}
@Override
@@ -664,6 +649,16 @@
return snooze;
}
+ static NotificationMenuItem createConversationItem(Context context) {
+ Resources res = context.getResources();
+ String infoDescription = res.getString(R.string.notification_menu_gear_description);
+ NotificationConversationInfo infoContent =
+ (NotificationConversationInfo) LayoutInflater.from(context).inflate(
+ R.layout.notification_conversation_info, null, false);
+ return new NotificationMenuItem(context, infoDescription, infoContent,
+ R.drawable.ic_settings);
+ }
+
static NotificationMenuItem createInfoItem(Context context) {
Resources res = context.getResources();
String infoDescription = res.getString(R.string.notification_menu_gear_description);
@@ -673,17 +668,6 @@
R.drawable.ic_settings);
}
- static NotificationMenuItem createInfoItem(Context context, boolean isCurrentlySilent) {
- Resources res = context.getResources();
- String infoDescription = res.getString(R.string.notification_menu_gear_description);
- NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
- R.layout.notification_info, null, false);
- int iconResId = isCurrentlySilent
- ? R.drawable.ic_notifications_silence
- : R.drawable.ic_notifications_alert;
- return new NotificationMenuItem(context, infoDescription, infoContent, iconResId);
- }
-
static MenuItem createAppOpsItem(Context context) {
AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(
R.layout.app_ops_info, null, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 823dd5a..dc2d99c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -6207,6 +6207,11 @@
}
@Override
+ public void onSnooze(StatusBarNotification sbn, int hours) {
+ mStatusBar.setNotificationSnoozed(sbn, hours);
+ }
+
+ @Override
public boolean shouldDismissQuickly() {
return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 4845ea1..6c0655e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -282,6 +282,11 @@
mCallback.onSnooze(sbn, snoozeOption);
}
+ @Override
+ public void snooze(StatusBarNotification sbn, int hours) {
+ mCallback.onSnooze(sbn, hours);
+ }
+
@VisibleForTesting
protected void handleMenuCoveredOrDismissed() {
View exposedMenuView = getExposedMenuView();
@@ -447,6 +452,8 @@
void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
+ void onSnooze(StatusBarNotification sbn, int hours);
+
void onDismiss();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index f25f910..9840a7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -97,6 +97,8 @@
// The edge width where touch down is allowed
private int mEdgeWidth;
+ // The bottom gesture area height
+ private int mBottomGestureHeight;
// The slop to distinguish between horizontal and vertical motion
private final float mTouchSlop;
// Duration after which we consider the event as longpress.
@@ -174,6 +176,8 @@
public void updateCurrentUserResources(Resources res) {
mEdgeWidth = res.getDimensionPixelSize(
com.android.internal.R.dimen.config_backGestureInset);
+ mBottomGestureHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_gesture_height);
}
/**
@@ -316,6 +320,11 @@
return false;
}
+ // Disallow if we are in the bottom gesture area
+ if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
+ return false;
+ }
+
// Always allow if the user is in a transient sticky immersive state
if (mIsNavBarShownTransiently) {
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 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/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ba70cf4..dc9cf77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -4026,6 +4026,11 @@
}
}
+ public void setNotificationSnoozed(StatusBarNotification sbn, int hoursToSnooze) {
+ mNotificationListener.snoozeNotification(sbn.getKey(),
+ hoursToSnooze * 60 * 60 * 1000);
+ }
+
@Override
public void toggleSplitScreen() {
toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
diff --git a/packages/SystemUI/src/com/android/systemui/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/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index cca76bd..8a1759d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -27,6 +27,9 @@
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
import java.util.WeakHashMap
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
/**
* Extension function for all objects which will return a PhysicsAnimator instance for that object.
@@ -35,6 +38,15 @@
private const val TAG = "PhysicsAnimator"
+private val UNSET = -Float.MAX_VALUE
+
+/**
+ * [FlingAnimation] multiplies the friction set via [FlingAnimation.setFriction] by 4.2f, which is
+ * where this number comes from. We use it in [PhysicsAnimator.flingThenSpring] to calculate the
+ * minimum velocity for a fling to reach a certain value, given the fling's friction.
+ */
+private const val FLING_FRICTION_SCALAR_MULTIPLIER = 4.2f
+
typealias EndAction = () -> Unit
/** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
@@ -236,6 +248,71 @@
}
/**
+ * Flings a property using the given start velocity. If the fling animation reaches the min/max
+ * bounds (from the [flingConfig]) with velocity remaining, it'll overshoot it and spring back.
+ *
+ * If the object is already out of the fling bounds, it will immediately spring back within
+ * bounds.
+ *
+ * This is useful for animating objects that are bounded by constraints such as screen edges,
+ * since otherwise the fling animation would end abruptly upon reaching the min/max bounds.
+ *
+ * @param property The property to animate.
+ * @param startVelocity The velocity, in pixels/second, with which to start the fling. If the
+ * object is already outside the fling bounds, this velocity will be used as the start velocity
+ * of the spring that will spring it back within bounds.
+ * @param flingMustReachMinOrMax If true, the fling animation is guaranteed to reach either its
+ * minimum bound (if [startVelocity] is negative) or maximum bound (if it's positive). The
+ * animator will use startVelocity if it's sufficient, or add more velocity if necessary. This
+ * is useful when fling's deceleration-based physics are preferable to the acceleration-based
+ * forces used by springs - typically, when you're allowing the user to move an object somewhere
+ * on the screen, but it needs to be along an edge.
+ * @param flingConfig The configuration to use for the fling portion of the animation.
+ * @param springConfig The configuration to use for the spring portion of the animation.
+ */
+ @JvmOverloads
+ fun flingThenSpring(
+ property: FloatPropertyCompat<in T>,
+ startVelocity: Float,
+ flingConfig: FlingConfig,
+ springConfig: SpringConfig,
+ flingMustReachMinOrMax: Boolean = false
+ ): PhysicsAnimator<T> {
+ val flingConfigCopy = flingConfig.copy()
+ val springConfigCopy = springConfig.copy()
+ val toAtLeast = if (startVelocity < 0) flingConfig.min else flingConfig.max
+
+ // If the fling needs to reach min/max, calculate the velocity required to do so and use
+ // that if the provided start velocity is not sufficient.
+ if (flingMustReachMinOrMax &&
+ toAtLeast != -Float.MAX_VALUE && toAtLeast != Float.MAX_VALUE) {
+ val distanceToDestination = toAtLeast - property.getValue(target)
+
+ // The minimum velocity required for the fling to end up at the given destination,
+ // taking the provided fling friction value.
+ val velocityToReachDestination = distanceToDestination *
+ (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
+
+ // Try to use the provided start velocity, but use the required velocity to reach the
+ // destination if the provided velocity is insufficient.
+ val sufficientVelocity =
+ if (distanceToDestination < 0)
+ min(velocityToReachDestination, startVelocity)
+ else
+ max(velocityToReachDestination, startVelocity)
+
+ flingConfigCopy.startVelocity = sufficientVelocity
+ springConfigCopy.finalPosition = toAtLeast
+ } else {
+ flingConfigCopy.startVelocity = startVelocity
+ }
+
+ flingConfigs[property] = flingConfigCopy
+ springConfigs[property] = springConfigCopy
+ return this
+ }
+
+ /**
* Adds a listener that will be called whenever any property on the animated object is updated.
* This will be called on every animation frame, with the current value of the animated object
* and the new property values.
@@ -246,7 +323,7 @@
}
/**
- * Adds a listener that will be called whenever a property's animation ends. This is useful if
+ * Adds a listener that will be called when a property stops animating. This is useful if
* you care about a specific property ending, or want to use the end value/end velocity from a
* particular property's animation. If you just want to run an action when all property
* animations have ended, use [withEndActions].
@@ -311,6 +388,114 @@
"your test setup.")
}
+ // Functions that will actually start the animations. These are run after we build and add
+ // the InternalListener, since some animations might update/end immediately and we don't
+ // want to miss those updates.
+ val animationStartActions = ArrayList<() -> Unit>()
+
+ for (animatedProperty in getAnimatedProperties()) {
+ val flingConfig = flingConfigs[animatedProperty]
+ val springConfig = springConfigs[animatedProperty]
+
+ // The property's current value on the object.
+ val currentValue = animatedProperty.getValue(target)
+
+ // Start by checking for a fling configuration. If one is present, we're either flinging
+ // or flinging-then-springing. Either way, we'll want to start the fling first.
+ if (flingConfig != null) {
+ animationStartActions.add {
+ // When the animation is starting, adjust the min/max bounds to include the
+ // current value of the property, if necessary. This is required to allow a
+ // fling to bring an out-of-bounds object back into bounds. For example, if an
+ // object was dragged halfway off the left side of the screen, but then flung to
+ // the right, we don't want the animation to end instantly just because the
+ // object started out of bounds. If the fling is in the direction that would
+ // take it farther out of bounds, it will end instantly as expected.
+ flingConfig.apply {
+ min = min(currentValue, this.min)
+ max = max(currentValue, this.max)
+ }
+
+ // Apply the configuration and start the animation.
+ getFlingAnimation(animatedProperty)
+ .also { flingConfig.applyToAnimation(it) }
+ .start()
+ }
+ }
+
+ // Check for a spring configuration. If one is present, we're either springing, or
+ // flinging-then-springing.
+ if (springConfig != null) {
+
+ // If there is no corresponding fling config, we're only springing.
+ if (flingConfig == null) {
+ // Apply the configuration and start the animation.
+ val springAnim = getSpringAnimation(animatedProperty)
+ springConfig.applyToAnimation(springAnim)
+ animationStartActions.add(springAnim::start)
+ } else {
+ // If there's a corresponding fling config, we're flinging-then-springing. Save
+ // the fling's original bounds so we can spring to them when the fling ends.
+ val flingMin = flingConfig.min
+ val flingMax = flingConfig.max
+
+ // Add an end listener that will start the spring when the fling ends.
+ endListeners.add(0, object : EndListener<T> {
+ override fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ ) {
+ // If this isn't the relevant property, it wasn't a fling, or the fling
+ // was explicitly cancelled, don't spring.
+ if (property != animatedProperty || !wasFling || canceled) {
+ return
+ }
+
+ val endedWithVelocity = abs(finalVelocity) > 0
+
+ // If the object was out of bounds when the fling animation started, it
+ // will immediately end. In that case, we'll spring it back in bounds.
+ val endedOutOfBounds = finalValue !in flingMin..flingMax
+
+ // If the fling ended either out of bounds or with remaining velocity,
+ // it's time to spring.
+ if (endedWithVelocity || endedOutOfBounds) {
+ springConfig.startVelocity = finalVelocity
+
+ // If the spring's final position isn't set, this is a
+ // flingThenSpring where flingMustReachMinOrMax was false. We'll
+ // need to set the spring's final position here.
+ if (springConfig.finalPosition == UNSET) {
+ if (endedWithVelocity) {
+ // If the fling ended with negative velocity, that means it
+ // hit the min bound, so spring to that bound (and vice
+ // versa).
+ springConfig.finalPosition =
+ if (finalVelocity < 0) flingMin else flingMax
+ } else if (endedOutOfBounds) {
+ // If the fling ended out of bounds, spring it to the
+ // nearest bound.
+ springConfig.finalPosition =
+ if (finalValue < flingMin) flingMin else flingMax
+ }
+ }
+
+ // Apply the configuration and start the spring animation.
+ getSpringAnimation(animatedProperty)
+ .also { springConfig.applyToAnimation(it) }
+ .start()
+ }
+ }
+ })
+ }
+ }
+ }
+
// Add an internal listener that will dispatch animation events to the provided listeners.
internalListeners.add(InternalListener(
getAnimatedProperties(),
@@ -318,24 +503,10 @@
ArrayList(endListeners),
ArrayList(endActions)))
- for ((property, config) in flingConfigs) {
- val currentValue = property.getValue(target)
-
- // If the fling is already out of bounds, don't start it.
- if (currentValue <= config.min || currentValue >= config.max) {
- continue
- }
-
- val flingAnim = getFlingAnimation(property)
- config.applyToAnimation(flingAnim)
- flingAnim.start()
- }
-
- for ((property, config) in springConfigs) {
- val springAnim = getSpringAnimation(property)
- config.applyToAnimation(springAnim)
- springAnim.start()
- }
+ // Actually start the DynamicAnimations. This is delayed until after the InternalListener is
+ // constructed and added so that we don't miss the end listener firing for any animations
+ // that immediately end.
+ animationStartActions.forEach { it.invoke() }
clearAnimator()
}
@@ -381,7 +552,10 @@
}
anim.addEndListener { _, canceled, value, velocity ->
internalListeners.removeAll {
- it.onInternalAnimationEnd(property, canceled, value, velocity) } }
+ it.onInternalAnimationEnd(
+ property, canceled, value, velocity, anim is FlingAnimation)
+ }
+ }
return anim
}
@@ -434,7 +608,8 @@
property: FloatPropertyCompat<in T>,
canceled: Boolean,
finalValue: Float,
- finalVelocity: Float
+ finalVelocity: Float,
+ isFling: Boolean
): Boolean {
// If this property animation isn't relevant to this listener, ignore it.
@@ -461,7 +636,15 @@
val allEnded = !arePropertiesAnimating(properties)
endListeners.forEach {
- it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) }
+ it.onAnimationEnd(
+ target, property, isFling, canceled, finalValue, finalVelocity,
+ allEnded)
+
+ // Check that the end listener didn't restart this property's animation.
+ if (isPropertyAnimating(property)) {
+ return false
+ }
+ }
// If all of the animations that this listener cares about have ended, run the end
// actions unless the animation was canceled.
@@ -495,7 +678,8 @@
/** Returns whether the given property is animating. */
fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
- return springAnimations[property]?.isRunning ?: false
+ return springAnimations[property]?.isRunning ?: false ||
+ flingAnimations[property]?.isRunning ?: false
}
/** Returns whether any of the given properties are animating. */
@@ -523,15 +707,15 @@
data class SpringConfig internal constructor(
internal var stiffness: Float,
internal var dampingRatio: Float,
- internal var startVel: Float = 0f,
- internal var finalPosition: Float = -Float.MAX_VALUE
+ internal var startVelocity: Float = 0f,
+ internal var finalPosition: Float = UNSET
) {
constructor() :
this(defaultSpring.stiffness, defaultSpring.dampingRatio)
constructor(stiffness: Float, dampingRatio: Float) :
- this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f)
+ this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
/** Apply these configuration settings to the given SpringAnimation. */
internal fun applyToAnimation(anim: SpringAnimation) {
@@ -542,7 +726,7 @@
finalPosition = this@SpringConfig.finalPosition
}
- if (startVel != 0f) anim.setStartVelocity(startVel)
+ if (startVelocity != 0f) anim.setStartVelocity(startVelocity)
}
}
@@ -555,7 +739,7 @@
internal var friction: Float,
internal var min: Float,
internal var max: Float,
- internal var startVel: Float
+ internal var startVelocity: Float
) {
constructor() : this(defaultFling.friction)
@@ -564,7 +748,7 @@
this(friction, defaultFling.min, defaultFling.max)
constructor(friction: Float, min: Float, max: Float) :
- this(friction, min, max, startVel = 0f)
+ this(friction, min, max, startVelocity = 0f)
/** Apply these configuration settings to the given FlingAnimation. */
internal fun applyToAnimation(anim: FlingAnimation) {
@@ -572,7 +756,7 @@
friction = this@FlingConfig.friction
setMinValue(min)
setMaxValue(max)
- setStartVelocity(startVel)
+ setStartVelocity(startVelocity)
}
}
}
@@ -625,6 +809,10 @@
*
* @param target The animated object itself.
* @param property The property whose animation has just ended.
+ * @param wasFling Whether this property ended after a fling animation (as opposed to a
+ * spring animation). If this property was animated via [flingThenSpring], this will be true
+ * if the fling animation did not reach the min/max bounds, decelerating to a stop
+ * naturally. It will be false if it hit the bounds and was sprung back.
* @param canceled Whether the animation was explicitly canceled before it naturally ended.
* @param finalValue The final value of the animated property.
* @param finalVelocity The final velocity (in pixels per second) of the ended animation.
@@ -662,6 +850,7 @@
fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
index e86970c..965decd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -19,6 +19,7 @@
import android.os.Looper
import android.util.ArrayMap
import androidx.dynamicanimation.animation.FloatPropertyCompat
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.prepareForTest
import java.util.ArrayDeque
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -119,6 +120,7 @@
override fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
@@ -389,8 +391,6 @@
val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
animationThreadHandler.post {
- val animatedProperties = animator.getAnimatedProperties()
-
// Add an update listener that dispatches to any test update listeners added by
// tests.
animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
@@ -398,6 +398,10 @@
target: T,
values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
) {
+ values.forEach { (property, value) ->
+ allUpdates.getOrPut(property, { ArrayList() }).add(value)
+ }
+
for (listener in testUpdateListeners) {
listener.onAnimationUpdateForProperty(target, values)
}
@@ -410,6 +414,7 @@
override fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
@@ -417,7 +422,7 @@
) {
for (listener in testEndListeners) {
listener.onAnimationEnd(
- target, property, canceled, finalValue, finalVelocity,
+ target, property, wasFling, canceled, finalValue, finalVelocity,
allRelevantPropertyAnimsEnded)
}
@@ -432,31 +437,6 @@
}
})
- val updateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>().also {
- it.add(object : PhysicsAnimator.UpdateListener<T> {
- override fun onAnimationUpdateForProperty(
- target: T,
- values: ArrayMap<FloatPropertyCompat<in T>,
- PhysicsAnimator.AnimationUpdate>
- ) {
- values.forEach { (property, value) ->
- allUpdates.getOrPut(property, { ArrayList() }).add(value)
- }
- }
- })
- }
-
- /**
- * Add an internal listener at the head of the list that captures update values
- * directly from DynamicAnimation. We use this to build a list of all updates so we
- * can verify that InternalListener dispatches to the real listeners properly.
- */
- animator.internalListeners.add(0, animator.InternalListener(
- animatedProperties,
- updateListeners,
- ArrayList(),
- ArrayList()))
-
animator.startInternal()
unblockLatch.countDown()
}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index bfb0e15..c51624b 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -35,7 +35,6 @@
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
<uses-permission android:name="android.permission.CONTROL_VPN" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/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/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 09cc5ba..fe8d769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -29,6 +29,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -48,6 +49,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.DumpController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
@@ -103,7 +105,7 @@
MockitoAnnotations.initMocks(this);
Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mCollection = new NotifCollection(mStatusBarService);
+ mCollection = new NotifCollection(mStatusBarService, mock(DumpController.class));
mCollection.attach(mGroupCoalescer);
mCollection.addCollectionListener(mCollectionListener);
mCollection.setBuildListener(mBuildListener);
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 be06748..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
@@ -16,9 +16,10 @@
package com.android.systemui.statusbar.notification.collection;
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
+import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
@@ -27,6 +28,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -37,6 +39,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.DumpController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
@@ -45,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;
@@ -100,7 +103,7 @@
MockitoAnnotations.initMocks(this);
Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog);
+ mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog, mock(DumpController.class));
mListBuilder.setOnRenderListListener(mOnRenderListListener);
mListBuilder.attach(mNotifCollection);
@@ -448,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));
@@ -550,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);
@@ -582,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
@@ -629,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);
@@ -637,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);
@@ -657,7 +762,7 @@
mOnBeforeTransformGroupsListener,
promoter,
mOnBeforeSortListener,
- sectioner,
+ section,
comparator,
preRenderFilter,
mOnBeforeRenderListListener,
@@ -670,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())
@@ -684,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
@@ -709,7 +814,7 @@
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
- sectionsProvider.invalidateList();
+ section.invalidateList();
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
@@ -982,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());
@@ -999,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) {
@@ -1081,7 +1192,8 @@
}
} catch (AssertionError err) {
throw new AssertionError(
- "List under test failed verification:\n" + dumpList(mBuiltList), err);
+ "List under test failed verification:\n" + dumpTree(mBuiltList,
+ true, ""), err);
}
}
@@ -1166,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;
@@ -1202,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/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
new file mode 100644
index 0000000..0bf458c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.BubblesTestActivity;
+import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationConversationInfoTest extends SysuiTestCase {
+ private static final String TEST_PACKAGE_NAME = "test_package";
+ private static final String TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME;
+ private static final int TEST_UID = 1;
+ private static final String TEST_CHANNEL = "test_channel";
+ private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
+ private static final String CONVERSATION_ID = "convo";
+
+ private TestableLooper mTestableLooper;
+ private NotificationConversationInfo mNotificationInfo;
+ private NotificationChannel mNotificationChannel;
+ private NotificationChannel mConversationChannel;
+ private StatusBarNotification mSbn;
+ private NotificationEntry mEntry;
+ private StatusBarNotification mBubbleSbn;
+ private NotificationEntry mBubbleEntry;
+ @Mock
+ private ShortcutInfo mShortcutInfo;
+ private Drawable mImage;
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private INotificationManager mMockINotificationManager;
+ @Mock
+ private PackageManager mMockPackageManager;
+ @Mock
+ private VisualStabilityManager mVisualStabilityManager;
+ @Mock
+ private BubbleController mBubbleController;
+ @Mock
+ private LauncherApps mLauncherApps;
+ @Mock
+ private ShortcutManager mShortcutManager;
+ @Mock
+ private NotificationGuts mNotificationGuts;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestableLooper = TestableLooper.get(this);
+
+ mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+ mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+ mDependency.injectTestDependency(BubbleController.class, mBubbleController);
+ // Inflate the layout
+ final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+ mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate(
+ R.layout.notification_conversation_info,
+ null);
+ mNotificationInfo.mShowHomeScreen = true;
+ mNotificationInfo.setGutsParent(mNotificationGuts);
+ doAnswer((Answer<Object>) invocation -> {
+ mNotificationInfo.handleCloseControls(true, false);
+ return null;
+ }).when(mNotificationGuts).closeControls(anyInt(), anyInt(), eq(true), eq(false));
+ // Our view is never attached to a window so the View#post methods in NotificationInfo never
+ // get called. Setting this will skip the post and do the action immediately.
+ mNotificationInfo.mSkipPost = true;
+
+ // PackageManager must return a packageInfo and applicationInfo.
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = TEST_PACKAGE_NAME;
+ when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt()))
+ .thenReturn(packageInfo);
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = TEST_UID; // non-zero
+ when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
+ applicationInfo);
+ final PackageInfo systemPackageInfo = new PackageInfo();
+ systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
+ when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt()))
+ .thenReturn(systemPackageInfo);
+ when(mMockPackageManager.getPackageInfo(eq("android"), anyInt()))
+ .thenReturn(packageInfo);
+
+ when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
+ List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
+ when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
+ mImage = mContext.getDrawable(R.drawable.ic_star);
+ when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo),
+ anyInt())).thenReturn(mImage);
+
+ mNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+
+ Notification notification = new Notification.Builder(mContext, mNotificationChannel.getId())
+ .setShortcutId(CONVERSATION_ID)
+ .setStyle(new Notification.MessagingStyle(new Person.Builder().setName("m").build())
+ .addMessage(new Notification.MessagingStyle.Message(
+ "hello!", 1000, new Person.Builder().setName("other").build())))
+ .build();
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
+ notification, UserHandle.CURRENT, null, 0);
+ mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
+
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0,
+ new Intent(mContext, BubblesTestActivity.class), 0);
+ mBubbleSbn = new SbnBuilder(mSbn).setBubbleMetadata(
+ new Notification.BubbleMetadata.Builder()
+ .setIntent(bubbleIntent)
+ .setIcon(Icon.createWithResource(mContext, R.drawable.android)).build())
+ .build();
+ mBubbleEntry = new NotificationEntryBuilder().setSbn(mBubbleSbn).build();
+
+ mConversationChannel = new NotificationChannel(
+ TEST_CHANNEL + " : " + CONVERSATION_ID, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+ mConversationChannel.setConversationId(TEST_CHANNEL, CONVERSATION_ID);
+ when(mMockINotificationManager.getConversationNotificationChannel(anyString(), anyInt(),
+ anyString(), eq(TEST_CHANNEL), eq(false), eq(CONVERSATION_ID)))
+ .thenReturn(mConversationChannel);
+ }
+
+ @Test
+ public void testBindNotification_SetsTextShortcutName() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.name);
+ assertEquals(mShortcutInfo.getShortLabel(), textView.getText().toString());
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsShortcutIcon() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
+ assertEquals(mImage, view.getDrawable());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextApplicationName() {
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
+ assertTrue(textView.getText().toString().contains("App Name"));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextChannelName() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.parent_channel_name);
+ assertTrue(textView.getText().toString().contains(mNotificationChannel.getName()));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextGroupName() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ when(mMockINotificationManager.getNotificationChannelGroupForPackage(
+ anyString(), anyString(), anyInt())).thenReturn(group);
+ mNotificationChannel.setGroup(group.getId());
+ mConversationChannel.setGroup(group.getId());
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+ assertTrue(textView.getText().toString().contains(group.getName()));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ assertEquals(VISIBLE, textView.getVisibility());
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_GroupNameHiddenIfNoGroup() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ assertEquals(GONE, textView.getVisibility());
+ assertEquals(GONE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_noDelegate() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+ assertEquals(GONE, nameView.getVisibility());
+ final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+ assertEquals(GONE, dividerView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_delegate() throws Exception {
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0,
+ mSbn.getNotification(), UserHandle.CURRENT, null, 0);
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = 7; // non-zero
+ when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn(
+ applicationInfo);
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other");
+
+ NotificationEntry entry = new NotificationEntryBuilder().setSbn(mSbn).build();
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ entry,
+ null,
+ null,
+ null,
+ true);
+ final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+ assertEquals(VISIBLE, nameView.getVisibility());
+ assertTrue(nameView.getText().toString().contains("Proxied"));
+ final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+ assertEquals(VISIBLE, dividerView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsOnClickListenerForSettings() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ (View v, NotificationChannel c, int appUid) -> {
+ assertEquals(mConversationChannel, c);
+ latch.countDown();
+ },
+ null,
+ null,
+ true);
+
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ settingsButton.performClick();
+ // Verify that listener was triggered.
+ assertEquals(0, latch.getCount());
+ }
+
+ @Test
+ public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+ }
+
+ @Test
+ public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ (View v, NotificationChannel c, int appUid) -> {
+ assertEquals(mNotificationChannel, c);
+ latch.countDown();
+ },
+ null,
+ null,
+ false);
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+ }
+
+ @Test
+ public void testBindNotification_bubbleActionVisibleWhenCanBubble() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+ assertEquals(View.VISIBLE, bubbleView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_bubbleActionVisibleWhenCannotBubble() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+ assertEquals(View.GONE, bubbleView.getVisibility());
+ }
+
+ @Test
+ public void testAddToHome() throws Exception {
+ when(mShortcutManager.isRequestPinShortcutSupported()).thenReturn(true);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.home).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null);
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ public void testSnooze() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ (View v, int hours) -> {
+ latch.countDown();
+ },
+ true);
+
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.snooze).performClick();
+ mTestableLooper.processAllMessages();
+
+ assertEquals(0, latch.getCount());
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ public void testBubble_promotesBubble() throws Exception {
+ mNotificationChannel.setAllowBubbles(false);
+ mConversationChannel.setAllowBubbles(false);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ assertFalse(mBubbleEntry.isBubble());
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.bubble).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mBubbleController, times(1)).onUserCreatedBubbleFromNotification(mBubbleEntry);
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertTrue(captor.getValue().canBubble());
+ }
+
+ @Test
+ public void testBubble_demotesBubble() throws Exception {
+ mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ assertTrue(mBubbleEntry.isBubble());
+
+ // Demote it
+ mNotificationInfo.findViewById(R.id.bubble).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mBubbleController, times(1)).onUserDemotedBubbleFromNotification(mBubbleEntry);
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertFalse(captor.getValue().canBubble());
+ }
+
+ @Test
+ public void testFavorite_favorite() throws Exception {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ Button fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_favorite),
+ fave.getText().toString());
+
+ fave.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertTrue(captor.getValue().canBypassDnd());
+ }
+
+ @Test
+ public void testFavorite_unfavorite() throws Exception {
+ mNotificationChannel.setBypassDnd(true);
+ mConversationChannel.setBypassDnd(true);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ Button fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
+ fave.getText().toString());
+
+ fave.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertFalse(captor.getValue().canBypassDnd());
+ }
+
+ @Test
+ public void testMute_mute() throws Exception {
+ mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ Button mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_mute),
+ mute.getText().toString());
+
+ mute.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertEquals(IMPORTANCE_LOW, captor.getValue().getImportance());
+ }
+
+ @Test
+ public void testMute_unmute() throws Exception {
+ mNotificationChannel.setImportance(IMPORTANCE_LOW);
+ mNotificationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+ mConversationChannel.setImportance(IMPORTANCE_LOW);
+ mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ Button mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_unmute),
+ mute.getText().toString());
+
+ mute.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance());
+ }
+
+ @Test
+ public void testBindNotification_createsNewChannel() throws Exception {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
+ anyString(), anyInt(), any(), eq(CONVERSATION_ID));
+ }
+
+ @Test
+ public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception {
+ mNotificationChannel.setConversationId("", CONVERSATION_ID);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
+ anyString(), anyInt(), any(), eq(CONVERSATION_ID));
+ }
+
+ @Test
+ public void testAdjustImportanceTemporarilyAllowsReordering() {
+ mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ mNotificationInfo.findViewById(R.id.mute).performClick();
+
+ verify(mVisualStabilityManager).temporarilyAllowReordering();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index f48c40c..b33d26f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -132,16 +132,6 @@
}
@Test
- public void testNoAppOpsInSlowSwipe_biDirectionalSwipe() {
- NotificationMenuRow row = new NotificationMenuRow(mContext, true);
- row.createMenu(mRow, null);
-
- ViewGroup container = (ViewGroup) row.getMenuView();
- // in the new interruption model there is only the blocking item
- assertEquals(1, container.getChildCount());
- }
-
- @Test
public void testIsSnappedAndOnSameSide() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
index 709a1a8..a785d4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -19,6 +19,7 @@
import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -154,6 +155,7 @@
verify(mockEndListener).onAnimationEnd(
testView,
DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
canceled = false,
finalValue = 10f,
finalVelocity = 0f,
@@ -175,6 +177,7 @@
verify(mockEndListener).onAnimationEnd(
testView,
DynamicAnimation.TRANSLATION_Y,
+ wasFling = false,
canceled = false,
finalValue = 500f,
finalVelocity = 0f,
@@ -214,8 +217,8 @@
verifyUpdateListenerCalls(animator, mockUpdateListener)
verify(mockEndListener, times(1)).onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(),
- eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(false), anyFloat(),
+ anyFloat(), eq(true))
verify(mockEndAction, times(1)).run()
animator
@@ -329,13 +332,24 @@
// The original end listener should also have been called, with allEnded = true since it was
// provided to an animator that animated only TRANSLATION_X.
verify(mockEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, true)
+ .onAnimationEnd(
+ testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 200f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
verifyNoMoreInteractions(mockEndListener)
// The second end listener should have been called, but with allEnded = false since it was
// provided to an animator that animated both TRANSLATION_X and TRANSLATION_Y.
verify(secondEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, false)
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 200f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = false)
verifyNoMoreInteractions(secondEndListener)
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
@@ -345,7 +359,12 @@
verifyNoMoreInteractions(mockEndListener)
verify(secondEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true)
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y,
+ wasFling = false,
+ canceled = false,
+ finalValue = 4000f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
verifyNoMoreInteractions(secondEndListener)
}
@@ -364,8 +383,8 @@
assertEquals(10f, testView.translationX, 1f)
verify(mockEndListener, times(1))
.onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f),
- anyFloat(), eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+ eq(10f), anyFloat(), eq(true))
animator
.fling(
@@ -381,8 +400,39 @@
assertEquals(-5f, testView.translationX, 1f)
verify(mockEndListener, times(1))
.onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f),
- anyFloat(), eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+ eq(-5f), anyFloat(), eq(true))
+ }
+
+ @Test
+ fun testIsPropertyAnimating() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ testView.physicsAnimator
+ .spring(DynamicAnimation.TRANSLATION_X, 500f, springConfig)
+ .fling(DynamicAnimation.TRANSLATION_Y, 10f, flingConfig)
+ .spring(DynamicAnimation.TRANSLATION_Z, 1000f, springConfig)
+ .start()
+
+ // All of the properties we just started should be animating.
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+ // Block until x and y end.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(testView.physicsAnimator,
+ DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)
+
+ // Verify that x and y are no longer animating, but that Z is (it's springing to 1000f).
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Z)
+
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
}
@Test
@@ -395,6 +445,118 @@
assertEquals(200f, testView.translationX, 1f)
}
+ @Test
+ fun testFlingThenSpring() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ // Start at 500f and fling hard to the left. We should quickly reach the 250f minimum, fly
+ // past it since there's so much velocity remaining, then spring back to 250f.
+ testView.translationX = 500f
+ animator
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -5000f,
+ flingConfig.apply { min = 250f },
+ springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // Block until we pass the minimum.
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+ animator) { v -> v.translationX <= 250f }
+
+ // Double check that the view is there.
+ assertTrue(testView.translationX <= 250f)
+
+ // The update listener should have been called with a value < 500f, and then a value less
+ // than or equal to the 250f minimum.
+ verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < 500f },
+ { u -> u.value <= 250f })
+
+ // Despite the fact that the fling has ended, the end listener shouldn't have been called
+ // since we're about to begin springing the same property.
+ verifyNoMoreInteractions(mockEndListener)
+ verifyNoMoreInteractions(mockEndAction)
+
+ // Wait for the spring to finish.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_X)
+
+ // Make sure we continued past 250f since the spring should have been started with some
+ // remaining negative velocity from the fling.
+ verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < 250f })
+
+ // At this point, the animation end listener should have been called once, and only once,
+ // when the spring ended at 250f.
+ verify(mockEndListener).onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 250f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // The end action should also have been called once.
+ verify(mockEndAction, times(1)).run()
+ verifyNoMoreInteractions(mockEndAction)
+
+ assertEquals(250f, testView.translationX)
+ }
+
+ @Test
+ fun testFlingThenSpring_objectOutsideFlingBounds() {
+ // Start the view at x = -500, well outside the fling bounds of min = 0f, with strong
+ // negative velocity.
+ testView.translationX = -500f
+ animator
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -5000f,
+ flingConfig.apply { min = 0f },
+ springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // The initial -5000f velocity should result in frames to the left of -500f before the view
+ // springs back towards 0f.
+ verifyAnimationUpdateFrames(
+ animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < -500f },
+ { u -> u.value > -500f })
+
+ // We should end up at the fling min.
+ assertEquals(0f, testView.translationX, 1f)
+ }
+
+ @Test
+ fun testFlingToMinMaxThenSpring() {
+ // Start at x = 500f.
+ testView.translationX = 500f
+
+ // Fling to the left at the very sad rate of -1 pixels per second. That won't get us much of
+ // anywhere, and certainly not to the 0f min.
+ animator
+ // Good thing we have flingToMinMaxThenSpring!
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -10000f,
+ flingConfig.apply { min = 0f },
+ springConfig,
+ flingMustReachMinOrMax = true)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // Thanks, flingToMinMaxThenSpring, for adding enough velocity to get us here.
+ assertEquals(0f, testView.translationX, 1f)
+ }
+
/**
* Verifies that the calls to the mock update listener match the animation update frames
* reported by the test internal listener, in order.
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index d297f3f..e441fb5 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -16,7 +16,7 @@
java_defaults {
name: "TetheringAndroidLibraryDefaults",
- platform_apis: true,
+ sdk_version: "system_current",
srcs: [
"src/**/*.java",
":framework-tethering-shared-srcs",
@@ -29,6 +29,7 @@
"netlink-client",
"networkstack-aidl-interfaces-unstable-java",
"android.hardware.tetheroffload.control-V1.0-java",
+ "net-utils-framework-common",
],
libs: [
"framework-tethering",
@@ -80,7 +81,7 @@
// Common defaults for compiling the actual APK.
java_defaults {
name: "TetheringAppDefaults",
- platform_apis: true,
+ sdk_version: "system_current",
privileged: true,
// Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
// explicitly.
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
index d5cdd8a..4dd6830 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
@@ -22,6 +22,8 @@
import android.net.RouteInfo;
import android.net.util.InterfaceSet;
+import com.android.net.module.util.NetUtils;
+
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -85,7 +87,7 @@
private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) {
final RouteInfo ri = (lp != null)
- ? RouteInfo.selectBestRoute(lp.getAllRoutes(), dst)
+ ? NetUtils.selectBestRoute(lp.getAllRoutes(), dst)
: null;
return (ri != null) ? ri.getInterface() : null;
}
diff --git a/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/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
index 2891c6c..4c9e590 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -82,7 +82,7 @@
mCurrentTaps++;
if (mCurrentTaps == mTargetTaps) {
// Done.
- completeAfterTapTimeout(event, rawEvent, policyFlags);
+ completeGesture(event, rawEvent, policyFlags);
return;
}
// Needs more taps.
diff --git a/services/api/current.txt b/services/api/current.txt
index d802177..18e38b1 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -1 +1,31 @@
// Signature format: 2.0
+package com.android.server {
+
+ public abstract class SystemService {
+ ctor public SystemService(@NonNull android.content.Context);
+ method @NonNull public final android.content.Context getContext();
+ method public boolean isSupportedUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onBootPhase(int);
+ method public void onCleanupUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public abstract void onStart();
+ method public void onStartUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onStopUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onSwitchUser(@Nullable com.android.server.SystemService.TargetUser, @NonNull com.android.server.SystemService.TargetUser);
+ method public void onUnlockUser(@NonNull com.android.server.SystemService.TargetUser);
+ method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder);
+ method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder, boolean);
+ field public static final int PHASE_ACTIVITY_MANAGER_READY = 550; // 0x226
+ field public static final int PHASE_BOOT_COMPLETED = 1000; // 0x3e8
+ field public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520; // 0x208
+ field public static final int PHASE_LOCK_SETTINGS_READY = 480; // 0x1e0
+ field public static final int PHASE_SYSTEM_SERVICES_READY = 500; // 0x1f4
+ field public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600; // 0x258
+ field public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // 0x64
+ }
+
+ public static final class SystemService.TargetUser {
+ method @NonNull public android.os.UserHandle getUserHandle();
+ }
+
+}
+
diff --git a/services/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/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 db54214..396b977 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2855,8 +2855,10 @@
*/
@Override
public void startCheckpoint(int numTries) throws RemoteException {
- // Only the system process is permitted to start checkpoints
- if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+ // Only the root, system_server and shell processes are permitted to start checkpoints
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID
+ && callingUid != Process.SHELL_UID) {
throw new SecurityException("no permission to start filesystem checkpoint");
}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 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/backup/PeopleBackupHelper.java b/services/core/java/com/android/server/backup/PeopleBackupHelper.java
new file mode 100644
index 0000000..e58a051
--- /dev/null
+++ b/services/core/java/com/android/server/backup/PeopleBackupHelper.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import android.app.backup.BlobBackupHelper;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.people.PeopleServiceInternal;
+
+class PeopleBackupHelper extends BlobBackupHelper {
+
+ private static final String TAG = PeopleBackupHelper.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ // Current schema of the backup state blob.
+ private static final int STATE_VERSION = 1;
+
+ // Key under which conversation infos state blob is committed to backup.
+ private static final String KEY_CONVERSATIONS = "people_conversation_infos";
+
+ private final int mUserId;
+
+ PeopleBackupHelper(int userId) {
+ super(STATE_VERSION, KEY_CONVERSATIONS);
+ mUserId = userId;
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ if (!KEY_CONVERSATIONS.equals(key)) {
+ Slog.w(TAG, "Unexpected backup key " + key);
+ return new byte[0];
+ }
+ PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+ if (DEBUG) {
+ Slog.d(TAG, "Handling backup of " + key);
+ }
+ return ps.backupConversationInfos(mUserId);
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ if (!KEY_CONVERSATIONS.equals(key)) {
+ Slog.w(TAG, "Unexpected restore key " + key);
+ return;
+ }
+ PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+ if (DEBUG) {
+ Slog.d(TAG, "Handling restore of " + key);
+ }
+ ps.restoreConversationInfos(mUserId, key, payload);
+ }
+}
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 35e8f56..1f4563b 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -55,6 +55,7 @@
private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
private static final String ACCOUNT_MANAGER_HELPER = "account_manager";
private static final String SLICES_HELPER = "slices";
+ private static final String PEOPLE_HELPER = "people";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -99,6 +100,7 @@
addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
addHelper(SLICES_HELPER, new SliceBackupHelper(this));
+ addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
}
@Override
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 6fa999c..04c792a 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -26,6 +26,7 @@
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
@@ -46,19 +47,18 @@
import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.Uri;
import android.os.BestClock;
import android.os.Handler;
import android.os.SystemClock;
-import android.net.Uri;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.TelephonyManager;
-import android.util.DataUnit;
import android.util.DebugUtils;
-import android.util.Pair;
import android.util.Range;
import android.util.Slog;
@@ -74,7 +74,6 @@
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
-import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -185,7 +184,6 @@
// Track information on mobile networks as they come and go.
class MultipathTracker {
final Network network;
- final int subId;
final String subscriberId;
private long mQuota;
@@ -198,13 +196,14 @@
public MultipathTracker(Network network, NetworkCapabilities nc) {
this.network = network;
this.mNetworkCapabilities = new NetworkCapabilities(nc);
- try {
- subId = Integer.parseInt(
- ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString());
- } catch (ClassCastException | NullPointerException | NumberFormatException e) {
+ NetworkSpecifier specifier = nc.getNetworkSpecifier();
+ int subId = INVALID_SUBSCRIPTION_ID;
+ if (specifier instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+ } else {
throw new IllegalStateException(String.format(
- "Can't get subId from mobile network %s (%s): %s",
- network, nc, e.getMessage()));
+ "Can't get subId from mobile network %s (%s)",
+ network, nc));
}
TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 2179518..2c41557 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -28,7 +28,7 @@
import android.content.Intent;
import android.content.res.Resources;
import android.net.NetworkSpecifier;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.net.wifi.WifiInfo;
import android.os.UserHandle;
import android.telephony.SubscriptionManager;
@@ -223,14 +223,8 @@
// name has been added to it
NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier();
int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- if (specifier instanceof StringNetworkSpecifier) {
- try {
- subId = Integer.parseInt(
- ((StringNetworkSpecifier) specifier).specifier);
- } catch (NumberFormatException e) {
- Slog.e(TAG, "NumberFormatException on "
- + ((StringNetworkSpecifier) specifier).specifier);
- }
+ if (specifier instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
}
details = mTelephonyManager.createForSubscriptionId(subId)
diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
index 3befa6e..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;
}
}
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..d21febb 100644
--- a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
+++ b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
@@ -18,9 +18,9 @@
/** A helper class containing special indexing file constants. */
public final class IndexingFileConstants {
- // The parsing time seems acceptable for this block size based on the tests in
- // go/ic-rule-file-format.
- public static final int INDEXING_BLOCK_SIZE = 100;
+ // We empirically experimented with different block sizes and identified that 250 is in the
+ // optimal range of efficient computation.
+ public static final int INDEXING_BLOCK_SIZE = 250;
public static final String START_INDEXING_KEY = "START_KEY";
public static final String END_INDEXING_KEY = "END_KEY";
diff --git a/services/core/java/com/android/server/integrity/parser/LimitInputStream.java b/services/core/java/com/android/server/integrity/parser/LimitInputStream.java
new file mode 100644
index 0000000..a91bbb7
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/LimitInputStream.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** An {@link InputStream} that basically truncates another {@link InputStream} */
+public class LimitInputStream extends FilterInputStream {
+ private int mReadBytes;
+ private final int mLimit;
+
+ public LimitInputStream(InputStream in, int limit) {
+ super(in);
+ if (limit < 0) {
+ throw new IllegalArgumentException("limit " + limit + " cannot be negative");
+ }
+ mReadBytes = 0;
+ mLimit = limit;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return Math.min(super.available(), mLimit - mReadBytes);
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (mReadBytes == mLimit) {
+ return -1;
+ }
+ mReadBytes++;
+ return super.read();
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (len <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return -1;
+ }
+ int result = super.read(b, off, Math.min(len, available));
+ mReadBytes += result;
+ return result;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ if (n <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return 0;
+ }
+ int bytesToSkip = (int) Math.min(available, n);
+ long bytesSkipped = super.skip(bytesToSkip);
+ mReadBytes += (int) bytesSkipped;
+ return bytesSkipped;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java
new file mode 100644
index 0000000..206e6a1
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** A wrapper around {@link RandomAccessObject} to turn it into a {@link InputStream}. */
+public class RandomAccessInputStream extends InputStream {
+
+ private final RandomAccessObject mRandomAccessObject;
+
+ private int mPosition;
+
+ public RandomAccessInputStream(RandomAccessObject object) throws IOException {
+ mRandomAccessObject = object;
+ mPosition = 0;
+ }
+
+ /** Returns the position of the file pointer. */
+ public int getPosition() {
+ return mPosition;
+ }
+
+ /** See {@link RandomAccessObject#seek(int)} */
+ public void seek(int position) throws IOException {
+ mRandomAccessObject.seek(position);
+ mPosition = position;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mRandomAccessObject.length() - mPosition;
+ }
+
+ @Override
+ public void close() throws IOException {
+ mRandomAccessObject.close();
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (available() <= 0) {
+ return -1;
+ }
+ mPosition++;
+ return mRandomAccessObject.read();
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (len <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return -1;
+ }
+ int result = mRandomAccessObject.read(b, off, Math.min(len, available));
+ mPosition += result;
+ return result;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ if (n <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return 0;
+ }
+ int skipAmount = (int) Math.min(available, n);
+ mPosition += skipAmount;
+ mRandomAccessObject.seek(mPosition);
+ return skipAmount;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java
new file mode 100644
index 0000000..d9b2e38
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+
+/** An interface for random access objects like RandomAccessFile or byte arrays. */
+public abstract class RandomAccessObject {
+
+ /** See {@link RandomAccessFile#seek(long)}. */
+ public abstract void seek(int position) throws IOException;
+
+ /** See {@link RandomAccessFile#read()}. */
+ public abstract int read() throws IOException;
+
+ /** See {@link RandomAccessFile#read(byte[], int, int)}. */
+ public abstract int read(byte[] bytes, int off, int len) throws IOException;
+
+ /** See {@link RandomAccessFile#close()}. */
+ public abstract void close() throws IOException;
+
+ /** See {@link java.io.RandomAccessFile#length()}. */
+ public abstract int length();
+
+ /** Static constructor from a file. */
+ public static RandomAccessObject ofFile(File file) throws IOException {
+ return new RandomAccessFileObject(file);
+ }
+
+ /** Static constructor from a byte array. */
+ public static RandomAccessObject ofBytes(byte[] bytes) {
+ return new RandomAccessByteArrayObject(bytes);
+ }
+
+ private static class RandomAccessFileObject extends RandomAccessObject {
+ private final RandomAccessFile mRandomAccessFile;
+ // We cache the length since File.length() invokes file IO.
+ private final int mLength;
+
+ RandomAccessFileObject(File file) throws IOException {
+ long length = file.length();
+ if (length > Integer.MAX_VALUE) {
+ throw new IOException("Unsupported file size (too big) " + length);
+ }
+
+ mRandomAccessFile = new RandomAccessFile(file, /* mode= */ "r");
+ mLength = (int) length;
+ }
+
+ @Override
+ public void seek(int position) throws IOException {
+ mRandomAccessFile.seek(position);
+ }
+
+ @Override
+ public int read() throws IOException {
+ return mRandomAccessFile.read();
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ return mRandomAccessFile.read(bytes, off, len);
+ }
+
+ @Override
+ public void close() throws IOException {
+ mRandomAccessFile.close();
+ }
+
+ @Override
+ public int length() {
+ return mLength;
+ }
+ }
+
+ private static class RandomAccessByteArrayObject extends RandomAccessObject {
+
+ private final ByteBuffer mBytes;
+
+ RandomAccessByteArrayObject(byte[] bytes) {
+ mBytes = ByteBuffer.wrap(bytes);
+ }
+
+ @Override
+ public void seek(int position) throws IOException {
+ mBytes.position(position);
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (!mBytes.hasRemaining()) {
+ return -1;
+ }
+
+ return mBytes.get() & 0xFF;
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ int bytesToCopy = Math.min(len, mBytes.remaining());
+ if (bytesToCopy <= 0) {
+ return 0;
+ }
+ mBytes.get(bytes, off, len);
+ return bytesToCopy;
+ }
+
+ @Override
+ public void close() throws IOException {}
+
+ @Override
+ public int length() {
+ return mBytes.capacity();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
index 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/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/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index b291a2e..c518614 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -161,7 +161,7 @@
import android.net.NetworkState;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.net.TrafficStats;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
@@ -5281,16 +5281,12 @@
}
private int parseSubId(NetworkState state) {
- // TODO: moved to using a legitimate NetworkSpecifier instead of string parsing
int subId = INVALID_SUBSCRIPTION_ID;
if (state != null && state.networkCapabilities != null
&& state.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
NetworkSpecifier spec = state.networkCapabilities.getNetworkSpecifier();
- if (spec instanceof StringNetworkSpecifier) {
- try {
- subId = Integer.parseInt(((StringNetworkSpecifier) spec).specifier);
- } catch (NumberFormatException e) {
- }
+ if (spec instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
}
}
return subId;
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index da2ca9b..eaf120e 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -42,9 +42,10 @@
return null;
}
- record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(),
+ record.updateNotificationChannel(mConfig.getConversationNotificationChannel(
+ record.sbn.getPackageName(),
record.sbn.getUid(), record.getChannel().getId(),
- record.getNotification().getShortcutId(), false));
+ record.getNotification().getShortcutId(), true, false));
return null;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0e08033..1d49364 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -472,7 +472,8 @@
private static final String LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE = "value";
private RankingHelper mRankingHelper;
- private PreferencesHelper mPreferencesHelper;
+ @VisibleForTesting
+ PreferencesHelper mPreferencesHelper;
private final UserProfiles mUserProfiles = new UserProfiles();
private NotificationListeners mListeners;
@@ -3054,23 +3055,24 @@
public NotificationChannel getNotificationChannel(String callingPkg, int userId,
String targetPkg, String channelId) {
return getConversationNotificationChannel(
- callingPkg, userId, targetPkg, channelId, null);
+ callingPkg, userId, targetPkg, channelId, true, null);
}
@Override
public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId,
- String targetPkg, String channelId, String conversationId) {
+ String targetPkg, String channelId, boolean returnParentIfNoConversationChannel,
+ String conversationId) {
if (canNotifyAsPackage(callingPkg, targetPkg, userId)
- || isCallingUidSystem()) {
+ || isCallerIsSystemOrSystemUi()) {
int targetUid = -1;
try {
targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
} catch (NameNotFoundException e) {
/* ignore */
}
- return mPreferencesHelper.getNotificationChannel(
+ return mPreferencesHelper.getConversationNotificationChannel(
targetPkg, targetUid, channelId, conversationId,
- false /* includeDeleted */);
+ returnParentIfNoConversationChannel, false /* includeDeleted */);
}
throw new SecurityException("Pkg " + callingPkg
+ " cannot read channels for " + targetPkg + " in " + userId);
@@ -5255,9 +5257,15 @@
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
channelId = (new Notification.TvExtender(notification)).getChannelId();
}
- final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
- notificationUid, channelId, notification.getShortcutId(),
- false /* includeDeleted */);
+ // TODO: flag this behavior
+ String shortcutId = notification.getShortcutId();
+ if (shortcutId == null
+ && notification.getNotificationStyle() == Notification.MessagingStyle.class) {
+ shortcutId = id + tag + NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+ }
+ final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
+ pkg, notificationUid, channelId, shortcutId,
+ true /* parent ok */, false /* includeDeleted */);
if (channel == null) {
final String noChannelStr = "No Channel found for "
+ "pkg=" + pkg
@@ -7893,6 +7901,14 @@
return isUidSystemOrPhone(Binder.getCallingUid());
}
+ private boolean isCallerIsSystemOrSystemUi() {
+ if (isCallerSystemOrPhone()) {
+ return true;
+ }
+ return getContext().checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+ == PERMISSION_GRANTED;
+ }
+
private void checkCallerIsSystemOrShell() {
int callingUid = Binder.getCallingUid();
if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 92fcb7f..185d75c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -858,12 +858,13 @@
public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted) {
Objects.requireNonNull(pkg);
- return getNotificationChannel(pkg, uid, channelId, null, includeDeleted);
+ return getConversationNotificationChannel(pkg, uid, channelId, null, true, includeDeleted);
}
@Override
- public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
- String conversationId, boolean includeDeleted) {
+ public NotificationChannel getConversationNotificationChannel(String pkg, int uid,
+ String channelId, String conversationId, boolean returnParentIfNoConversationChannel,
+ boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
synchronized (mPackagePreferences) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
@@ -873,16 +874,19 @@
if (channelId == null) {
channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
}
- if (conversationId == null) {
+ NotificationChannel channel = null;
+ if (conversationId != null) {
+ // look for an automatically created conversation specific channel
+ channel = findConversationChannel(r, channelId, conversationId, includeDeleted);
+ }
+ if (channel == null && returnParentIfNoConversationChannel) {
+ // look for it just based on its id
final NotificationChannel nc = r.channels.get(channelId);
if (nc != null && (includeDeleted || !nc.isDeleted())) {
return nc;
}
- } else {
- // look for an automatically created conversation specific channel
- return findConversationChannel(r, channelId, conversationId, includeDeleted);
}
- return null;
+ return channel;
}
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 4b044c1..7e98be7 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -45,8 +45,9 @@
boolean fromUser);
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted);
- NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
- String conversationId, boolean includeDeleted);
+ NotificationChannel getConversationNotificationChannel(String pkg, int uid, String channelId,
+ String conversationId, boolean returnParentIfNoConversationChannel,
+ boolean includeDeleted);
void deleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannels(String pkg, int uid);
diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java
index 31d30362..c5b868f 100644
--- a/services/core/java/com/android/server/people/PeopleServiceInternal.java
+++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java
@@ -16,9 +16,25 @@
package com.android.server.people;
+import android.annotation.NonNull;
import android.service.appprediction.IPredictionService;
/**
* @hide Only for use within the system server.
*/
-public abstract class PeopleServiceInternal extends IPredictionService.Stub {}
+public abstract class PeopleServiceInternal extends IPredictionService.Stub {
+
+ /**
+ * The number conversation infos will be dynamic, based on the currently installed apps on the
+ * device. All of which should be combined into a single blob to be backed up.
+ */
+ public abstract byte[] backupConversationInfos(@NonNull int userId);
+
+ /**
+ * Multiple conversation infos may exist in the restore payload, child classes are required to
+ * manage the restoration based on how individual conversation infos were originally combined
+ * during backup.
+ */
+ public abstract void restoreConversationInfos(@NonNull int userId, @NonNull String key,
+ @NonNull byte[] payload);
+}
diff --git a/services/core/java/com/android/server/pm/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 13efdcd..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;
}
}
@@ -22290,6 +22301,13 @@
mPermissionManager.onNewUserCreated(userId);
}
+ boolean readPermissionStateForUser(@UserIdInt int userId) {
+ synchronized (mPackages) {
+ mSettings.readPermissionStateForUserSyncLPr(userId);
+ return mSettings.areDefaultRuntimePermissionsGrantedLPr(userId);
+ }
+ }
+
@Override
public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
mContext.enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3e665c3..4f18cb4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3125,6 +3125,10 @@
return true;
}
+ void readPermissionStateForUserSyncLPr(@UserIdInt int userId) {
+ mRuntimePermissionsPersistence.readStateForUserSyncLPr(userId);
+ }
+
void applyDefaultPreferredAppsLPw(int userId) {
// First pull data from any pre-installed apps.
final PackageManagerInternal pmInternal =
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e5d5b57..66a2b01 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -492,6 +492,10 @@
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mUms.cleanupPartialUsers();
+
+ if (mUms.mPm.isDeviceUpgrading()) {
+ mUms.cleanupPreCreatedUsers();
+ }
}
}
@@ -617,6 +621,33 @@
}
}
+ /**
+ * Removes any pre-created users from the system. Should be invoked after OTAs, to ensure
+ * pre-created users are not stale. New pre-created pool can be re-created after the update.
+ */
+ void cleanupPreCreatedUsers() {
+ final ArrayList<UserInfo> preCreatedUsers;
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ preCreatedUsers = new ArrayList<>(userSize);
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i).info;
+ if (ui.preCreated) {
+ preCreatedUsers.add(ui);
+ addRemovingUserIdLocked(ui.id);
+ ui.flags |= UserInfo.FLAG_DISABLED;
+ ui.partial = true;
+ }
+ }
+ }
+ final int preCreatedSize = preCreatedUsers.size();
+ for (int i = 0; i < preCreatedSize; i++) {
+ UserInfo ui = preCreatedUsers.get(i);
+ Slog.i(LOG_TAG, "Removing pre-created user " + ui.id);
+ removeUserState(ui.id);
+ }
+ }
+
@Override
public String getUserAccount(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("get user account");
@@ -3078,7 +3109,6 @@
@NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId,
boolean preCreate, @Nullable String[] disallowedPackages,
@NonNull TimingsTraceAndSlog t) {
-
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
if (userTypeDetails == null) {
Slog.e(LOG_TAG, "Cannot create user of invalid user type: " + userType);
@@ -3254,9 +3284,9 @@
mBaseUserRestrictions.append(userId, restrictions);
}
- t.traceBegin("PM.onNewUserCreated");
+ t.traceBegin("PM.onNewUserCreated-" + userId);
mPm.onNewUserCreated(userId);
-
+ t.traceEnd();
if (preCreate) {
// Must start user (which will be stopped right away, through
// UserController.finishUserUnlockedCompleted) so services can properly
@@ -3323,11 +3353,16 @@
preCreatedUser.preCreated = false;
preCreatedUser.creationTime = getCreationTime();
- dispatchUserAddedIntent(preCreatedUser);
synchronized (mPackagesLock) {
writeUserLP(preCreatedUserData);
writeUserListLP();
}
+ updateUserIds();
+ if (!mPm.readPermissionStateForUser(preCreatedUser.id)) {
+ // Could not read the existing permissions, re-grant them.
+ mPm.onNewUserCreated(preCreatedUser.id);
+ }
+ dispatchUserAddedIntent(preCreatedUser);
return preCreatedUser;
}
@@ -3360,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
@@ -3643,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() {
@@ -4027,14 +4068,16 @@
synchronized (mUsersLock) {
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).info.partial) {
+ UserInfo userInfo = mUsers.valueAt(i).info;
+ if (!userInfo.partial && !userInfo.preCreated) {
num++;
}
}
final int[] newUsers = new int[num];
int n = 0;
for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).info.partial) {
+ UserInfo userInfo = mUsers.valueAt(i).info;
+ if (!userInfo.partial && !userInfo.preCreated) {
newUsers[n++] = mUsers.keyAt(i);
}
}
@@ -4095,7 +4138,10 @@
* recycled.
*/
void reconcileUsers(String volumeUuid) {
- mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(true /* excludeDying */));
+ mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(
+ /* excludePartial= */ true,
+ /* excludeDying= */ true,
+ /* excludePreCreated= */ false));
}
/**
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 29183bb..df24c013 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -222,6 +222,7 @@
// If the dex file is the primary apk (or a split) and not isUsedByOtherApps
// do not record it. This case does not bring any new usable information
// and can be safely skipped.
+ dexPathIndex++;
continue;
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d468cd9..0411e29 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -55,6 +55,8 @@
import android.app.ActivityManager;
import android.app.ApplicationPackageManager;
import android.app.IActivityManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -107,6 +109,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.compat.IPlatformCompat;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.RoSystemProperties;
@@ -224,6 +227,8 @@
private final Handler mHandler;
private final Context mContext;
private final MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
/** Internal storage for permissions and related settings */
@GuardedBy("mLock")
@@ -1824,6 +1829,14 @@
return true;
}
+ /**
+ * This change makes it so that apps are told to show rationale for asking for background
+ * location access every time they request.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L;
+
@Override
public boolean shouldShowRequestPermissionRationale(String permName,
String packageName, int userId) {
@@ -1862,6 +1875,16 @@
return false;
}
+ try {
+ if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+ && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID,
+ packageName, userId)) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to check if compatibility change is enabled.", e);
+ }
+
return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
}
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/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..7a8ddd4 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, "");
}
@@ -245,12 +245,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 +260,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 +274,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 +366,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 9290381..75fbf01 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;
@@ -263,6 +264,7 @@
registerProcessMemoryHighWaterMark();
registerProcessMemorySnapshot();
registerSystemIonHeapSize();
+ registerIonHeapSize();
registerProcessSystemIonHeapSize();
registerTemperature();
registerCoolingDevice();
@@ -622,12 +624,35 @@
return StatsManager.PULL_SUCCESS;
}
+ private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+ private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
private void registerKernelWakelock() {
- // No op.
+ int tagId = StatsLog.KERNEL_WAKELOCK;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ /* PullAtomMetadata */ null,
+ (atomTag, data) -> pullKernelWakelock(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
}
- private void pullKernelWakelock() {
- // No op.
+ private int pullKernelWakelock(int atomTag, List<StatsEvent> pulledData) {
+ final KernelWakelockStats wakelockStats =
+ mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+ for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+ String name = ent.getKey();
+ KernelWakelockStats.Entry kws = ent.getValue();
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeString(name)
+ .writeInt(kws.mCount)
+ .writeInt(kws.mVersion)
+ .writeLong(kws.mTotalTime)
+ .build();
+ pulledData.add(e);
+ }
+ return StatsManager.PULL_SUCCESS;
}
private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
@@ -931,6 +956,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.
}
@@ -1131,11 +1176,30 @@
}
private void registerBuildInformation() {
- // No op.
+ int tagId = StatsLog.BUILD_INFORMATION;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullBuildInformation(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
}
- private void pullBuildInformation() {
- // No op.
+ private int pullBuildInformation(int atomTag, List<StatsEvent> pulledData) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeString(Build.FINGERPRINT)
+ .writeString(Build.BRAND)
+ .writeString(Build.PRODUCT)
+ .writeString(Build.DEVICE)
+ .writeString(Build.VERSION.RELEASE)
+ .writeString(Build.ID)
+ .writeString(Build.VERSION.INCREMENTAL)
+ .writeString(Build.TYPE)
+ .writeString(Build.TAGS)
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
}
private void registerRoleHolder() {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c9a702f..19bc560 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2137,8 +2137,10 @@
(intent == null || (intent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
}
+ @Override
boolean isFocusable() {
- return mRootWindowContainer.isFocusable(this, isAlwaysFocusable());
+ return super.isFocusable()
+ && (getWindowConfiguration().canReceiveKeys() || isAlwaysFocusable());
}
boolean isResizeable() {
@@ -2489,7 +2491,7 @@
// We are finishing the top focused activity and its stack has nothing to be focused so
// the next focusable stack should be focused.
if (mayAdjustTop
- && (stack.topRunningActivity() == null || !stack.isFocusable())) {
+ && (stack.topRunningActivity() == null || !stack.isTopActivityFocusable())) {
if (shouldAdjustGlobalFocus) {
// Move the entire hierarchy to top with updating global top resumed activity
// and focused application if needed.
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 9fd3ea4..f5fba8e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -161,9 +161,9 @@
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.ITaskOrganizer;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.ITaskOrganizer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -1248,13 +1248,20 @@
}
}
+ @Override
boolean isFocusable() {
+ return super.isFocusable() && !(inSplitScreenPrimaryWindowingMode()
+ && mRootWindowContainer.mIsDockMinimized);
+ }
+
+ boolean isTopActivityFocusable() {
final ActivityRecord r = topRunningActivity();
- return mRootWindowContainer.isFocusable(this, r != null && r.isFocusable());
+ return r != null ? r.isFocusable()
+ : (isFocusable() && getWindowConfiguration().canReceiveKeys());
}
boolean isFocusableAndVisible() {
- return isFocusable() && shouldBeVisible(null /* starting */);
+ return isTopActivityFocusable() && shouldBeVisible(null /* starting */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 61ba15c..6587226 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1569,7 +1569,7 @@
if (mDoResume) {
final ActivityRecord topTaskActivity =
mStartActivity.getTask().topRunningActivityLocked();
- if (!mTargetStack.isFocusable()
+ if (!mTargetStack.isTopActivityFocusable()
|| (topTaskActivity != null && topTaskActivity.isTaskOverlay()
&& mStartActivity != topTaskActivity)) {
// If the activity is not focusable, we can't resume it, but still would like to
@@ -1588,7 +1588,7 @@
// will not update the focused stack. If starting the new activity now allows the
// task stack to be focusable, then ensure that we now update the focused stack
// accordingly.
- if (mTargetStack.isFocusable()
+ if (mTargetStack.isTopActivityFocusable()
&& !mRootWindowContainer.isTopDisplayFocusedStack(mTargetStack)) {
mTargetStack.moveToFront("startActivityInner");
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3092a08..e308f6b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -248,7 +248,6 @@
import com.android.internal.policy.KeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -345,6 +344,10 @@
/** This activity is being relaunched due to a free-resize operation. */
public static final int RELAUNCH_REASON_FREE_RESIZE = 2;
+ /** Flag indicating that an applied transaction may have effected lifecycle */
+ private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1;
+ private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1;
+
Context mContext;
/**
@@ -3300,7 +3303,7 @@
}
}
- private void sanitizeAndApplyConfigChange(ConfigurationContainer container,
+ private int sanitizeAndApplyChange(ConfigurationContainer container,
WindowContainerTransaction.Change change) {
if (!(container instanceof Task || container instanceof ActivityStack)) {
throw new RuntimeException("Invalid token in task transaction");
@@ -3312,12 +3315,22 @@
configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
- Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
- c.setTo(change.getConfiguration(), configMask, windowMask);
- container.onRequestedOverrideConfigurationChanged(c);
- // TODO(b/145675353): remove the following once we could apply new bounds to the
- // pinned stack together with its children.
- resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+ int effects = 0;
+ if (configMask != 0) {
+ Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
+ c.setTo(change.getConfiguration(), configMask, windowMask);
+ container.onRequestedOverrideConfigurationChanged(c);
+ // TODO(b/145675353): remove the following once we could apply new bounds to the
+ // pinned stack together with its children.
+ resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+ effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ }
+ if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) {
+ if (container.setFocusable(change.getFocusable())) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
+ return effects;
}
private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
@@ -3334,9 +3347,9 @@
}
}
- private void applyWindowContainerChange(ConfigurationContainer cc,
+ private int applyWindowContainerChange(ConfigurationContainer cc,
WindowContainerTransaction.Change c) {
- sanitizeAndApplyConfigChange(cc, c);
+ int effects = sanitizeAndApplyChange(cc, c);
Rect enterPipBounds = c.getEnterPipBounds();
if (enterPipBounds != null) {
@@ -3344,25 +3357,58 @@
mStackSupervisor.updatePictureInPictureMode(tr,
enterPipBounds, true);
}
+ return effects;
}
@Override
public void applyContainerTransaction(WindowContainerTransaction t) {
mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()");
+ if (t == null) {
+ return;
+ }
long ident = Binder.clearCallingIdentity();
try {
- if (t == null) {
- return;
- }
synchronized (mGlobalLock) {
- Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
- t.getChanges().entrySet().iterator();
- while (entries.hasNext()) {
- final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
- entries.next();
- final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder(
- entry.getKey()).getContainer();
- applyWindowContainerChange(cc, entry.getValue());
+ int effects = 0;
+ deferWindowLayout();
+ try {
+ ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
+ Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
+ t.getChanges().entrySet().iterator();
+ while (entries.hasNext()) {
+ final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
+ entries.next();
+ final ConfigurationContainer cc =
+ ConfigurationContainer.RemoteToken.fromBinder(
+ entry.getKey()).getContainer();
+ int containerEffect = applyWindowContainerChange(cc, entry.getValue());
+ effects |= containerEffect;
+ // Lifecycle changes will trigger ensureConfig for everything.
+ if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
+ && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+ if (cc instanceof WindowContainer) {
+ haveConfigChanges.add((WindowContainer) cc);
+ }
+ }
+ }
+ if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
+ // Already calls ensureActivityConfig
+ mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+ final PooledConsumer f = PooledLambda.obtainConsumer(
+ ActivityRecord::ensureActivityConfiguration,
+ PooledLambda.__(ActivityRecord.class), 0,
+ false /* preserveWindow */);
+ try {
+ for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
+ haveConfigChanges.valueAt(i).forAllActivities(f);
+ }
+ } finally {
+ f.recycle();
+ }
+ }
+ } finally {
+ continueWindowLayout();
}
}
} finally {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index d0310f1..1e60ce8 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -624,6 +624,21 @@
return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
}
+ /**
+ * Returns {@code true} if this container is focusable. Generally, if a parent is not focusable,
+ * this will not be focusable either.
+ */
+ boolean isFocusable() {
+ // TODO(split): Move this to WindowContainer once Split-screen is based on a WindowContainer
+ // like DisplayArea vs. TaskTiles.
+ ConfigurationContainer parent = getParent();
+ return parent == null || parent.isFocusable();
+ }
+
+ boolean setFocusable(boolean focusable) {
+ return false;
+ }
+
boolean hasChild() {
return getChildCount() > 0;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ba9d757..908c4f1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5915,7 +5915,7 @@
final ActivityRecord resumedActivity = stack.getResumedActivity();
if (resumedActivity != null
&& (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
- || !stack.isFocusable())) {
+ || !stack.isTopActivityFocusable())) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack
+ " mResumedActivity=" + resumedActivity);
someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
@@ -6238,7 +6238,7 @@
for (int i = getStackCount() - 1; i >= 0; --i) {
final ActivityStack stack = getStackAt(i);
// Only consider focusable stacks other than the current focused one.
- if (stack == focusedStack || !stack.isFocusable()) {
+ if (stack == focusedStack || !stack.isTopActivityFocusable()) {
continue;
}
topRunning = stack.topRunningActivity();
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index c09834f..9d985d7 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -75,7 +75,8 @@
// activities are actually behind other fullscreen activities, but still required
// to be visible (such as performing Recents animation).
final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind
- && mContiner.isFocusable() && mContiner.isInStackLocked(starting) == null;
+ && mContiner.isTopActivityFocusable()
+ && mContiner.isInStackLocked(starting) == null;
final PooledConsumer f = PooledLambda.obtainConsumer(
EnsureActivitiesVisibleHelper::setActivityVisibilityState, this,
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c3e815d..2f726e9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1860,14 +1860,6 @@
return null;
}
- boolean isFocusable(ConfigurationContainer container, boolean alwaysFocusable) {
- if (container.inSplitScreenPrimaryWindowingMode() && mIsDockMinimized) {
- return false;
- }
-
- return container.getWindowConfiguration().canReceiveKeys() || alwaysFocusable;
- }
-
boolean isTopDisplayFocusedStack(ActivityStack stack) {
return stack != null && stack == getTopDisplayFocusedStack();
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index d36a5d4..38a7000 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -53,7 +53,7 @@
private static final String SNAPSHOTS_DIRNAME = "snapshots";
private static final String REDUCED_POSTFIX = "_reduced";
private static final float REDUCED_SCALE = .5f;
- private static final float LOW_RAM_REDUCED_SCALE = .6f;
+ private static final float LOW_RAM_REDUCED_SCALE = .8f;
static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
private static final long DELAY_MS = 100;
private static final int QUALITY = 95;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index c38868a..3b2d519 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -247,6 +247,8 @@
private MagnificationSpec mLastMagnificationSpec;
+ private boolean mIsFocusable = true;
+
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mPendingTransaction = wms.mTransactionFactory.get();
@@ -851,6 +853,21 @@
return false;
}
+ @Override
+ boolean isFocusable() {
+ return super.isFocusable() && mIsFocusable;
+ }
+
+ /** Set whether this container or its children can be focusable */
+ @Override
+ boolean setFocusable(boolean focusable) {
+ if (mIsFocusable == focusable) {
+ return false;
+ }
+ mIsFocusable = focusable;
+ return true;
+ }
+
/**
* @return Whether this child is on top of the window hierarchy.
*/
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..c55acab 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7456,8 +7456,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 +7477,7 @@
return false;
}
Objects.requireNonNull(who, "ComponentName is null");
- enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+ enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
@@ -7492,8 +7491,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 +7512,7 @@
return false;
}
Objects.requireNonNull(who, "ComponentName is null");
- enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+ enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
@@ -8724,7 +8722,6 @@
if (!mHasFeature) {
return false;
}
- enforceManageUsers();
return mInjector.binderWithCleanCallingIdentity(() -> {
for (UserInfo ui : mUserManager.getUsers()) {
@@ -9061,23 +9058,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");
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/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index ef3da60..9569c6e 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -137,5 +137,14 @@
Slog.e(TAG, "Failed to calling callback" + e);
}
}
+
+ @Override
+ public byte[] backupConversationInfos(int userId) {
+ return new byte[0];
+ }
+
+ @Override
+ public void restoreConversationInfos(int userId, String key, byte[] payload) {
+ }
}
}
diff --git a/services/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/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/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/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index d670361..2cb7103 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3832,11 +3832,7 @@
when(getServices().userManager.getUsers())
.thenReturn(Arrays.asList(managedProfileUserInfo));
- // Any caller without the MANAGE_USERS permission should get a security exception.
- assertExpectException(SecurityException.class, null, () ->
- dpm.isOrganizationOwnedDeviceWithManagedProfile());
- // But when the right permission is granted, this should succeed.
- mContext.permissions.add(android.Manifest.permission.MANAGE_USERS);
+ // Any caller should be able to call this method.
assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile());
configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile());
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/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 3d40637..355cada 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -112,7 +112,7 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.os.Binder;
import android.os.Handler;
import android.os.INetworkManagementService;
@@ -1913,7 +1913,8 @@
if (!roaming) {
nc.addCapability(NET_CAPABILITY_NOT_ROAMING);
}
- nc.setNetworkSpecifier(new StringNetworkSpecifier(String.valueOf(subId)));
+ nc.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(subId).build());
return nc;
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 82bbdcb..cb9d816 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -28,10 +28,12 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.parsing.AndroidPackage;
+import android.content.pm.parsing.ComponentParseUtils;
import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo;
import android.content.pm.parsing.PackageImpl;
import android.content.pm.parsing.ParsingPackage;
+import android.net.Uri;
import android.os.Build;
import android.os.Process;
import android.util.ArrayMap;
@@ -49,6 +51,7 @@
import org.mockito.MockitoAnnotations;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -116,6 +119,15 @@
.addActivity(activity);
}
+ private static ParsingPackage pkgWithProvider(String packageName, String authority) {
+ ComponentParseUtils.ParsedProvider provider = new ComponentParseUtils.ParsedProvider();
+ provider.setPackageName(packageName);
+ provider.setExported(true);
+ provider.setAuthority(authority);
+ return pkg(packageName)
+ .addProvider(provider);
+ }
+
@Before
public void setup() throws Exception {
mExisting = new ArrayMap<>();
@@ -149,6 +161,55 @@
}
@Test
+ public void testQueriesProvider_FilterMatches() {
+ final AppsFilter appsFilter =
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
+
+ PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
+ PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package",
+ new Intent().setData(Uri.parse("content://com.some.authority"))),
+ DUMMY_CALLING_UID);
+
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+ }
+
+ @Test
+ public void testQueriesDifferentProvider_Filters() {
+ final AppsFilter appsFilter =
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
+
+ PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
+ PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package",
+ new Intent().setData(Uri.parse("content://com.some.other.authority"))),
+ DUMMY_CALLING_UID);
+
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+ }
+
+ @Test
+ public void testQueriesProviderWithSemiColon_FilterMatches() {
+ final AppsFilter appsFilter =
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
+
+ PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"),
+ DUMMY_TARGET_UID);
+ PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package",
+ new Intent().setData(Uri.parse("content://com.some.authority"))),
+ DUMMY_CALLING_UID);
+
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+ }
+
+ @Test
public void testQueriesAction_NoMatchingAction_Filters() {
final AppsFilter appsFilter =
new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index f08044c..f5e5e2a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -160,6 +160,31 @@
}
@Test
+ public void testNotifyPrimaryAndSecondary() {
+ List<String> dexFiles = mFooUser0.getBaseAndSplitDexPaths();
+ List<String> secondaries = mFooUser0.getSecondaryDexPaths();
+ int baseAndSplitCount = dexFiles.size();
+ dexFiles.addAll(secondaries);
+
+ notifyDexLoad(mFooUser0, dexFiles, mUser0);
+
+ PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+ assertIsUsedByOtherApps(mFooUser0, pui, false);
+ assertEquals(secondaries.size(), pui.getDexUseInfoMap().size());
+
+ String[] allExpectedContexts = DexoptUtils.processContextForDexLoad(
+ Arrays.asList(mFooUser0.mClassLoader),
+ Arrays.asList(String.join(File.pathSeparator, dexFiles)));
+ String[] secondaryExpectedContexts = Arrays.copyOfRange(allExpectedContexts,
+ baseAndSplitCount, dexFiles.size());
+
+ assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0,
+ secondaryExpectedContexts);
+
+ assertHasDclInfo(mFooUser0, mFooUser0, secondaries);
+ }
+
+ @Test
public void testNotifySecondaryForeign() {
// Foo loads bar secondary files.
List<String> barSecondaries = mBarUser0.getSecondaryDexPaths();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index bd7d9ec..f5af3ec 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -64,7 +64,8 @@
NotificationChannel updatedChannel =
new NotificationChannel("a", "", IMPORTANCE_HIGH);
- when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(null), eq(false)))
+ when(mConfig.getConversationNotificationChannel(
+ any(), anyInt(), eq("a"), eq(null), eq(true), eq(false)))
.thenReturn(updatedChannel);
assertNull(extractor.process(r));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 62f5230..2ac4642 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -471,16 +471,17 @@
mTestableLooper.processAllMessages();
}
- private void setUpPrefsForBubbles(boolean globalEnabled, boolean pkgEnabled,
- boolean channelEnabled) {
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
- when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
- when(mPreferencesHelper.getNotificationChannel(
- anyString(), anyInt(), anyString(), eq(null), anyBoolean())).thenReturn(
- mTestNotificationChannel);
- when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
- mTestNotificationChannel.getImportance());
+ private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled,
+ boolean pkgEnabled, boolean channelEnabled) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_BUBBLES, globalEnabled ? 1 : 0);
+ mService.mPreferencesHelper.updateBubblesEnabled();
+ assertEquals(globalEnabled, mService.mPreferencesHelper.bubblesEnabled());
+ try {
+ mBinderService.setBubblesAllowed(pkg, uid, pkgEnabled);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
mTestNotificationChannel.setAllowBubbles(channelEnabled);
}
@@ -1763,8 +1764,8 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mPreferencesHelper, times(1)).getNotificationChannel(
- anyString(), anyInt(), eq("foo"), eq(null), anyBoolean());
+ verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
+ anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
}
@Test
@@ -1778,9 +1779,9 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
0, generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mPreferencesHelper, times(1)).getNotificationChannel(
+ verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
- anyBoolean());
+ anyBoolean(), anyBoolean());
}
@Test
@@ -4759,7 +4760,7 @@
@Test
public void testFlagBubble() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr =
generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble");
@@ -4778,7 +4779,7 @@
@Test
public void testFlagBubble_noFlag_appNotAllowed() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, false /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubble_noFlag_appNotAllowed");
@@ -4797,7 +4798,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_whenAppForeground() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif with bubble metadata but not our other misc requirements
Notification.Builder nb = new Notification.Builder(mContext,
@@ -4825,7 +4826,7 @@
@Test
public void testFlagBubbleNotifs_flag_messaging() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_flag_messaging");
@@ -4842,7 +4843,7 @@
@Test
public void testFlagBubbleNotifs_flag_phonecall() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4878,7 +4879,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noForegroundService() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4911,7 +4912,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noPerson() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4942,7 +4943,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noCategory() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4977,7 +4978,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_messaging_appNotAllowed() throws RemoteException {
// Bubbles are NOT allowed!
- setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_noFlag_messaging_appNotAllowed");
@@ -4995,7 +4996,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_notBubble() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Messaging notif WITHOUT bubble metadata
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
@@ -5019,7 +5020,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed() throws RemoteException {
// Bubbles are allowed except on this channel
- setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed");
@@ -5037,7 +5038,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_notAllowed() throws RemoteException {
// Bubbles are not allowed!
- setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, false /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -5072,7 +5073,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed() throws RemoteException {
// Bubbles are allowed, but not on channel.
- setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -5280,7 +5281,7 @@
@Test
public void testNotificationBubbleChanged_false() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif with bubble metadata
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
@@ -5311,7 +5312,7 @@
@Test
public void testNotificationBubbleChanged_true() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif that is not a bubble
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
@@ -5348,7 +5349,7 @@
@Test
public void testNotificationBubbleChanged_true_notAllowed() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif that is not a bubble
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
@@ -5547,7 +5548,7 @@
@Test
public void testNotificationBubbles_disabled_lowRamDevice() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// And we are low ram
when(mActivityManager.isLowRamDevice()).thenReturn(true);
@@ -5631,7 +5632,7 @@
public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testNotificationBubbles_flagAutoExpandForeground_fails_notForeground");
@@ -5661,7 +5662,7 @@
public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground()
throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground");
@@ -5691,7 +5692,7 @@
public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
true /* summaryAutoCancel */);
@@ -5714,7 +5715,7 @@
public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
true /* summaryAutoCancel */);
@@ -5836,7 +5837,7 @@
mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend");
NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel(
- PKG, 0, PKG, original.getId(), "friend");
+ PKG, 0, PKG, original.getId(), false, "friend");
assertEquals(original.getName(), friendChannel.getName());
assertEquals(original.getId(), friendChannel.getParentChannelId());
@@ -5874,9 +5875,9 @@
conversationId);
NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel(
- PKG, 0, PKG, messagesParent.getId(), conversationId);
+ PKG, 0, PKG, messagesParent.getId(), false, conversationId);
NotificationChannel callsChild = mBinderService.getConversationNotificationChannel(
- PKG, 0, PKG, callsParent.getId(), conversationId);
+ PKG, 0, PKG, callsParent.getId(), false, conversationId);
assertEquals(messagesParent.getId(), messagesChild.getParentChannelId());
assertEquals(conversationId, messagesChild.getConversationId());
@@ -5887,8 +5888,8 @@
mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId);
assertNull(mBinderService.getConversationNotificationChannel(
- PKG, 0, PKG, messagesParent.getId(), conversationId));
+ PKG, 0, PKG, messagesParent.getId(), false, conversationId));
assertNull(mBinderService.getConversationNotificationChannel(
- PKG, 0, PKG, callsParent.getId(), conversationId));
+ PKG, 0, PKG, callsParent.getId(), false, conversationId));
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 7173e0c..7ac45f0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -354,6 +354,7 @@
channel2.setGroup(ncg.getId());
channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
channel2.setLightColor(Color.BLUE);
+ channel2.setConversationId("id1", "conversation");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);
@@ -2822,8 +2823,20 @@
friend.setConversationId(parent.getId(), conversationId);
mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
- compareChannelsParentChild(parent, mHelper.getNotificationChannel(
- PKG_O, UID_O, parent.getId(), conversationId, false), conversationId);
+ compareChannelsParentChild(parent, mHelper.getConversationNotificationChannel(
+ PKG_O, UID_O, parent.getId(), conversationId, false, false), conversationId);
+ }
+
+ @Test
+ public void testGetNotificationChannel_conversationProvidedByNotCustomizedYet() {
+ String conversationId = "friend";
+
+ NotificationChannel parent =
+ new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+ compareChannels(parent, mHelper.getConversationNotificationChannel(
+ PKG_O, UID_O, parent.getId(), conversationId, true, false));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 079c49f..d22502d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -27,6 +27,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -153,6 +154,19 @@
}
@Test
+ public void testContainerChanges() {
+ removeGlobalMinSizeRestriction();
+ final ActivityStack stack = new StackBuilder(mRootWindowContainer)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ final Task task = stack.getTopMostTask();
+ WindowContainerTransaction t = new WindowContainerTransaction();
+ assertTrue(task.isFocusable());
+ t.setFocusable(stack.mRemoteToken, false);
+ mService.applyContainerTransaction(t);
+ assertFalse(task.isFocusable());
+ }
+
+ @Test
public void testDisplayWindowListener() {
final ArrayList<Integer> added = new ArrayList<>();
final ArrayList<Integer> changed = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 04d79ca..ea8d082 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -253,12 +253,12 @@
// Under split screen primary we should be focusable when not minimized
mRootWindowContainer.setDockedStackMinimized(false);
- assertTrue(stack.isFocusable());
+ assertTrue(stack.isTopActivityFocusable());
assertTrue(activity.isFocusable());
// Under split screen primary we should not be focusable when minimized
mRootWindowContainer.setDockedStackMinimized(true);
- assertFalse(stack.isFocusable());
+ assertFalse(stack.isTopActivityFocusable());
assertFalse(activity.isFocusable());
final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack(
@@ -267,19 +267,19 @@
.setStack(pinnedStack).build();
// We should not be focusable when in pinned mode
- assertFalse(pinnedStack.isFocusable());
+ assertFalse(pinnedStack.isTopActivityFocusable());
assertFalse(pinnedActivity.isFocusable());
// Add flag forcing focusability.
pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE;
// We should not be focusable when in pinned mode
- assertTrue(pinnedStack.isFocusable());
+ assertTrue(pinnedStack.isTopActivityFocusable());
assertTrue(pinnedActivity.isFocusable());
// Without the overridding activity, stack should not be focusable.
pinnedStack.removeChild(pinnedActivity.getTask(), "testFocusability");
- assertFalse(pinnedStack.isFocusable());
+ assertFalse(pinnedStack.isTopActivityFocusable());
}
/**
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 8397aa4..b8cd378 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2069,6 +2069,13 @@
}
@Override
+ public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime,
+ boolean shouldObfuscateInstantApps) {
+ return UsageStatsService.this.queryEvents(
+ userId, beginTime, endTime, shouldObfuscateInstantApps);
+ }
+
+ @Override
public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime);
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index bde2cfd..7099c09 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -756,7 +756,9 @@
return;
}
- model.setStopped();
+ if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ model.setStopped();
+ }
try {
callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
@@ -900,7 +902,9 @@
return;
}
- modelData.setStopped();
+ if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ modelData.setStopped();
+ }
try {
modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
diff --git a/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/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/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 5e2e554..0cfb8c5 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -138,7 +138,7 @@
* To check the SDK version for {@link PreciseDataConnectionState#getDataConnectionState}.
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private static final long GET_DATA_CONNECTION_STATE_CODE_CHANGE = 147600208L;
/**
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 2c62d06..2c8014e 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -16,8 +16,6 @@
package android.telephony;
-import com.android.telephony.Rlog;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,6 +34,8 @@
import android.telephony.NetworkRegistrationInfo.NRState;
import android.text.TextUtils;
+import com.android.telephony.Rlog;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -353,6 +353,7 @@
private String mOperatorAlphaLongRaw;
private String mOperatorAlphaShortRaw;
+ private boolean mIsDataRoamingFromRegistration;
private boolean mIsIwlanPreferred;
/**
@@ -438,6 +439,7 @@
mNrFrequencyRange = s.mNrFrequencyRange;
mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw;
mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw;
+ mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration;
mIsIwlanPreferred = s.mIsIwlanPreferred;
}
@@ -472,6 +474,7 @@
mNrFrequencyRange = in.readInt();
mOperatorAlphaLongRaw = in.readString();
mOperatorAlphaShortRaw = in.readString();
+ mIsDataRoamingFromRegistration = in.readBoolean();
mIsIwlanPreferred = in.readBoolean();
}
@@ -499,6 +502,7 @@
out.writeInt(mNrFrequencyRange);
out.writeString(mOperatorAlphaLongRaw);
out.writeString(mOperatorAlphaShortRaw);
+ out.writeBoolean(mIsDataRoamingFromRegistration);
out.writeBoolean(mIsIwlanPreferred);
}
@@ -584,8 +588,8 @@
*/
@DuplexMode
public int getDuplexMode() {
- // only support LTE duplex mode
- if (!isLte(getRilDataRadioTechnology())) {
+ // support LTE/NR duplex mode
+ if (!isPsOnlyTech(getRilDataRadioTechnology())) {
return DUPLEX_MODE_UNKNOWN;
}
@@ -649,7 +653,9 @@
}
/**
- * Get current data network roaming type
+ * Get whether the current data network is roaming.
+ * This value may be overwritten by resource overlay or carrier configuration.
+ * @see #getDataRoamingFromRegistration() to get the value from the network registration.
* @return roaming type
* @hide
*/
@@ -659,18 +665,25 @@
}
/**
- * Get whether data network registration state is roaming
+ * Set whether the data network registration state is roaming.
+ * This should only be set to the roaming value received
+ * once the data registration phase has completed.
+ * @hide
+ */
+ public void setDataRoamingFromRegistration(boolean dataRoaming) {
+ mIsDataRoamingFromRegistration = dataRoaming;
+ }
+
+ /**
+ * Get whether data network registration state is roaming.
+ * This value is set directly from the modem and will not be overwritten
+ * by resource overlay or carrier configuration.
* @return true if registration indicates roaming, false otherwise
* @hide
*/
+ @SystemApi
public boolean getDataRoamingFromRegistration() {
- final NetworkRegistrationInfo regState = getNetworkRegistrationInfo(
- NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
- if (regState != null) {
- return regState.getRegistrationState()
- == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
- }
- return false;
+ return mIsDataRoamingFromRegistration;
}
/**
@@ -873,6 +886,7 @@
mNrFrequencyRange,
mOperatorAlphaLongRaw,
mOperatorAlphaShortRaw,
+ mIsDataRoamingFromRegistration,
mIsIwlanPreferred);
}
}
@@ -903,6 +917,7 @@
&& mNetworkRegistrationInfos.size() == s.mNetworkRegistrationInfos.size()
&& mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos)
&& mNrFrequencyRange == s.mNrFrequencyRange
+ && mIsDataRoamingFromRegistration == s.mIsDataRoamingFromRegistration
&& mIsIwlanPreferred == s.mIsIwlanPreferred;
}
}
@@ -1062,6 +1077,8 @@
.append(", mNrFrequencyRange=").append(mNrFrequencyRange)
.append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
.append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
+ .append(", mIsDataRoamingFromRegistration=")
+ .append(mIsDataRoamingFromRegistration)
.append(", mIsIwlanPreferred=").append(mIsIwlanPreferred)
.append("}").toString();
}
@@ -1102,6 +1119,7 @@
}
mOperatorAlphaLongRaw = null;
mOperatorAlphaShortRaw = null;
+ mIsDataRoamingFromRegistration = false;
mIsIwlanPreferred = false;
}
@@ -1624,7 +1642,8 @@
* @return Current data network type
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ @SystemApi
+ @TestApi
public @NetworkType int getDataNetworkType() {
final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
@@ -1717,9 +1736,10 @@
}
/** @hide */
- public static boolean isLte(int radioTechnology) {
- return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE ||
- radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA;
+ public static boolean isPsOnlyTech(int radioTechnology) {
+ return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_NR;
}
/** @hide */
diff --git a/telephony/java/android/telephony/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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7ee68ea..13aad7e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2276,6 +2276,16 @@
public static final int PHONE_TYPE_CDMA = PhoneConstants.PHONE_TYPE_CDMA;
/** Phone is via SIP. */
public static final int PHONE_TYPE_SIP = PhoneConstants.PHONE_TYPE_SIP;
+ /** Phone is via IMS. */
+ public static final int PHONE_TYPE_IMS = PhoneConstants.PHONE_TYPE_IMS;
+
+ /**
+ * Phone is via Third Party.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PHONE_TYPE_THIRD_PARTY = PhoneConstants.PHONE_TYPE_THIRD_PARTY;
/**
* Returns the current phone type.
@@ -5449,7 +5459,7 @@
* To check the SDK version for {@link TelephonyManager#getDataState}.
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private static final long GET_DATA_STATE_CODE_CHANGE = 147600208L;
/**
@@ -5535,7 +5545,7 @@
* To check the SDK version for {@link TelephonyManager#listen}.
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
private static final long LISTEN_CODE_CHANGE = 147600208L;
/**
@@ -7649,6 +7659,18 @@
RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA;
/**
+ * The default preferred network mode constant.
+ *
+ * <p> This constant is used in case of nothing is set in
+ * TelephonyProperties#default_network().
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int DEFAULT_PREFERRED_NETWORK_MODE =
+ RILConstants.DEFAULT_PREFERRED_NETWORK_MODE;
+
+ /**
* Get the preferred network type.
* Used for device configuration by some CDMA operators.
*
@@ -10685,6 +10707,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param enabled control enable or disable carrier data.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10711,6 +10734,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param enabled control enable or disable radio.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10737,6 +10761,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param report control start/stop reporting network status.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index ab22d46..9b739d3 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -25,8 +25,6 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.WorkerThread;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.RemoteException;
import android.telephony.CarrierConfigManager;
@@ -382,10 +380,6 @@
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerProvisioningChangedCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull Callback callback) throws ImsException {
- if (!isImsAvailableOnDevice()) {
- throw new ImsException("IMS not available on device.",
- ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
- }
callback.setExecutor(executor);
try {
getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder());
@@ -618,26 +612,6 @@
}
- private static boolean isImsAvailableOnDevice() {
- IPackageManager pm = IPackageManager.Stub.asInterface(
- TelephonyFrameworkInitializer
- .getTelephonyServiceManager()
- .getPackageManagerServiceRegisterer()
- .get());
- if (pm == null) {
- // For some reason package manger is not available.. This will fail internally anyways,
- // so do not throw error and allow.
- return true;
- }
- try {
- return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS, 0);
- } catch (RemoteException e) {
- // For some reason package manger is not available.. This will fail internally anyways,
- // so do not throw error and allow.
- }
- return true;
- }
-
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(
TelephonyFrameworkInitializer
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/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/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/net/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
new file mode 100644
index 0000000..47afed4
--- /dev/null
+++ b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+
+import android.telephony.SubscriptionManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link android.net.TelephonyNetworkSpecifier}.
+ */
+@SmallTest
+public class TelephonyNetworkSpecifierTest {
+ private static final int TEST_SUBID = 5;
+
+ /**
+ * Validate that IllegalArgumentException will be thrown if build TelephonyNetworkSpecifier
+ * without calling {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+ */
+ @Test
+ public void testBuilderBuildWithDefault() {
+ try {
+ new TelephonyNetworkSpecifier.Builder().build();
+ } catch (IllegalArgumentException iae) {
+ // expected, test pass
+ }
+ }
+
+ /**
+ * Validate that no exception will be thrown even if pass invalid subscription id to
+ * {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+ */
+ @Test
+ public void testBuilderBuildWithInvalidSubId() {
+ TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .build();
+ assertEquals(specifier.getSubscriptionId(), SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ /**
+ * Validate the correctness of TelephonyNetworkSpecifier when provide valid subId.
+ */
+ @Test
+ public void testBuilderBuildWithValidSubId() {
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ assertEquals(TEST_SUBID, specifier.getSubscriptionId());
+ }
+
+ /**
+ * Validate that parcel marshalling/unmarshalling works.
+ */
+ @Test
+ public void testParcel() {
+ TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ assertParcelSane(specifier, 1 /* fieldCount */);
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index b783467..de1028c 100644
--- a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -51,6 +51,7 @@
import android.net.NetworkPolicyManager;
import android.net.NetworkTemplate;
import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.os.Handler;
import android.provider.Settings;
import android.telephony.TelephonyManager;
@@ -229,7 +230,7 @@
verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any());
// Simulate callback after capability changes
- final NetworkCapabilities capabilities = new NetworkCapabilities()
+ NetworkCapabilities capabilities = new NetworkCapabilities()
.addCapability(NET_CAPABILITY_INTERNET)
.addTransportType(TRANSPORT_CELLULAR)
.setNetworkSpecifier(new StringNetworkSpecifier("234"));
@@ -239,6 +240,19 @@
networkCallback.getValue().onCapabilitiesChanged(
TEST_NETWORK,
capabilities);
+
+ // make sure it also works with the new introduced TelephonyNetworkSpecifier
+ capabilities = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(234).build());
+ if (!roaming) {
+ capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING);
+ }
+ networkCallback.getValue().onCapabilitiesChanged(
+ TEST_NETWORK,
+ capabilities);
}
@Test
diff --git a/tests/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/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/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/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index d4fd903..a9621fc 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() { }
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..9540103 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}.
*/
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;