Merge "Add perf test for Typeface serialization."
diff --git a/Android.bp b/Android.bp
index 9b8e018..8164d6a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -22,9 +22,9 @@
],
errorprone: {
javacflags: [
- "-Xep:AndroidFrameworkBinderIdentity:ERROR",
+ // "-Xep:AndroidFrameworkBinderIdentity:ERROR",
"-Xep:AndroidFrameworkCompatChange:ERROR",
- "-Xep:AndroidFrameworkUid:ERROR",
+ // "-Xep:AndroidFrameworkUid:ERROR",
// NOTE: only enable to generate local patchfiles
// "-XepPatchChecks:refaster:frameworks/base/errorprone/refaster/EfficientXml.java.refaster",
// "-XepPatchLocation:/tmp/refaster/",
@@ -44,6 +44,7 @@
"-Xep:AndroidFrameworkEfficientCollections:OFF",
"-Xep:AndroidFrameworkEfficientParcelable:OFF",
"-Xep:AndroidFrameworkEfficientStrings:OFF",
+ "-Xep:AndroidFrameworkEfficientXml:OFF",
],
},
}
@@ -527,6 +528,7 @@
"android.hardware.vibrator-V1.3-java",
"android.system.keystore2-java",
"android.system.suspend.control.internal-java",
+ "cameraprotosnano",
"devicepolicyprotosnano",
"com.android.sysprop.apex",
@@ -626,6 +628,7 @@
"av-types-aidl-java",
"mediatranscoding_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
+ "modules-utils-os",
],
}
@@ -1267,7 +1270,6 @@
filegroup {
name: "framework-telephony-common-shared-srcs",
srcs: [
- "core/java/android/os/BasicShellCommandHandler.java",
"core/java/android/os/RegistrantList.java",
"core/java/android/os/Registrant.java",
"core/java/android/util/IndentingPrintWriter.java",
@@ -1350,7 +1352,6 @@
name: "framework-wifi-service-shared-srcs",
srcs: [
"core/java/android/net/InterfaceConfiguration.java",
- "core/java/android/os/BasicShellCommandHandler.java",
"core/java/android/util/BackupUtils.java",
"core/java/android/util/Rational.java",
"core/java/com/android/internal/util/FastXmlSerializer.java",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 49a42d7..380839e 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -70,6 +70,7 @@
"android.hardware.cas-V1.2-java",
"android.hardware.health-V1.0-java-constants",
"android.hardware.radio-V1.5-java",
+ "android.hardware.radio-V1.6-java",
"android.hardware.thermal-V1.0-java-constants",
"android.hardware.thermal-V2.0-java",
"android.hardware.tv.input-V1.0-java-constants",
@@ -252,21 +253,7 @@
"framework-wifi.stubs",
"private-stub-annotations-jar",
],
- defaults: [
- "android_defaults_stubs_current",
- "android_stubs_dists_default",
- ],
- dist: {
- dir: "apistubs/android/system",
- },
- dists: [
- {
- // Legacy dist path
- targets: ["sdk", "win_sdk"],
- tag: ".jar",
- dest: "android_system.jar",
- },
- ],
+ defaults: ["android_defaults_stubs_current"],
}
java_library_static {
@@ -285,7 +272,21 @@
"framework-wifi.stubs.system",
"private-stub-annotations-jar",
],
- defaults: ["android_defaults_stubs_current"],
+ defaults: [
+ "android_defaults_stubs_current",
+ "android_stubs_dists_default",
+ ],
+ dist: {
+ dir: "apistubs/android/system",
+ },
+ dists: [
+ {
+ // Legacy dist path
+ targets: ["sdk", "win_sdk"],
+ tag: ".jar",
+ dest: "android_system.jar",
+ },
+ ],
}
java_library_static {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java
index beb9ad3..9ca363e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java
@@ -16,10 +16,8 @@
package android.app.appsearch;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
-
import android.app.appsearch.AppSearchSchema.PropertyConfig;
/**
@@ -29,9 +27,8 @@
*
* @hide
*/
-
public class AppSearchEmail extends GenericDocument {
- /** The name of the schema type for {@link AppSearchEmail} documents.*/
+ /** The name of the schema type for {@link AppSearchEmail} documents. */
public static final String SCHEMA_TYPE = "builtin:Email";
private static final String KEY_FROM = "from";
@@ -41,54 +38,55 @@
private static final String KEY_SUBJECT = "subject";
private static final String KEY_BODY = "body";
- public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE)
- .addProperty(new PropertyConfig.Builder(KEY_FROM)
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .build()
-
- ).addProperty(new PropertyConfig.Builder(KEY_TO)
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .build()
-
- ).addProperty(new PropertyConfig.Builder(KEY_CC)
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .build()
-
- ).addProperty(new PropertyConfig.Builder(KEY_BCC)
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .build()
-
- ).addProperty(new PropertyConfig.Builder(KEY_SUBJECT)
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .build()
-
- ).addProperty(new PropertyConfig.Builder(KEY_BODY)
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .build()
-
- ).build();
+ public static final AppSearchSchema SCHEMA =
+ new AppSearchSchema.Builder(SCHEMA_TYPE)
+ .addProperty(
+ new PropertyConfig.Builder(KEY_FROM)
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new PropertyConfig.Builder(KEY_TO)
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new PropertyConfig.Builder(KEY_CC)
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new PropertyConfig.Builder(KEY_BCC)
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new PropertyConfig.Builder(KEY_SUBJECT)
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new PropertyConfig.Builder(KEY_BODY)
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
/**
- * Creates a new {@link AppSearchEmail} from the contents of an existing
- * {@link GenericDocument}.
+ * Creates a new {@link AppSearchEmail} from the contents of an existing {@link
+ * GenericDocument}.
*
* @param document The {@link GenericDocument} containing the email content.
*/
@@ -109,8 +107,8 @@
/**
* Gets the destination addresses of {@link AppSearchEmail}.
*
- * @return The destination addresses of {@link AppSearchEmail} or {@code null} if it's not
- * been set yet.
+ * @return The destination addresses of {@link AppSearchEmail} or {@code null} if it's not been
+ * set yet.
*/
@Nullable
public String[] getTo() {
@@ -157,9 +155,7 @@
return getPropertyString(KEY_BODY);
}
- /**
- * The builder class for {@link AppSearchEmail}.
- */
+ /** The builder class for {@link AppSearchEmail}. */
public static class Builder extends GenericDocument.Builder<AppSearchEmail.Builder> {
/**
@@ -171,54 +167,42 @@
super(uri, SCHEMA_TYPE);
}
- /**
- * Sets the from address of {@link AppSearchEmail}
- */
+ /** Sets the from address of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setFrom(@NonNull String from) {
setPropertyString(KEY_FROM, from);
return this;
}
- /**
- * Sets the destination address of {@link AppSearchEmail}
- */
+ /** Sets the destination address of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setTo(@NonNull String... to) {
setPropertyString(KEY_TO, to);
return this;
}
- /**
- * Sets the CC list of {@link AppSearchEmail}
- */
+ /** Sets the CC list of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setCc(@NonNull String... cc) {
setPropertyString(KEY_CC, cc);
return this;
}
- /**
- * Sets the BCC list of {@link AppSearchEmail}
- */
+ /** Sets the BCC list of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setBcc(@NonNull String... bcc) {
setPropertyString(KEY_BCC, bcc);
return this;
}
- /**
- * Sets the subject of {@link AppSearchEmail}
- */
+ /** Sets the subject of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setSubject(@NonNull String subject) {
setPropertyString(KEY_SUBJECT, subject);
return this;
}
- /**
- * Sets the body of {@link AppSearchEmail}
- */
+ /** Sets the body of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setBody(@NonNull String body) {
setPropertyString(KEY_BODY, body);
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
index 3933726..50583ad 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
@@ -16,15 +16,14 @@
package android.app.appsearch;
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-
+import android.annotation.SuppressLint;
import android.app.appsearch.exceptions.IllegalSchemaException;
+import android.os.Bundle;
import android.util.ArraySet;
+
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -40,6 +39,8 @@
* <p>For example, an e-mail message or a music recording could be a schema type.
*
* <p>The schema consists of type information, properties, and config (like tokenization type).
+ *
+ * @see AppSearchSession#setSchema
* @hide
*/
public final class AppSearchSchema {
@@ -49,7 +50,6 @@
private final Bundle mBundle;
/** @hide */
-
public AppSearchSchema(@NonNull Bundle bundle) {
Preconditions.checkNotNull(bundle);
mBundle = bundle;
@@ -57,9 +57,9 @@
/**
* Returns the {@link Bundle} populated by this builder.
+ *
* @hide
*/
-
@NonNull
public Bundle getBundle() {
return mBundle;
@@ -72,7 +72,7 @@
/** Returns the name of this schema type, e.g. Email. */
@NonNull
- public String getSchemaTypeName() {
+ public String getSchemaType() {
return mBundle.getString(SCHEMA_TYPE_FIELD, "");
}
@@ -144,8 +144,8 @@
/**
* Configuration for a single property (field) of a document type.
*
- * <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be
- * a property.
+ * <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be a
+ * property.
*/
public static final class PropertyConfig {
private static final String NAME_FIELD = "name";
@@ -157,18 +157,20 @@
/**
* Physical data-types of the contents of the property.
+ *
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.PropertyConfigProto.DataType.Code.
- @IntDef(value = {
- DATA_TYPE_STRING,
- DATA_TYPE_INT64,
- DATA_TYPE_DOUBLE,
- DATA_TYPE_BOOLEAN,
- DATA_TYPE_BYTES,
- DATA_TYPE_DOCUMENT,
- })
+ @IntDef(
+ value = {
+ DATA_TYPE_STRING,
+ DATA_TYPE_INT64,
+ DATA_TYPE_DOUBLE,
+ DATA_TYPE_BOOLEAN,
+ DATA_TYPE_BYTES,
+ DATA_TYPE_DOCUMENT,
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface DataType {}
@@ -181,23 +183,25 @@
public static final int DATA_TYPE_BYTES = 5;
/**
- * Indicates that the property itself is an Document, making it part a hierarchical
- * Document schema. Any property using this DataType MUST have a valid
- * {@code schemaType}.
+ * Indicates that the property is itself a {@link GenericDocument}, making it part of a
+ * hierarchical schema. Any property using this DataType MUST have a valid {@link
+ * PropertyConfig#getSchemaType}.
*/
public static final int DATA_TYPE_DOCUMENT = 6;
/**
* The cardinality of the property (whether it is required, optional or repeated).
+ *
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code.
- @IntDef(value = {
- CARDINALITY_REPEATED,
- CARDINALITY_OPTIONAL,
- CARDINALITY_REQUIRED,
- })
+ @IntDef(
+ value = {
+ CARDINALITY_REPEATED,
+ CARDINALITY_OPTIONAL,
+ CARDINALITY_REQUIRED,
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface Cardinality {}
@@ -212,23 +216,25 @@
/**
* Encapsulates the configurations on how AppSearch should query/index these terms.
+ *
* @hide
*/
- @IntDef(value = {
- INDEXING_TYPE_NONE,
- INDEXING_TYPE_EXACT_TERMS,
- INDEXING_TYPE_PREFIXES,
- })
+ @IntDef(
+ value = {
+ INDEXING_TYPE_NONE,
+ INDEXING_TYPE_EXACT_TERMS,
+ INDEXING_TYPE_PREFIXES,
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface IndexingType {}
/**
* Content in this property will not be tokenized or indexed.
*
- * <p>Useful if the data type is not made up of terms (e.g.
- * {@link PropertyConfig#DATA_TYPE_DOCUMENT} or {@link PropertyConfig#DATA_TYPE_BYTES}
- * type). All the properties inside the nested property won't be indexed regardless of the
- * value of {@code indexingType} for the nested properties.
+ * <p>Useful if the data type is not made up of terms (e.g. {@link
+ * PropertyConfig#DATA_TYPE_DOCUMENT} or {@link PropertyConfig#DATA_TYPE_BYTES} type). None
+ * of the properties inside the nested property will be indexed regardless of the value of
+ * {@code indexingType} for the nested properties.
*/
public static final int INDEXING_TYPE_NONE = 0;
@@ -250,20 +256,22 @@
/**
* Configures how tokens should be extracted from this property.
+ *
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.IndexingConfig.TokenizerType.Code.
- @IntDef(value = {
- TOKENIZER_TYPE_NONE,
- TOKENIZER_TYPE_PLAIN,
- })
+ @IntDef(
+ value = {
+ TOKENIZER_TYPE_NONE,
+ TOKENIZER_TYPE_PLAIN,
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface TokenizerType {}
/**
- * It is only valid for tokenizer_type to be 'NONE' if the data type is
- * {@link PropertyConfig#DATA_TYPE_DOCUMENT}.
+ * It is only valid for tokenizer_type to be 'NONE' if the data type is {@link
+ * PropertyConfig#DATA_TYPE_DOCUMENT}.
*/
public static final int TOKENIZER_TYPE_NONE = 0;
@@ -295,8 +303,8 @@
/**
* Returns the logical schema-type of the contents of this property.
*
- * <p>Only set when {@link #getDataType} is set to {@link #DATA_TYPE_DOCUMENT}.
- * Otherwise, it is {@code null}.
+ * <p>Only set when {@link #getDataType} is set to {@link #DATA_TYPE_DOCUMENT}. Otherwise,
+ * it is {@code null}.
*/
@Nullable
public String getSchemaType() {
@@ -325,9 +333,10 @@
*
* <p>The following properties must be set, or {@link PropertyConfig} construction will
* fail:
+ *
* <ul>
- * <li>dataType
- * <li>cardinality
+ * <li>dataType
+ * <li>cardinality
* </ul>
*
* <p>In addition, if {@code schemaType} is {@link #DATA_TYPE_DOCUMENT}, {@code schemaType}
@@ -359,8 +368,8 @@
/**
* The logical schema-type of the contents of this property.
*
- * <p>Only required when {@link #setDataType} is set to
- * {@link #DATA_TYPE_DOCUMENT}. Otherwise, it is ignored.
+ * <p>Only required when {@link #setDataType} is set to {@link #DATA_TYPE_DOCUMENT}.
+ * Otherwise, it is ignored.
*/
@NonNull
public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/android/app/appsearch/GenericDocument.java
index 48d3ac0..f41fa8d 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/GenericDocument.java
@@ -16,15 +16,14 @@
package android.app.appsearch;
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-import android.util.Log;
-
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-
+import android.annotation.SuppressLint;
import android.app.appsearch.exceptions.AppSearchException;
+import android.os.Bundle;
+import android.util.Log;
+
import com.android.internal.util.Preconditions;
import java.lang.reflect.Array;
@@ -37,17 +36,21 @@
* Represents a document unit.
*
* <p>Documents are constructed via {@link GenericDocument.Builder}.
+ *
+ * @see AppSearchSession#putDocuments
+ * @see AppSearchSession#getByUri
+ * @see AppSearchSession#query
* @hide
*/
public class GenericDocument {
private static final String TAG = "GenericDocument";
- /** The default empty namespace.*/
+ /** The default empty namespace. */
public static final String DEFAULT_NAMESPACE = "";
/**
- * The maximum number of elements in a repeatable field. Will reject the request if exceed
- * this limit.
+ * 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;
@@ -78,47 +81,43 @@
/**
* The maximum number of indexed properties a document can have.
*
- * <p>Indexed properties are properties where the
- * {@link android.app.appsearch.annotation.AppSearchDocument.Property#indexingType} constant is
- * anything other than {@link
- * android.app.appsearch.AppSearchSchema.PropertyConfig.IndexingType#INDEXING_TYPE_NONE}.
+ * <p>Indexed properties are properties where the {@link
+ * AppSearchSchema.PropertyConfig#getIndexingType()} constant is anything other than {@link
+ * AppSearchSchema.PropertyConfig.IndexingType#INDEXING_TYPE_NONE}.
*/
public static int getMaxIndexedProperties() {
return MAX_INDEXED_PROPERTIES;
}
- /** Contains {@link GenericDocument} basic information (uri, schemaType etc).*/
- @NonNull
- final Bundle mBundle;
+ /** Contains {@link GenericDocument} basic information (uri, schemaType etc). */
+ @NonNull final Bundle mBundle;
- /** Contains all properties in {@link GenericDocument} to support getting properties via keys.*/
- @NonNull
- private final Bundle mProperties;
+ /**
+ * Contains all properties in {@link GenericDocument} to support getting properties via keys.
+ */
+ @NonNull private final Bundle mProperties;
- @NonNull
- private final String mUri;
- @NonNull
- private final String mSchemaType;
+ @NonNull private final String mUri;
+ @NonNull private final String mSchemaType;
private final long mCreationTimestampMillis;
- @Nullable
- private Integer mHashCode;
+ @Nullable private Integer mHashCode;
/**
* Rebuilds a {@link GenericDocument} by the a bundle.
- * @param bundle Contains {@link GenericDocument} basic information (uri, schemaType etc) and
- * a properties bundle contains all properties in {@link GenericDocument} to
- * support getting properties via keys.
+ *
+ * @param bundle Contains {@link GenericDocument} basic information (uri, schemaType etc) and a
+ * properties bundle contains all properties in {@link GenericDocument} to support getting
+ * properties via keys.
* @hide
*/
-
public GenericDocument(@NonNull Bundle bundle) {
Preconditions.checkNotNull(bundle);
mBundle = bundle;
mProperties = Preconditions.checkNotNull(bundle.getParcelable(PROPERTIES_FIELD));
mUri = Preconditions.checkNotNull(mBundle.getString(URI_FIELD));
mSchemaType = Preconditions.checkNotNull(mBundle.getString(SCHEMA_TYPE_FIELD));
- mCreationTimestampMillis = mBundle.getLong(CREATION_TIMESTAMP_MILLIS_FIELD,
- System.currentTimeMillis());
+ mCreationTimestampMillis =
+ mBundle.getLong(CREATION_TIMESTAMP_MILLIS_FIELD, System.currentTimeMillis());
}
/**
@@ -132,9 +131,9 @@
/**
* Returns the {@link Bundle} populated by this builder.
+ *
* @hide
*/
-
@NonNull
public Bundle getBundle() {
return mBundle;
@@ -158,7 +157,11 @@
return mSchemaType;
}
- /** Returns the creation timestamp of the {@link GenericDocument}, in milliseconds. */
+ /**
+ * Returns the creation timestamp of the {@link GenericDocument}, in milliseconds.
+ *
+ * <p>The value is in the {@link System#currentTimeMillis} time base.
+ */
public long getCreationTimestampMillis() {
return mCreationTimestampMillis;
}
@@ -166,8 +169,12 @@
/**
* Returns the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
*
+ * <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of
+ * {@code creationTimestampMillis + ttlMillis}, measured in the {@link System#currentTimeMillis}
+ * time base, the document will be auto-deleted.
+ *
* <p>The default value is 0, which means the document is permanent and won't be auto-deleted
- * until the app is uninstalled.
+ * until the app is uninstalled.
*/
public long getTtlMillis() {
return mBundle.getLong(TTL_MILLIS_FIELD, DEFAULT_TTL_MILLIS);
@@ -179,7 +186,10 @@
* <p>The score is a query-independent measure of the document's quality, relative to other
* {@link GenericDocument}s of the same type.
*
- * <p>The default value is 0.
+ * <p>Results may be sorted by score using {@link SearchSpec.Builder#setRankingStrategy}.
+ * Documents with higher scores are considered better than documents with lower scores.
+ *
+ * <p>Any nonnegative integer can be used a score.
*/
public int getScore() {
return mBundle.getInt(SCORE_FIELD, DEFAULT_SCORE);
@@ -195,8 +205,8 @@
* Retrieves 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.
+ * @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.
*/
@Nullable
public String getPropertyString(@NonNull String key) {
@@ -214,7 +224,7 @@
*
* @param key The key to look for.
* @return The first {@code long} associated with the given key or default value {@code 0} if
- * there is no such key or the value is of a different type.
+ * there is no such key or the value is of a different type.
*/
public long getPropertyLong(@NonNull String key) {
Preconditions.checkNotNull(key);
@@ -231,7 +241,7 @@
*
* @param key The key to look for.
* @return The first {@code double} associated with the given key or default value {@code 0.0}
- * if there is no such key or the value is of a different type.
+ * if there is no such key or the value is of a different type.
*/
public double getPropertyDouble(@NonNull String key) {
Preconditions.checkNotNull(key);
@@ -247,8 +257,8 @@
* Retrieves a {@code boolean} value by key.
*
* @param key The key to look for.
- * @return The first {@code boolean} associated with the given key or default value
- * {@code false} if there is no such key or the value is of a different type.
+ * @return The first {@code boolean} associated with the given key or default value {@code
+ * false} if there is no such key or the value is of a different type.
*/
public boolean getPropertyBoolean(@NonNull String key) {
Preconditions.checkNotNull(key);
@@ -264,8 +274,8 @@
* Retrieves a {@code byte[]} value by key.
*
* @param key The key to look for.
- * @return The first {@code byte[]} associated with the given key or {@code null} if there
- * is no such key or the value is of a different type.
+ * @return The first {@code byte[]} associated with the given key or {@code null} if there is no
+ * such key or the value is of a different type.
*/
@Nullable
public byte[] getPropertyBytes(@NonNull String key) {
@@ -283,7 +293,7 @@
*
* @param key The key to look for.
* @return The first {@link GenericDocument} associated with the given key or {@code null} if
- * there is no such key or the value is of a different type.
+ * there is no such key or the value is of a different type.
*/
@Nullable
public GenericDocument getPropertyDocument(@NonNull String key) {
@@ -300,10 +310,18 @@
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().");
+ Log.w(
+ TAG,
+ "The value for \""
+ + key
+ + "\" contains "
+ + propertyLength
+ + " elements. Only the first one will be returned from "
+ + "getProperty"
+ + propertyType
+ + "(). Try getProperty"
+ + propertyType
+ + "Array().");
}
}
@@ -311,8 +329,8 @@
* Retrieves 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.
+ * @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.
*/
@Nullable
public String[] getPropertyStringArray(@NonNull String key) {
@@ -324,8 +342,8 @@
* Retrieves a repeated {@link String} 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.
+ * @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.
*/
@Nullable
public long[] getPropertyLongArray(@NonNull String key) {
@@ -337,8 +355,8 @@
* Retrieves 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.
+ * @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.
*/
@Nullable
public double[] getPropertyDoubleArray(@NonNull String key) {
@@ -350,8 +368,8 @@
* Retrieves 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.
+ * @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.
*/
@Nullable
public boolean[] getPropertyBooleanArray(@NonNull String key) {
@@ -363,8 +381,8 @@
* Retrieves a {@code byte[][]} property by key.
*
* @param key The key to look for.
- * @return The {@code byte[][]} associated with the given key, or {@code null} if no value
- * is set or the value is of a different type.
+ * @return The {@code byte[][]} associated with the given key, or {@code null} if no value is
+ * set or the value is of a different type.
*/
@SuppressLint("ArrayReturn")
@Nullable
@@ -397,7 +415,7 @@
*
* @param key The key to look for.
* @return The {@link GenericDocument}[] associated with the given key, or {@code null} if no
- * value is set or the value is of a different type.
+ * value is set or the value is of a different type.
*/
@SuppressLint("ArrayReturn")
@Nullable
@@ -419,8 +437,8 @@
}
/**
- * Gets a repeated property of the given key, and casts it to the given class type, which
- * must be an array class type.
+ * 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) {
@@ -449,8 +467,9 @@
}
/**
- * Deeply checks two bundles are equally or not.
- * <p> Two bundles will be considered equally if they contain same content.
+ * Deeply checks whether two bundles are equal.
+ *
+ * <p>Two bundles will be considered equal if they contain the same content.
*/
@SuppressWarnings("unchecked")
private static boolean bundleEquals(Bundle one, Bundle two) {
@@ -537,8 +556,9 @@
/**
* Calculates the hash code for a bundle.
- * <p> The hash code is only effected by the contents in the bundle. Bundles will get
- * consistent hash code if they have same contents.
+ *
+ * <p>The hash code is only effected by the contents in the bundle. Bundles will get consistent
+ * hash code if they have same contents.
*/
@SuppressWarnings("unchecked")
private static int bundleHashCode(Bundle bundle) {
@@ -663,11 +683,11 @@
* Create a new {@link GenericDocument.Builder}.
*
* @param uri The uri of {@link GenericDocument}.
- * @param schemaType The schema type of the {@link GenericDocument}. The passed-in
- * {@code schemaType} must be defined using {@link AppSearchSession#setSchema} prior
- * to inserting a document of this {@code schemaType} into the AppSearch index using
- * {@link AppSearchSession#putDocuments}. Otherwise, the document will be
- * rejected by {@link AppSearchSession#putDocuments}.
+ * @param schemaType The schema type of the {@link GenericDocument}. The passed-in {@code
+ * schemaType} must be defined using {@link AppSearchSession#setSchema} prior to
+ * inserting a document of this {@code schemaType} into the AppSearch index using {@link
+ * AppSearchSession#putDocuments}. Otherwise, the document will be rejected by {@link
+ * AppSearchSession#putDocuments}.
*/
@SuppressWarnings("unchecked")
public Builder(@NonNull String uri, @NonNull String schemaType) {
@@ -678,16 +698,16 @@
mBundle.putString(GenericDocument.SCHEMA_TYPE_FIELD, schemaType);
mBundle.putString(GenericDocument.NAMESPACE_FIELD, DEFAULT_NAMESPACE);
// Set current timestamp for creation timestamp by default.
- mBundle.putLong(GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD,
- System.currentTimeMillis());
+ mBundle.putLong(
+ GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD, System.currentTimeMillis());
mBundle.putLong(GenericDocument.TTL_MILLIS_FIELD, DEFAULT_TTL_MILLIS);
mBundle.putInt(GenericDocument.SCORE_FIELD, DEFAULT_SCORE);
mBundle.putBundle(PROPERTIES_FIELD, mProperties);
}
/**
- * Sets the app-defined namespace this Document resides in. No special values are
- * reserved or understood by the infrastructure.
+ * Sets the app-defined namespace this Document resides in. No special values are reserved
+ * or understood by the infrastructure.
*
* <p>URIs are unique within a namespace.
*
@@ -702,8 +722,13 @@
/**
* Sets the score of the {@link GenericDocument}.
*
- * <p>The score is a query-independent measure of the document's quality, relative to
- * other {@link GenericDocument}s of the same type.
+ * <p>The score is a query-independent measure of the document's quality, relative to other
+ * {@link GenericDocument}s of the same type.
+ *
+ * <p>Results may be sorted by score using {@link SearchSpec.Builder#setRankingStrategy}.
+ * Documents with higher scores are considered better than documents with lower scores.
+ *
+ * <p>Any nonnegative integer can be used a score.
*
* @throws IllegalArgumentException If the provided value is negative.
*/
@@ -718,22 +743,28 @@
}
/**
- * Sets the creation timestamp of the {@link GenericDocument}, in milliseconds. Should be
- * set using a value obtained from the {@link System#currentTimeMillis()} time base.
+ * Sets the creation timestamp of the {@link GenericDocument}, in milliseconds.
+ *
+ * <p>Should be set using a value obtained from the {@link System#currentTimeMillis} time
+ * base.
*/
@NonNull
public BuilderType setCreationTimestampMillis(long creationTimestampMillis) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mBundle.putLong(GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD,
- creationTimestampMillis);
+ mBundle.putLong(
+ GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD, creationTimestampMillis);
return mBuilderTypeInstance;
}
/**
* Sets the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
*
- * <p>After this many milliseconds since the {@link #setCreationTimestampMillis creation
- * timestamp}, the document is deleted.
+ * <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of
+ * {@code creationTimestampMillis + ttlMillis}, measured in the {@link
+ * System#currentTimeMillis} time base, the document will be auto-deleted.
+ *
+ * <p>The default value is 0, which means the document is permanent and won't be
+ * auto-deleted until the app is uninstalled.
*
* @param ttlMillis A non-negative duration in milliseconds.
* @throws IllegalArgumentException If the provided value is negative.
@@ -749,8 +780,7 @@
}
/**
- * Sets one or multiple {@code String} values for a property, replacing its previous
- * values.
+ * 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.
@@ -781,8 +811,7 @@
}
/**
- * Sets one or multiple {@code long} values for a property, replacing its previous
- * values.
+ * 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 property.
@@ -797,8 +826,7 @@
}
/**
- * Sets one or multiple {@code double} values for a property, replacing its previous
- * values.
+ * 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 property.
@@ -851,9 +879,14 @@
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 + ".");
+ throw new IllegalArgumentException(
+ "The String at "
+ + i
+ + " length is: "
+ + values[i].length()
+ + ", which exceeds length limit: "
+ + MAX_STRING_LENGTH
+ + ".");
}
}
mProperties.putStringArray(key, values);
@@ -911,7 +944,10 @@
throw new IllegalArgumentException("The input array is empty.");
} else if (length > MAX_REPEATED_PROPERTY_LENGTH) {
throw new IllegalArgumentException(
- "Repeated property \"" + key + "\" has length " + length
+ "Repeated property \""
+ + key
+ + "\" has length "
+ + length
+ ", which exceeds the limit of "
+ MAX_REPEATED_PROPERTY_LENGTH);
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/android/app/appsearch/GetByUriRequest.java
index e1e0eda..053d401 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/GetByUriRequest.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.util.ArraySet;
+
import com.android.internal.util.Preconditions;
import java.util.Arrays;
@@ -28,7 +29,7 @@
/**
* Encapsulates a request to retrieve documents by namespace and URI.
*
- * @see AppSearchManager#getByUri
+ * @see AppSearchSession#getByUri
* @hide
*/
public final class GetByUriRequest {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/PutDocumentsRequest.java b/apex/appsearch/framework/java/android/app/appsearch/PutDocumentsRequest.java
index 1f90bc1..42f1ff2 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/PutDocumentsRequest.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/PutDocumentsRequest.java
@@ -16,10 +16,10 @@
package android.app.appsearch;
-import android.annotation.SuppressLint;
-
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.app.appsearch.exceptions.AppSearchException;
+
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
@@ -29,9 +29,9 @@
import java.util.List;
/**
- * Encapsulates a request to index a document into an {@link AppSearchManager} database.
+ * Encapsulates a request to index a document into an {@link AppSearchSession} database.
*
- * @see AppSearchManager#putDocuments
+ * @see AppSearchSession#putDocuments
* @hide
*/
public final class PutDocumentsRequest {
@@ -53,7 +53,7 @@
private boolean mBuilt = false;
/** Adds one or more documents to the request. */
- @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
+ @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
@NonNull
public Builder addGenericDocument(@NonNull GenericDocument... documents) {
Preconditions.checkNotNull(documents);
@@ -61,7 +61,7 @@
}
/** Adds one or more documents to the request. */
- @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
+ @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
@NonNull
public Builder addGenericDocument(@NonNull Collection<GenericDocument> documents) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
diff --git a/apex/appsearch/framework/java/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/android/app/appsearch/RemoveByUriRequest.java
index 486857f..3d83c39 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/RemoveByUriRequest.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.util.ArraySet;
+
import com.android.internal.util.Preconditions;
import java.util.Arrays;
@@ -28,7 +29,7 @@
/**
* Encapsulates a request to remove documents by namespace and URI.
*
- * @see AppSearchManager#removeByUri
+ * @see AppSearchSession#removeByUri
* @hide
*/
public final class RemoveByUriRequest {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResult.java
index 99cb2f1..5f3c06a 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResult.java
@@ -16,60 +16,61 @@
package android.app.appsearch;
-import android.os.Bundle;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Bundle;
-import java.util.Objects;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
- * This class represents one of the results obtained from the query.
+ * This class represents one of the results obtained from an AppSearch query.
*
- * <p>It contains the document which matched, information about which section(s) in the document
- * matched, and snippet information containing textual summaries of the document's match(es).
+ * <p>This allows clients to obtain:
+ *
+ * <ul>
+ * <li>The document which matched, using {@link #getDocument}
+ * <li>Information about which properties in the document matched, and "snippet" information
+ * containing textual summaries of the document's matches, using {@link #getMatches}
+ * </ul>
+ *
+ * <p>"Snippet" refers to a substring of text from the content of document that is returned as a
+ * part of search result.
+ *
+ * @see SearchResults
* @hide
*/
public final class SearchResult {
/** @hide */
-
public static final String DOCUMENT_FIELD = "document";
/** @hide */
-
public static final String MATCHES_FIELD = "matches";
- @NonNull
- private final Bundle mBundle;
+ @NonNull private final Bundle mBundle;
- @NonNull
- private final Bundle mDocumentBundle;
+ @NonNull private final Bundle mDocumentBundle;
/** Cache of the inflated document. Comes from inflating mDocumentBundle at first use. */
- @Nullable
- private GenericDocument mDocument;
+ @Nullable private GenericDocument mDocument;
/**
* Contains a list of MatchInfo bundles that matched the request.
*
- * Only populated when requested in both {@link SearchSpec.Builder#setSnippetCount} and
+ * <p>Only populated when requested in both {@link SearchSpec.Builder#setSnippetCount} and
* {@link SearchSpec.Builder#setSnippetCountPerProperty}.
*
* @see #getMatches()
*/
- @NonNull
- private final List<Bundle> mMatchBundles;
+ @NonNull private final List<Bundle> mMatchBundles;
/** Cache of the inflated matches. Comes from inflating mMatchBundles at first use. */
- @Nullable
- private List<MatchInfo> mMatches;
+ @Nullable private List<MatchInfo> mMatches;
/** @hide */
-
public SearchResult(@NonNull Bundle bundle) {
mBundle = Preconditions.checkNotNull(bundle);
mDocumentBundle = Preconditions.checkNotNull(bundle.getBundle(DOCUMENT_FIELD));
@@ -77,7 +78,6 @@
}
/** @hide */
-
@NonNull
public Bundle getBundle() {
return mBundle;
@@ -85,6 +85,7 @@
/**
* Contains the matching {@link GenericDocument}.
+ *
* @return Document object which matched the query.
*/
@NonNull
@@ -98,10 +99,10 @@
/**
* Contains a list of Snippets that matched the request.
*
- * @return List of matches based on {@link SearchSpec}. If snippeting is disabled using
- * {@link SearchSpec.Builder#setSnippetCount} or
- * {@link SearchSpec.Builder#setSnippetCountPerProperty}, for all results after that
- * value, this method returns an empty list.
+ * @return List of matches based on {@link SearchSpec}. If snippeting is disabled using {@link
+ * SearchSpec.Builder#setSnippetCount} or {@link
+ * SearchSpec.Builder#setSnippetCountPerProperty}, for all results after that value, this
+ * method returns an empty list.
*/
@NonNull
public List<MatchInfo> getMatches() {
@@ -116,79 +117,94 @@
}
/**
- * Snippet: It refers to a substring of text from the content of document that is returned as a
- * part of search result.
- * This class represents a match objects for any Snippets that might be present in
- * {@link SearchResults} from query. Using this class
- * user can get the full text, exact matches and Snippets of document content for a given match.
+ * This class represents a match objects for any Snippets that might be present in {@link
+ * SearchResults} from query. Using this class user can get the full text, exact matches and
+ * Snippets of document content for a given match.
*
- * <p>Class Example 1:
- * A document contains following text in property subject:
+ * <p>Class Example 1: A document contains following text in property subject:
+ *
* <p>A commonly used fake word is foo. Another nonsense word that’s used a lot is bar.
*
* <p>If the queryExpression is "foo".
*
* <p>{@link MatchInfo#getPropertyPath()} returns "subject"
+ *
* <p>{@link MatchInfo#getFullText()} returns "A commonly used fake word is foo. Another
* nonsense word that’s used a lot is bar."
+ *
* <p>{@link MatchInfo#getExactMatchPosition()} returns [29, 32]
+ *
* <p>{@link MatchInfo#getExactMatch()} returns "foo"
+ *
* <p>{@link MatchInfo#getSnippetPosition()} returns [26, 33]
+ *
* <p>{@link MatchInfo#getSnippet()} returns "is foo."
+ *
* <p>
- * <p>Class Example 2:
- * A document contains a property name sender which contains 2 property names name and email, so
- * we will have 2 property paths: {@code sender.name} and {@code sender.email}.
- * <p>Let {@code sender.name = "Test Name Jr."} and
- * {@code sender.email = "TestNameJr@gmail.com"}
+ *
+ * <p>Class Example 2: A document contains a property name sender which contains 2 property
+ * names name and email, so we will have 2 property paths: {@code sender.name} and {@code
+ * sender.email}.
+ *
+ * <p>Let {@code sender.name = "Test Name Jr."} and {@code sender.email =
+ * "TestNameJr@gmail.com"}
*
* <p>If the queryExpression is "Test". We will have 2 matches.
*
- * <p> Match-1
+ * <p>Match-1
+ *
* <p>{@link MatchInfo#getPropertyPath()} returns "sender.name"
+ *
* <p>{@link MatchInfo#getFullText()} returns "Test Name Jr."
+ *
* <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 4]
+ *
* <p>{@link MatchInfo#getExactMatch()} returns "Test"
+ *
* <p>{@link MatchInfo#getSnippetPosition()} returns [0, 9]
+ *
* <p>{@link MatchInfo#getSnippet()} returns "Test Name"
- * <p> Match-2
+ *
+ * <p>Match-2
+ *
* <p>{@link MatchInfo#getPropertyPath()} returns "sender.email"
+ *
* <p>{@link MatchInfo#getFullText()} returns "TestNameJr@gmail.com"
+ *
* <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 20]
+ *
* <p>{@link MatchInfo#getExactMatch()} returns "TestNameJr@gmail.com"
+ *
* <p>{@link MatchInfo#getSnippetPosition()} returns [0, 20]
+ *
* <p>{@link MatchInfo#getSnippet()} returns "TestNameJr@gmail.com"
*/
public static final class MatchInfo {
/**
* The path of the matching snippet property.
+ *
* @hide
*/
-
public static final String PROPERTY_PATH_FIELD = "propertyPath";
/**
* The index of matching value in its property. A property may have multiple values. This
* index indicates which value is the match.
+ *
* @hide
*/
-
public static final String VALUES_INDEX_FIELD = "valuesIndex";
/** @hide */
-
public static final String EXACT_MATCH_POSITION_LOWER_FIELD = "exactMatchPositionLower";
/** @hide */
-
public static final String EXACT_MATCH_POSITION_UPPER_FIELD = "exactMatchPositionUpper";
/** @hide */
-
public static final String WINDOW_POSITION_LOWER_FIELD = "windowPositionLower";
/** @hide */
-
public static final String WINDOW_POSITION_UPPER_FIELD = "windowPositionUpper";
private final String mFullText;
@@ -201,16 +217,18 @@
mBundle = Preconditions.checkNotNull(bundle);
Preconditions.checkNotNull(document);
mPropertyPath = Preconditions.checkNotNull(bundle.getString(PROPERTY_PATH_FIELD));
- mFullText = getPropertyValues(
- document, mPropertyPath, mBundle.getInt(VALUES_INDEX_FIELD));
+ mFullText =
+ getPropertyValues(document, mPropertyPath, mBundle.getInt(VALUES_INDEX_FIELD));
}
/**
* Gets the property path corresponding to the given entry.
+ *
* <p>Property Path: '.' - delimited sequence of property names indicating which property in
* the Document these snippets correspond to.
- * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc.
- * For class example 1 this returns "subject"
+ *
+ * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc. For class
+ * example 1 this returns "subject"
*/
@NonNull
public String getPropertyPath() {
@@ -219,6 +237,7 @@
/**
* Gets the full text corresponding to the given entry.
+ *
* <p>For class example this returns "A commonly used fake word is foo. Another nonsense
* word that's used a lot is bar."
*/
@@ -229,20 +248,23 @@
/**
* Gets the exact {@link MatchRange} corresponding to the given entry.
+ *
* <p>For class example 1 this returns [29, 32]
*/
@NonNull
public MatchRange getExactMatchPosition() {
if (mExactMatchRange == null) {
- mExactMatchRange = new MatchRange(
- mBundle.getInt(EXACT_MATCH_POSITION_LOWER_FIELD),
- mBundle.getInt(EXACT_MATCH_POSITION_UPPER_FIELD));
+ mExactMatchRange =
+ new MatchRange(
+ mBundle.getInt(EXACT_MATCH_POSITION_LOWER_FIELD),
+ mBundle.getInt(EXACT_MATCH_POSITION_UPPER_FIELD));
}
return mExactMatchRange;
}
/**
- * Gets the {@link MatchRange} corresponding to the given entry.
+ * Gets the {@link MatchRange} corresponding to the given entry.
+ *
* <p>For class example 1 this returns "foo"
*/
@NonNull
@@ -252,26 +274,31 @@
/**
* Gets the snippet {@link MatchRange} corresponding to the given entry.
- * <p>Only populated when set maxSnippetSize > 0 in
- * {@link SearchSpec.Builder#setMaxSnippetSize}.
+ *
+ * <p>Only populated when set maxSnippetSize > 0 in {@link
+ * SearchSpec.Builder#setMaxSnippetSize}.
+ *
* <p>For class example 1 this returns [29, 41].
*/
@NonNull
public MatchRange getSnippetPosition() {
if (mWindowRange == null) {
- mWindowRange = new MatchRange(
- mBundle.getInt(WINDOW_POSITION_LOWER_FIELD),
- mBundle.getInt(WINDOW_POSITION_UPPER_FIELD));
+ mWindowRange =
+ new MatchRange(
+ mBundle.getInt(WINDOW_POSITION_LOWER_FIELD),
+ mBundle.getInt(WINDOW_POSITION_UPPER_FIELD));
}
return mWindowRange;
}
/**
* Gets the snippet corresponding to the given entry.
+ *
* <p>Snippet - Provides a subset of the content to display. Only populated when requested
- * maxSnippetSize > 0. The size of this content can be changed by
- * {@link SearchSpec.Builder#setMaxSnippetSize}. Windowing is centered around the middle of
- * the matched token with content on either side clipped to token boundaries.
+ * maxSnippetSize > 0. The size of this content can be changed by {@link
+ * SearchSpec.Builder#setMaxSnippetSize}. Windowing is centered around the middle of the
+ * matched token with content on either side clipped to token boundaries.
+ *
* <p>For class example 1 this returns "foo. Another"
*/
@NonNull
@@ -302,11 +329,10 @@
/**
* Class providing the position range of matching information.
*
- * <p> All ranges are finite, and the left side of the range is always {@code <=} the right
- * side of the range.
+ * <p>All ranges are finite, and the left side of the range is always {@code <=} the right side
+ * of the range.
*
- * <p> Example: MatchRange(0, 100) represent a hundred ints from 0 to 99."
- *
+ * <p>Example: MatchRange(0, 100) represent a hundred ints from 0 to 99."
*/
public static final class MatchRange {
private final int mEnd;
@@ -314,18 +340,18 @@
/**
* Creates a new immutable range.
- * <p> The endpoints are {@code [start, end)}; that is the range is bounded. {@code start}
+ *
+ * <p>The endpoints are {@code [start, end)}; that is the range is bounded. {@code start}
* must be lesser or equal to {@code end}.
*
* @param start The start point (inclusive)
* @param end The end point (exclusive)
* @hide
*/
-
public MatchRange(int start, int end) {
if (start > end) {
- throw new IllegalArgumentException("Start point must be less than or equal to "
- + "end point");
+ throw new IllegalArgumentException(
+ "Start point must be less than or equal to " + "end point");
}
mStart = start;
mEnd = end;
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResultPage.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResultPage.java
index 756d1b5..dbd09d6 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchResultPage.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResultPage.java
@@ -16,10 +16,9 @@
package android.app.appsearch;
-import android.os.Bundle;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Bundle;
import com.android.internal.util.Preconditions;
@@ -29,19 +28,17 @@
/**
* This class represents a page of {@link SearchResult}s
+ *
* @hide
*/
-
public class SearchResultPage {
public static final String RESULTS_FIELD = "results";
public static final String NEXT_PAGE_TOKEN_FIELD = "nextPageToken";
private final long mNextPageToken;
- @Nullable
- private List<SearchResult> mResults;
+ @Nullable private List<SearchResult> mResults;
- @NonNull
- private final Bundle mBundle;
+ @NonNull private final Bundle mBundle;
public SearchResultPage(@NonNull Bundle bundle) {
mBundle = Preconditions.checkNotNull(bundle);
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
index 15acf10..885802d 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
@@ -16,14 +16,14 @@
package android.app.appsearch;
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
-
+import android.annotation.SuppressLint;
import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.exceptions.IllegalSearchSpecException;
+import android.os.Bundle;
+
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -52,10 +52,10 @@
static final String MAX_SNIPPET_FIELD = "maxSnippet";
/** @hide */
-
public static final int DEFAULT_NUM_PER_PAGE = 10;
- // TODO(b/170371356): In framework, we may want these limits might be flag controlled.
+ // TODO(b/170371356): In framework, we may want these limits to be flag controlled.
+ // If that happens, the @IntRange() directives in this class may have to change.
private static final int MAX_NUM_PER_PAGE = 10_000;
private static final int MAX_SNIPPET_COUNT = 10_000;
private static final int MAX_SNIPPET_PER_PROPERTY_COUNT = 10_000;
@@ -63,43 +63,45 @@
/**
* Term Match Type for the query.
+ *
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// {@link com.google.android.icing.proto.SearchSpecProto.termMatchType}
- @IntDef(value = {
- TERM_MATCH_EXACT_ONLY,
- TERM_MATCH_PREFIX
- })
+ @IntDef(value = {TERM_MATCH_EXACT_ONLY, TERM_MATCH_PREFIX})
@Retention(RetentionPolicy.SOURCE)
public @interface TermMatch {}
/**
* Query terms will only match exact tokens in the index.
+ *
* <p>Ex. A query term "foo" will only match indexed token "foo", and not "foot" or "football".
*/
public static final int TERM_MATCH_EXACT_ONLY = 1;
/**
* Query terms will match indexed tokens when the query term is a prefix of the token.
+ *
* <p>Ex. A query term "foo" will match indexed tokens like "foo", "foot", and "football".
*/
public static final int TERM_MATCH_PREFIX = 2;
/**
* Ranking Strategy for query result.
+ *
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// {@link ScoringSpecProto.RankingStrategy.Code}
- @IntDef(value = {
- RANKING_STRATEGY_NONE,
- RANKING_STRATEGY_DOCUMENT_SCORE,
- RANKING_STRATEGY_CREATION_TIMESTAMP
- })
+ @IntDef(
+ value = {
+ RANKING_STRATEGY_NONE,
+ RANKING_STRATEGY_DOCUMENT_SCORE,
+ RANKING_STRATEGY_CREATION_TIMESTAMP
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface RankingStrategy {}
- /** No Ranking, results are returned in arbitrary order.*/
+ /** No Ranking, results are returned in arbitrary order. */
public static final int RANKING_STRATEGY_NONE = 0;
/** Ranked by app-provided document scores. */
public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1;
@@ -108,14 +110,12 @@
/**
* Order for query result.
+ *
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// {@link ScoringSpecProto.Order.Code}
- @IntDef(value = {
- ORDER_DESCENDING,
- ORDER_ASCENDING
- })
+ @IntDef(value = {ORDER_DESCENDING, ORDER_ASCENDING})
@Retention(RetentionPolicy.SOURCE)
public @interface Order {}
@@ -127,7 +127,6 @@
private final Bundle mBundle;
/** @hide */
-
public SearchSpec(@NonNull Bundle bundle) {
Preconditions.checkNotNull(bundle);
mBundle = bundle;
@@ -135,9 +134,9 @@
/**
* Returns the {@link Bundle} populated by this builder.
+ *
* @hide
*/
-
@NonNull
public Bundle getBundle() {
return mBundle;
@@ -154,12 +153,12 @@
* <p>If empty, the query will search over all schema types.
*/
@NonNull
- public List<String> getSchemas() {
- List<String> schemas = mBundle.getStringArrayList(SCHEMA_TYPE_FIELD);
- if (schemas == null) {
+ public List<String> getSchemaTypes() {
+ List<String> schemaTypes = mBundle.getStringArrayList(SCHEMA_TYPE_FIELD);
+ if (schemaTypes == null) {
return Collections.emptyList();
}
- return Collections.unmodifiableList(schemas);
+ return Collections.unmodifiableList(schemaTypes);
}
/**
@@ -176,8 +175,8 @@
return Collections.unmodifiableList(namespaces);
}
- /** Returns the number of results per page in the returned object. */
- public int getNumPerPage() {
+ /** Returns the number of results per page in the result set. */
+ public int getResultCountPerPage() {
return mBundle.getInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
}
@@ -222,14 +221,12 @@
mBundle.putInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
}
- /**
- * Indicates how the query terms should match {@code TermMatchCode} in the index.
- */
+ /** Indicates how the query terms should match {@code TermMatchCode} in the index. */
@NonNull
public Builder setTermMatch(@TermMatch int termMatchTypeCode) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkArgumentInRange(termMatchTypeCode, TERM_MATCH_EXACT_ONLY,
- TERM_MATCH_PREFIX, "Term match type");
+ Preconditions.checkArgumentInRange(
+ termMatchTypeCode, TERM_MATCH_EXACT_ONLY, TERM_MATCH_PREFIX, "Term match type");
mBundle.putInt(TERM_MATCH_TYPE_FIELD, termMatchTypeCode);
return this;
}
@@ -241,10 +238,10 @@
* <p>If unset, the query will search over all schema types.
*/
@NonNull
- public Builder addSchema(@NonNull String... schemaTypes) {
+ public Builder addSchemaType(@NonNull String... schemaTypes) {
Preconditions.checkNotNull(schemaTypes);
Preconditions.checkState(!mBuilt, "Builder has already been used");
- return addSchema(Arrays.asList(schemaTypes));
+ return addSchemaType(Arrays.asList(schemaTypes));
}
/**
@@ -254,7 +251,7 @@
* <p>If unset, the query will search over all schema types.
*/
@NonNull
- public Builder addSchema(@NonNull Collection<String> schemaTypes) {
+ public Builder addSchemaType(@NonNull Collection<String> schemaTypes) {
Preconditions.checkNotNull(schemaTypes);
Preconditions.checkState(!mBuilt, "Builder has already been used");
mSchemaTypes.addAll(schemaTypes);
@@ -262,8 +259,9 @@
}
/**
- * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that
- * have the specified namespaces.
+ * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that have
+ * the specified namespaces.
+ *
* <p>If unset, the query will search over all namespaces.
*/
@NonNull
@@ -274,8 +272,9 @@
}
/**
- * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that
- * have the specified namespaces.
+ * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that have
+ * the specified namespaces.
+ *
* <p>If unset, the query will search over all namespaces.
*/
@NonNull
@@ -288,51 +287,56 @@
/**
* Sets the number of results per page in the returned object.
- * <p> The default number of results per page is 10. And should be set in range [0, 10k].
+ *
+ * <p>The default number of results per page is 10.
*/
@NonNull
- public SearchSpec.Builder setNumPerPage(int numPerPage) {
+ public SearchSpec.Builder setResultCountPerPage(
+ @IntRange(from = 0, to = MAX_NUM_PER_PAGE) int numPerPage) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkArgumentInRange(numPerPage, 0, MAX_NUM_PER_PAGE, "NumPerPage");
mBundle.putInt(NUM_PER_PAGE_FIELD, numPerPage);
return this;
}
- /** Sets ranking strategy for AppSearch results.*/
+ /** Sets ranking strategy for AppSearch results. */
@NonNull
public Builder setRankingStrategy(@RankingStrategy int rankingStrategy) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkArgumentInRange(rankingStrategy, RANKING_STRATEGY_NONE,
- RANKING_STRATEGY_CREATION_TIMESTAMP, "Result ranking strategy");
+ Preconditions.checkArgumentInRange(
+ rankingStrategy,
+ RANKING_STRATEGY_NONE,
+ RANKING_STRATEGY_CREATION_TIMESTAMP,
+ "Result ranking strategy");
mBundle.putInt(RANKING_STRATEGY_FIELD, rankingStrategy);
return this;
}
/**
- * Indicates the order of returned search results, the default is DESC, meaning that results
- * with higher scores come first.
+ * Indicates the order of returned search results, the default is {@link #ORDER_DESCENDING},
+ * meaning that results with higher scores come first.
+ *
* <p>This order field will be ignored if RankingStrategy = {@code RANKING_STRATEGY_NONE}.
*/
@NonNull
public Builder setOrder(@Order int order) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkArgumentInRange(order, ORDER_DESCENDING, ORDER_ASCENDING,
- "Result ranking order");
+ Preconditions.checkArgumentInRange(
+ order, ORDER_DESCENDING, ORDER_ASCENDING, "Result ranking order");
mBundle.putInt(ORDER_FIELD, order);
return this;
}
/**
- * Only the first {@code snippetCount} documents based on the ranking strategy
- * will have snippet information provided.
+ * Only the first {@code snippetCount} documents based on the ranking strategy will have
+ * snippet information provided.
*
* <p>If set to 0 (default), snippeting is disabled and {@link SearchResult#getMatches} will
* return {@code null} for that result.
- *
- * <p>The value should be set in range[0, 10k].
*/
@NonNull
- public SearchSpec.Builder setSnippetCount(int snippetCount) {
+ public SearchSpec.Builder setSnippetCount(
+ @IntRange(from = 0, to = MAX_SNIPPET_COUNT) int snippetCount) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkArgumentInRange(snippetCount, 0, MAX_SNIPPET_COUNT, "snippetCount");
mBundle.putInt(SNIPPET_COUNT_FIELD, snippetCount);
@@ -341,39 +345,40 @@
/**
* Sets {@code snippetCountPerProperty}. Only the first {@code snippetCountPerProperty}
- * snippets for a every property of {@link GenericDocument} will contain snippet
- * information.
+ * snippets for each property of {@link GenericDocument} will contain snippet information.
*
- * <p>If set to 0, snippeting is disabled and {@link SearchResult#getMatches}
- * will return {@code null} for that result.
- *
- * <p>The value should be set in range[0, 10k].
+ * <p>If set to 0, snippeting is disabled and {@link SearchResult#getMatches} will return
+ * {@code null} for that result.
*/
@NonNull
- public SearchSpec.Builder setSnippetCountPerProperty(int snippetCountPerProperty) {
+ public SearchSpec.Builder setSnippetCountPerProperty(
+ @IntRange(from = 0, to = MAX_SNIPPET_PER_PROPERTY_COUNT)
+ int snippetCountPerProperty) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkArgumentInRange(snippetCountPerProperty,
- 0, MAX_SNIPPET_PER_PROPERTY_COUNT, "snippetCountPerProperty");
+ Preconditions.checkArgumentInRange(
+ snippetCountPerProperty,
+ 0,
+ MAX_SNIPPET_PER_PROPERTY_COUNT,
+ "snippetCountPerProperty");
mBundle.putInt(SNIPPET_COUNT_PER_PROPERTY_FIELD, snippetCountPerProperty);
return this;
}
/**
- * Sets {@code maxSnippetSize}, the maximum snippet size. Snippet windows start at
- * {@code maxSnippetSize/2} bytes before the middle of the matching token and end at
- * {@code maxSnippetSize/2} bytes after the middle of the matching token. It respects
- * token boundaries, therefore the returned window may be smaller than requested.
+ * Sets {@code maxSnippetSize}, the maximum snippet size. Snippet windows start at {@code
+ * maxSnippetSize/2} bytes before the middle of the matching token and end at {@code
+ * maxSnippetSize/2} bytes after the middle of the matching token. It respects token
+ * boundaries, therefore the returned window may be smaller than requested.
*
- * <p> Setting {@code maxSnippetSize} to 0 will disable windowing and an empty string will
- * be returned. If matches enabled is also set to false, then snippeting is disabled.
+ * <p>Setting {@code maxSnippetSize} to 0 will disable windowing and an empty string will be
+ * returned. If matches enabled is also set to false, then snippeting is disabled.
*
* <p>Ex. {@code maxSnippetSize} = 16. "foo bar baz bat rat" with a query of "baz" will
* return a window of "bar baz bat" which is only 11 bytes long.
- *
- * <p>The value should be in range[0, 10k].
*/
@NonNull
- public SearchSpec.Builder setMaxSnippetSize(int maxSnippetSize) {
+ public SearchSpec.Builder setMaxSnippetSize(
+ @IntRange(from = 0, to = MAX_SNIPPET_SIZE_LIMIT) int maxSnippetSize) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkArgumentInRange(
maxSnippetSize, 0, MAX_SNIPPET_SIZE_LIMIT, "maxSnippetSize");
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/android/app/appsearch/SetSchemaRequest.java
index f2c8156..3e472fd 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SetSchemaRequest.java
@@ -16,11 +16,11 @@
package android.app.appsearch;
-import android.annotation.SuppressLint;
-
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.app.appsearch.exceptions.AppSearchException;
import android.util.ArraySet;
+
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
@@ -30,9 +30,9 @@
import java.util.Set;
/**
- * Encapsulates a request to update the schema of an {@link AppSearchManager} database.
+ * Encapsulates a request to update the schema of an {@link AppSearchSession} database.
*
- * @see AppSearchManager#setSchema
+ * @see AppSearchSession#setSchema
* @hide
*/
public final class SetSchemaRequest {
@@ -81,10 +81,10 @@
* Configures the {@link SetSchemaRequest} to delete any existing documents that don't
* follow the new schema.
*
- * <p>By default, this is {@code false} and schema incompatibility causes the
- * {@link AppSearchManager#setSchema} call to fail.
+ * <p>By default, this is {@code false} and schema incompatibility causes the {@link
+ * AppSearchSession#setSchema} call to fail.
*
- * @see AppSearchManager#setSchema
+ * @see AppSearchSession#setSchema
*/
@NonNull
public Builder setForceOverride(boolean forceOverride) {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java b/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java
index 15d0992..704f180 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java
@@ -21,18 +21,19 @@
import android.app.appsearch.AppSearchResult;
/**
- * An exception thrown by {@code android.app.appsearch.AppSearchManager} or a subcomponent.
+ * An exception thrown by {@link android.app.appsearch.AppSearchSession} or a subcomponent.
*
- * <p>These exceptions can be converted into a failed {@link AppSearchResult}
- * for propagating to the client.
+ * <p>These exceptions can be converted into a failed {@link AppSearchResult} for propagating to the
+ * client.
+ *
* @hide
*/
-//TODO(b/157082794): Linkify to AppSearchManager once that API is public
public class AppSearchException extends Exception {
private final @AppSearchResult.ResultCode int mResultCode;
/**
* Initializes an {@link AppSearchException} with no message.
+ *
* @hide
*/
public AppSearchException(@AppSearchResult.ResultCode int resultCode) {
@@ -59,9 +60,7 @@
return mResultCode;
}
- /**
- * Converts this {@link java.lang.Exception} into a failed {@link AppSearchResult}
- */
+ /** Converts this {@link java.lang.Exception} into a failed {@link AppSearchResult} */
@NonNull
public <T> AppSearchResult<T> toAppSearchResult() {
return AppSearchResult.newFailedResult(mResultCode, getMessage());
diff --git a/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSchemaException.java b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSchemaException.java
index 6dd86f5..5f8da7f 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSchemaException.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSchemaException.java
@@ -18,14 +18,12 @@
import android.annotation.NonNull;
-
/**
* Indicates that a {@link android.app.appsearch.AppSearchSchema} has logical inconsistencies such
* as unpopulated mandatory fields or illegal combinations of parameters.
*
* @hide
*/
-
public class IllegalSchemaException extends IllegalArgumentException {
/**
* Constructs a new {@link IllegalSchemaException}.
diff --git a/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSearchSpecException.java b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSearchSpecException.java
index 3ef887f..0b5dc2e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSearchSpecException.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSearchSpecException.java
@@ -18,14 +18,12 @@
import android.annotation.NonNull;
-
/**
- * Indicates that a {@link android.app.appsearch.SearchResult} has logical inconsistencies such
- * as unpopulated mandatory fields or illegal combinations of parameters.
+ * Indicates that a {@link android.app.appsearch.SearchResult} has logical inconsistencies such as
+ * unpopulated mandatory fields or illegal combinations of parameters.
*
* @hide
*/
-
public class IllegalSearchSpecException extends IllegalArgumentException {
/**
* Constructs a new {@link IllegalSearchSpecException}.
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index e0215449..247089b 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -16,13 +16,7 @@
package com.android.server.appsearch.external.localstorage;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
import android.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
import android.annotation.WorkerThread;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
@@ -30,6 +24,12 @@
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.exceptions.AppSearchException;
+import android.os.Bundle;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
@@ -62,7 +62,6 @@
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -77,55 +76,65 @@
*
* <p>A single instance of {@link AppSearchImpl} can support all databases. Schemas and documents
* are physically saved together in {@link IcingSearchEngine}, but logically isolated:
+ *
* <ul>
- * <li>Rewrite SchemaType in SchemaProto by adding database name prefix and save into
- * SchemaTypes set in {@link #setSchema}.
- * <li>Rewrite namespace and SchemaType in DocumentProto by adding database name prefix and
- * save to namespaces set in {@link #putDocument}.
- * <li>Remove database name prefix when retrieve documents in {@link #getDocument} and
- * {@link #query}.
- * <li>Rewrite filters in {@link SearchSpecProto} to have all namespaces and schema types of
- * the queried database when user using empty filters in {@link #query}.
+ * <li>Rewrite SchemaType in SchemaProto by adding database name prefix and save into SchemaTypes
+ * set in {@link #setSchema}.
+ * <li>Rewrite namespace and SchemaType in DocumentProto by adding database name prefix and save
+ * to namespaces set in {@link #putDocument}.
+ * <li>Remove database name prefix when retrieve documents in {@link #getDocument} and {@link
+ * #query}.
+ * <li>Rewrite filters in {@link SearchSpecProto} to have all namespaces and schema types of the
+ * queried database when user using empty filters in {@link #query}.
* </ul>
*
* <p>Methods in this class belong to two groups, the query group and the mutate group.
+ *
* <ul>
- * <li>All methods are going to modify global parameters and data in Icing are executed under
- * WRITE lock to keep thread safety.
- * <li>All methods are going to access global parameters or query data from Icing are executed
- * under READ lock to improve query performance.
+ * <li>All methods are going to modify global parameters and data in Icing are executed under
+ * WRITE lock to keep thread safety.
+ * <li>All methods are going to access global parameters or query data from Icing are executed
+ * under READ lock to improve query performance.
* </ul>
*
* <p>This class is thread safe.
*
* @hide
*/
-
@WorkerThread
public final class AppSearchImpl {
private static final String TAG = "AppSearchImpl";
- private static final char DATABASE_DELIMITER = '/';
- @VisibleForTesting
- static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000;
- @VisibleForTesting
- static final int OPTIMIZE_THRESHOLD_BYTES = 1_000_000; // 1MB
- @VisibleForTesting
- static final int CHECK_OPTIMIZE_INTERVAL = 100;
+ @VisibleForTesting static final char DATABASE_DELIMITER = '/';
+
+ @VisibleForTesting static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000;
+ @VisibleForTesting static final int OPTIMIZE_THRESHOLD_BYTES = 1_000_000; // 1MB
+ @VisibleForTesting static final int CHECK_OPTIMIZE_INTERVAL = 100;
private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
- private final IcingSearchEngine mIcingSearchEngine;
+
+ @GuardedBy("mReadWriteLock")
+ private final IcingSearchEngine mIcingSearchEngineLocked;
+
+ @GuardedBy("mReadWriteLock")
+ private final VisibilityStore mVisibilityStoreLocked;
// The map contains schemaTypes and namespaces for all database. All values in the map have
- // been already added database name prefix.
- private final Map<String, Set<String>> mSchemaMap = new HashMap<>();
- private final Map<String, Set<String>> mNamespaceMap = new HashMap<>();
+ // the database name prefix.
+ // TODO(b/172360376): Check if this can be replaced with an ArrayMap
+ @GuardedBy("mReadWriteLock")
+ private final Map<String, Set<String>> mSchemaMapLocked = new HashMap<>();
+
+ // TODO(b/172360376): Check if this can be replaced with an ArrayMap
+ @GuardedBy("mReadWriteLock")
+ private final Map<String, Set<String>> mNamespaceMapLocked = new HashMap<>();
/**
- * The counter to check when to call {@link #checkForOptimize(boolean)}. The interval is
+ * The counter to check when to call {@link #checkForOptimizeLocked(boolean)}. The interval is
* {@link #CHECK_OPTIMIZE_INTERVAL}.
*/
- private int mOptimizeIntervalCount = 0;
+ @GuardedBy("mReadWriteLock")
+ private int mOptimizeIntervalCountLocked = 0;
/**
* Creates and initializes an instance of {@link AppSearchImpl} which writes data to the given
@@ -134,44 +143,77 @@
@NonNull
public static AppSearchImpl create(@NonNull File icingDir) throws AppSearchException {
Preconditions.checkNotNull(icingDir);
- return new AppSearchImpl(icingDir);
+ AppSearchImpl appSearchImpl = new AppSearchImpl(icingDir);
+ appSearchImpl.initializeVisibilityStore();
+ return appSearchImpl;
}
private AppSearchImpl(@NonNull File icingDir) throws AppSearchException {
boolean isReset = false;
mReadWriteLock.writeLock().lock();
+
try {
// We synchronize here because we don't want to call IcingSearchEngine.initialize() more
// than once. It's unnecessary and can be a costly operation.
- IcingSearchEngineOptions options = IcingSearchEngineOptions.newBuilder()
- .setBaseDir(icingDir.getAbsolutePath()).build();
- mIcingSearchEngine = new IcingSearchEngine(options);
+ IcingSearchEngineOptions options =
+ IcingSearchEngineOptions.newBuilder()
+ .setBaseDir(icingDir.getAbsolutePath())
+ .build();
+ mIcingSearchEngineLocked = new IcingSearchEngine(options);
- InitializeResultProto initializeResultProto = mIcingSearchEngine.initialize();
+ InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize();
SchemaProto schemaProto = null;
GetAllNamespacesResultProto getAllNamespacesResultProto = null;
try {
checkSuccess(initializeResultProto.getStatus());
- schemaProto = getSchemaProto();
- getAllNamespacesResultProto = mIcingSearchEngine.getAllNamespaces();
+ schemaProto = getSchemaProtoLocked();
+ getAllNamespacesResultProto = mIcingSearchEngineLocked.getAllNamespaces();
checkSuccess(getAllNamespacesResultProto.getStatus());
} catch (AppSearchException e) {
+ Log.w(TAG, "Error initializing, resetting IcingSearchEngine.", e);
// Some error. Reset and see if it fixes it.
reset();
isReset = true;
}
+
+ // Populate schema map
for (SchemaTypeConfigProto schema : schemaProto.getTypesList()) {
String qualifiedSchemaType = schema.getSchemaType();
- addToMap(mSchemaMap, getDatabaseName(qualifiedSchemaType), qualifiedSchemaType);
+ addToMap(
+ mSchemaMapLocked,
+ getDatabaseName(qualifiedSchemaType),
+ qualifiedSchemaType);
}
+
+ // Populate namespace map
for (String qualifiedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
- addToMap(mNamespaceMap, getDatabaseName(qualifiedNamespace), qualifiedNamespace);
+ addToMap(
+ mNamespaceMapLocked,
+ getDatabaseName(qualifiedNamespace),
+ qualifiedNamespace);
}
+
// TODO(b/155939114): It's possible to optimize after init, which would reduce the time
// to when we're able to serve queries. Consider moving this optimize call out.
if (!isReset) {
- checkForOptimize(/* force= */ true);
+ checkForOptimizeLocked(/* force= */ true);
}
+
+ mVisibilityStoreLocked = new VisibilityStore(this);
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Initialize the visibility store in AppSearchImpl.
+ *
+ * @throws AppSearchException on IcingSearchEngine error.
+ */
+ void initializeVisibilityStore() throws AppSearchException {
+ mReadWriteLock.writeLock().lock();
+ try {
+ mVisibilityStoreLocked.initialize();
} finally {
mReadWriteLock.writeLock().unlock();
}
@@ -182,35 +224,36 @@
*
* <p>This method belongs to mutate group.
*
- * @param databaseName The name of the database where this schema lives.
- * @param schemas Schemas to set for this app.
+ * @param databaseName The name of the database where this schema lives.
+ * @param schemas Schemas to set for this app.
* @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents
- * which do not comply with the new schema will be deleted.
+ * which do not comply with the new schema will be deleted.
* @throws AppSearchException on IcingSearchEngine error.
*/
- public void setSchema(@NonNull String databaseName, @NonNull Set<AppSearchSchema> schemas,
- boolean forceOverride) throws AppSearchException {
- SchemaProto schemaProto = getSchemaProto();
-
- SchemaProto.Builder existingSchemaBuilder = schemaProto.toBuilder();
-
- SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
- for (AppSearchSchema schema : schemas) {
- SchemaTypeConfigProto schemaTypeProto = SchemaToProtoConverter.convert(schema);
- newSchemaBuilder.addTypes(schemaTypeProto);
- }
-
- // Combine the existing schema (which may have types from other databases) with this
- // database's new schema. Modifies the existingSchemaBuilder.
- Set<String> newTypeNames = rewriteSchema(databaseName, existingSchemaBuilder,
- newSchemaBuilder.build());
-
- SetSchemaResultProto setSchemaResultProto;
+ public void setSchema(
+ @NonNull String databaseName,
+ @NonNull Set<AppSearchSchema> schemas,
+ boolean forceOverride)
+ throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
+ SchemaProto.Builder existingSchemaBuilder = getSchemaProtoLocked().toBuilder();
+
+ SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
+ for (AppSearchSchema schema : schemas) {
+ SchemaTypeConfigProto schemaTypeProto = SchemaToProtoConverter.convert(schema);
+ newSchemaBuilder.addTypes(schemaTypeProto);
+ }
+
+ // Combine the existing schema (which may have types from other databases) with this
+ // database's new schema. Modifies the existingSchemaBuilder.
+ RewrittenSchemaResults rewrittenSchemaResults =
+ rewriteSchema(databaseName, existingSchemaBuilder, newSchemaBuilder.build());
+
// Apply schema
- setSchemaResultProto =
- mIcingSearchEngine.setSchema(existingSchemaBuilder.build(), forceOverride);
+ SetSchemaResultProto setSchemaResultProto =
+ mIcingSearchEngineLocked.setSchema(
+ existingSchemaBuilder.build(), forceOverride);
// Determine whether it succeeded.
try {
@@ -219,11 +262,12 @@
// Improve the error message by merging in information about incompatible types.
if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
|| setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0) {
- String newMessage = e.getMessage()
- + "\n Deleted types: "
- + setSchemaResultProto.getDeletedSchemaTypesList()
- + "\n Incompatible types: "
- + setSchemaResultProto.getIncompatibleSchemaTypesList();
+ String newMessage =
+ e.getMessage()
+ + "\n Deleted types: "
+ + setSchemaResultProto.getDeletedSchemaTypesList()
+ + "\n Incompatible types: "
+ + setSchemaResultProto.getIncompatibleSchemaTypesList();
throw new AppSearchException(e.getResultCode(), newMessage, e.getCause());
} else {
throw e;
@@ -231,16 +275,18 @@
}
// Update derived data structures.
- mSchemaMap.put(databaseName, newTypeNames);
+ mSchemaMapLocked.put(databaseName, rewrittenSchemaResults.mRewrittenQualifiedTypes);
+ mVisibilityStoreLocked.updateSchemas(
+ databaseName, rewrittenSchemaResults.mDeletedQualifiedTypes);
// Determine whether to schedule an immediate optimize.
if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
|| (setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0
- && forceOverride)) {
+ && forceOverride)) {
// Any existing schemas which is not in 'schemas' will be deleted, and all
// documents of these types were also deleted. And so well if we force override
// incompatible schemas.
- checkForOptimize(/* force= */true);
+ checkForOptimizeLocked(/* force= */ true);
}
} finally {
mReadWriteLock.writeLock().unlock();
@@ -248,28 +294,63 @@
}
/**
+ * Update the visibility settings for this app.
+ *
+ * <p>This method belongs to the mutate group
+ *
+ * @param databaseName The name of the database where the visibility settings will apply.
+ * @param schemasHiddenFromPlatformSurfaces Schemas that should be hidden from platform surfaces
+ * @throws AppSearchException on IcingSearchEngine error
+ */
+ public void setVisibility(
+ @NonNull String databaseName, @NonNull Set<String> schemasHiddenFromPlatformSurfaces)
+ throws AppSearchException {
+ mReadWriteLock.writeLock().lock();
+ try {
+ String databasePrefix = getDatabasePrefix(databaseName);
+ Set<String> qualifiedSchemasHiddenFromPlatformSurface =
+ new ArraySet<>(schemasHiddenFromPlatformSurfaces.size());
+ for (String schema : schemasHiddenFromPlatformSurfaces) {
+ Set<String> existingSchemas = mSchemaMapLocked.get(databaseName);
+ if (existingSchemas == null || !existingSchemas.contains(databasePrefix + schema)) {
+ throw new AppSearchException(
+ AppSearchResult.RESULT_NOT_FOUND,
+ "Unknown schema(s): "
+ + schemasHiddenFromPlatformSurfaces
+ + " provided during setVisibility.");
+ }
+ qualifiedSchemasHiddenFromPlatformSurface.add(databasePrefix + schema);
+ }
+ mVisibilityStoreLocked.setVisibility(
+ databaseName, qualifiedSchemasHiddenFromPlatformSurface);
+ } finally {
+ mReadWriteLock.writeLock().lock();
+ }
+ }
+
+ /**
* Adds a document to the AppSearch index.
*
* <p>This method belongs to mutate group.
*
* @param databaseName The databaseName this document resides in.
- * @param document The document to index.
+ * @param document The document to index.
* @throws AppSearchException on IcingSearchEngine error.
*/
public void putDocument(@NonNull String databaseName, @NonNull GenericDocument document)
throws AppSearchException {
- DocumentProto.Builder documentBuilder = GenericDocumentToProtoConverter.convert(
- document).toBuilder();
+ DocumentProto.Builder documentBuilder =
+ GenericDocumentToProtoConverter.convert(document).toBuilder();
addPrefixToDocument(documentBuilder, getDatabasePrefix(databaseName));
PutResultProto putResultProto;
mReadWriteLock.writeLock().lock();
try {
- putResultProto = mIcingSearchEngine.put(documentBuilder.build());
- addToMap(mNamespaceMap, databaseName, documentBuilder.getNamespace());
+ putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
+ addToMap(mNamespaceMapLocked, databaseName, documentBuilder.getNamespace());
// The existing documents with same URI will be deleted, so there maybe some resources
// could be released after optimize().
- checkForOptimize(/* force= */false);
+ checkForOptimizeLocked(/* force= */ false);
} finally {
mReadWriteLock.writeLock().unlock();
}
@@ -282,19 +363,20 @@
* <p>This method belongs to query group.
*
* @param databaseName The databaseName this document resides in.
- * @param namespace The namespace this document resides in.
- * @param uri The URI of the document to get.
+ * @param namespace The namespace this document resides in.
+ * @param uri The URI of the document to get.
* @return The Document contents
* @throws AppSearchException on IcingSearchEngine error.
*/
@NonNull
- public GenericDocument getDocument(@NonNull String databaseName, @NonNull String namespace,
- @NonNull String uri) throws AppSearchException {
+ public GenericDocument getDocument(
+ @NonNull String databaseName, @NonNull String namespace, @NonNull String uri)
+ throws AppSearchException {
GetResultProto getResultProto;
mReadWriteLock.readLock().lock();
try {
- getResultProto = mIcingSearchEngine.get(
- getDatabasePrefix(databaseName) + namespace, uri);
+ getResultProto =
+ mIcingSearchEngineLocked.get(getDatabasePrefix(databaseName) + namespace, uri);
} finally {
mReadWriteLock.readLock().unlock();
}
@@ -310,19 +392,25 @@
*
* <p>This method belongs to query group.
*
- * @param databaseName The databaseName this query for.
+ * @param databaseName The databaseName this query for.
* @param queryExpression Query String to search.
- * @param searchSpec Spec for setting filters, raw query etc.
- * @return The results of performing this search. It may contain an empty list of results if
- * no documents matched the query.
+ * @param searchSpec Spec for setting filters, raw query etc.
+ * @return The results of performing this search. It may contain an empty list of results if no
+ * documents matched the query.
* @throws AppSearchException on IcingSearchEngine error.
*/
@NonNull
public SearchResultPage query(
@NonNull String databaseName,
@NonNull String queryExpression,
- @NonNull SearchSpec searchSpec) throws AppSearchException {
- return doQuery(Collections.singleton(databaseName), queryExpression, searchSpec);
+ @NonNull SearchSpec searchSpec)
+ throws AppSearchException {
+ mReadWriteLock.readLock().lock();
+ try {
+ return doQueryLocked(Collections.singleton(databaseName), queryExpression, searchSpec);
+ } finally {
+ mReadWriteLock.readLock().unlock();
+ }
}
/**
@@ -332,45 +420,54 @@
* <p>This method belongs to query group.
*
* @param queryExpression Query String to search.
- * @param searchSpec Spec for setting filters, raw query etc.
- * @return The results of performing this search. It may contain an empty list of results if
- * no documents matched the query.
+ * @param searchSpec Spec for setting filters, raw query etc.
+ * @return The results of performing this search. It may contain an empty list of results if no
+ * documents matched the query.
* @throws AppSearchException on IcingSearchEngine error.
*/
@NonNull
public SearchResultPage globalQuery(
- @NonNull String queryExpression,
- @NonNull SearchSpec searchSpec) throws AppSearchException {
- return doQuery(mNamespaceMap.keySet(), queryExpression, searchSpec);
+ @NonNull String queryExpression, @NonNull SearchSpec searchSpec)
+ throws AppSearchException {
+ // TODO(b/169883602): Check if the platform is querying us at a higher level. At this
+ // point, we should add all platform-surfaceable schemas assuming the querier has been
+ // verified.
+ mReadWriteLock.readLock().lock();
+ try {
+ // We use the mNamespaceMap.keySet here because it's the smaller set of valid databases
+ // that could exist.
+ return doQueryLocked(mNamespaceMapLocked.keySet(), queryExpression, searchSpec);
+ } finally {
+ mReadWriteLock.readLock().unlock();
+ }
}
- private SearchResultPage doQuery(
- @NonNull Set<String> databases, @NonNull String queryExpression,
+ @GuardedBy("mReadWriteLock")
+ private SearchResultPage doQueryLocked(
+ @NonNull Set<String> databases,
+ @NonNull String queryExpression,
@NonNull SearchSpec searchSpec)
throws AppSearchException {
- SearchSpecProto searchSpecProto =
- SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
- SearchSpecProto.Builder searchSpecBuilder = searchSpecProto.toBuilder()
- .setQuery(queryExpression);
+ SearchSpecProto searchSpecProto = SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
+ SearchSpecProto.Builder searchSpecBuilder =
+ searchSpecProto.toBuilder().setQuery(queryExpression);
ResultSpecProto resultSpec = SearchSpecToProtoConverter.toResultSpecProto(searchSpec);
ScoringSpecProto scoringSpec = SearchSpecToProtoConverter.toScoringSpecProto(searchSpec);
SearchResultProto searchResultProto;
- mReadWriteLock.readLock().lock();
- try {
- // rewriteSearchSpecForDatabases will return false if none of the databases have
- // documents, so we can return an empty SearchResult and skip sending request to Icing.
- // We use the mNamespaceMap.keySet here because it's the smaller set of valid databases
- // that could exist.
- if (!rewriteSearchSpecForDatabases(searchSpecBuilder, databases)) {
- return new SearchResultPage(Bundle.EMPTY);
- }
- searchResultProto = mIcingSearchEngine.search(
- searchSpecBuilder.build(), scoringSpec, resultSpec);
- } finally {
- mReadWriteLock.readLock().unlock();
+
+ // rewriteSearchSpecForDatabases will return false if none of the databases that the
+ // client is trying to search on exist, so we can return an empty SearchResult and skip
+ // sending request to Icing.
+ // We use the mNamespaceMap.keySet here because it's the smaller set of valid databases
+ // that could exist.
+ if (!rewriteSearchSpecForDatabasesLocked(searchSpecBuilder, databases)) {
+ return new SearchResultPage(Bundle.EMPTY);
}
+ searchResultProto =
+ mIcingSearchEngineLocked.search(searchSpecBuilder.build(), scoringSpec, resultSpec);
checkSuccess(searchResultProto.getStatus());
+
return rewriteSearchResultProto(searchResultProto);
}
@@ -385,11 +482,16 @@
* @throws AppSearchException on IcingSearchEngine error.
*/
@NonNull
- public SearchResultPage getNextPage(long nextPageToken)
- throws AppSearchException {
- SearchResultProto searchResultProto = mIcingSearchEngine.getNextPage(nextPageToken);
- checkSuccess(searchResultProto.getStatus());
- return rewriteSearchResultProto(searchResultProto);
+ public SearchResultPage getNextPage(long nextPageToken) throws AppSearchException {
+ mReadWriteLock.readLock().lock();
+ try {
+ SearchResultProto searchResultProto =
+ mIcingSearchEngineLocked.getNextPage(nextPageToken);
+ checkSuccess(searchResultProto.getStatus());
+ return rewriteSearchResultProto(searchResultProto);
+ } finally {
+ mReadWriteLock.readLock().unlock();
+ }
}
/**
@@ -398,10 +500,15 @@
* <p>This method belongs to query group.
*
* @param nextPageToken The token of pre-loaded results of previously executed query to be
- * Invalidated.
+ * Invalidated.
*/
public void invalidateNextPageToken(long nextPageToken) {
- mIcingSearchEngine.invalidateNextPageToken(nextPageToken);
+ mReadWriteLock.readLock().lock();
+ try {
+ mIcingSearchEngineLocked.invalidateNextPageToken(nextPageToken);
+ } finally {
+ mReadWriteLock.readLock().unlock();
+ }
}
/**
@@ -410,18 +517,18 @@
* <p>This method belongs to mutate group.
*
* @param databaseName The databaseName the document is in.
- * @param namespace Namespace of the document to remove.
- * @param uri URI of the document to remove.
+ * @param namespace Namespace of the document to remove.
+ * @param uri URI of the document to remove.
* @throws AppSearchException on IcingSearchEngine error.
*/
- public void remove(@NonNull String databaseName, @NonNull String namespace,
- @NonNull String uri) throws AppSearchException {
+ public void remove(@NonNull String databaseName, @NonNull String namespace, @NonNull String uri)
+ throws AppSearchException {
String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace;
DeleteResultProto deleteResultProto;
mReadWriteLock.writeLock().lock();
try {
- deleteResultProto = mIcingSearchEngine.delete(qualifiedNamespace, uri);
- checkForOptimize(/* force= */false);
+ deleteResultProto = mIcingSearchEngineLocked.delete(qualifiedNamespace, uri);
+ checkForOptimizeLocked(/* force= */ false);
} finally {
mReadWriteLock.writeLock().unlock();
}
@@ -433,39 +540,38 @@
*
* <p>This method belongs to mutate group.
*
- * @param databaseName The databaseName the document is in.
+ * @param databaseName The databaseName the document is in.
* @param queryExpression Query String to search.
- * @param searchSpec Defines what and how to remove
+ * @param searchSpec Defines what and how to remove
* @throws AppSearchException on IcingSearchEngine error.
*/
- public void removeByQuery(@NonNull String databaseName, @NonNull String queryExpression,
+ public void removeByQuery(
+ @NonNull String databaseName,
+ @NonNull String queryExpression,
@NonNull SearchSpec searchSpec)
throws AppSearchException {
-
- SearchSpecProto searchSpecProto =
- SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
- SearchSpecProto.Builder searchSpecBuilder = searchSpecProto.toBuilder()
- .setQuery(queryExpression);
+ SearchSpecProto searchSpecProto = SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
+ SearchSpecProto.Builder searchSpecBuilder =
+ searchSpecProto.toBuilder().setQuery(queryExpression);
DeleteResultProto deleteResultProto;
mReadWriteLock.writeLock().lock();
try {
// Only rewrite SearchSpec for non empty database.
// rewriteSearchSpecForNonEmptyDatabase will return false for empty database, we
// should skip sending request to Icing and return in here.
- if (!rewriteSearchSpecForDatabases(searchSpecBuilder,
- Collections.singleton(databaseName))) {
+ if (!rewriteSearchSpecForDatabasesLocked(
+ searchSpecBuilder, Collections.singleton(databaseName))) {
return;
}
- deleteResultProto = mIcingSearchEngine.deleteByQuery(
- searchSpecBuilder.build());
- checkForOptimize(/* force= */true);
+ deleteResultProto = mIcingSearchEngineLocked.deleteByQuery(searchSpecBuilder.build());
+ checkForOptimizeLocked(/* force= */ true);
} finally {
mReadWriteLock.writeLock().unlock();
}
// It seems that the caller wants to get success if the data matching the query is not in
// the DB because it was not there or was successfully deleted.
- checkCodeOneOf(deleteResultProto.getStatus(),
- StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
+ checkCodeOneOf(
+ deleteResultProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
}
/**
@@ -475,36 +581,53 @@
*
* @throws AppSearchException on IcingSearchEngine error.
*/
- @VisibleForTesting
- public void reset() throws AppSearchException {
+ private void reset() throws AppSearchException {
ResetResultProto resetResultProto;
mReadWriteLock.writeLock().lock();
try {
- resetResultProto = mIcingSearchEngine.reset();
- mOptimizeIntervalCount = 0;
- mSchemaMap.clear();
- mNamespaceMap.clear();
+ resetResultProto = mIcingSearchEngineLocked.reset();
+ mOptimizeIntervalCountLocked = 0;
+ mSchemaMapLocked.clear();
+ mNamespaceMapLocked.clear();
+
+ // Must be called after everything else since VisibilityStore may repopulate
+ // IcingSearchEngine with an initial schema.
+ mVisibilityStoreLocked.handleReset();
} finally {
mReadWriteLock.writeLock().unlock();
}
checkSuccess(resetResultProto.getStatus());
}
+ /** Wrapper around schema changes */
+ @VisibleForTesting
+ static class RewrittenSchemaResults {
+ // Any database-qualified types that used to exist in the schema, but are deleted in the
+ // new one.
+ final Set<String> mDeletedQualifiedTypes = new ArraySet<>();
+
+ // Database-qualified types that were part of the new schema.
+ final Set<String> mRewrittenQualifiedTypes = new ArraySet<>();
+ }
+
/**
* Rewrites all types mentioned in the given {@code newSchema} to prepend {@code prefix}.
* Rewritten types will be added to the {@code existingSchema}.
*
- * @param databaseName The name of the database where this schema lives.
+ * @param databaseName The name of the database where this schema lives.
* @param existingSchema A schema that may contain existing types from across all database
- * instances. Will be mutated to contain the properly rewritten schema
- * types from {@code newSchema}.
- * @param newSchema Schema with types to add to the {@code existingSchema}.
- * @return a Set contains all remaining qualified schema type names in given database.
+ * instances. Will be mutated to contain the properly rewritten schema types from {@code
+ * newSchema}.
+ * @param newSchema Schema with types to add to the {@code existingSchema}.
+ * @return a RewrittenSchemaResults contains all qualified schema type names in the given
+ * database as well as a set of schema types that were deleted from the database.
*/
@VisibleForTesting
- Set<String> rewriteSchema(@NonNull String databaseName,
+ static RewrittenSchemaResults rewriteSchema(
+ @NonNull String databaseName,
@NonNull SchemaProto.Builder existingSchema,
- @NonNull SchemaProto newSchema) throws AppSearchException {
+ @NonNull SchemaProto newSchema)
+ throws AppSearchException {
String prefix = getDatabasePrefix(databaseName);
HashMap<String, SchemaTypeConfigProto> newTypesToProto = new HashMap<>();
// Rewrite the schema type to include the typePrefix.
@@ -523,8 +646,7 @@
PropertyConfigProto.Builder propertyConfigBuilder =
typeConfigBuilder.getProperties(propertyIdx).toBuilder();
if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
- String newPropertySchemaType =
- prefix + propertyConfigBuilder.getSchemaType();
+ String newPropertySchemaType = prefix + propertyConfigBuilder.getSchemaType();
propertyConfigBuilder.setSchemaType(newPropertySchemaType);
typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
}
@@ -533,7 +655,9 @@
newTypesToProto.put(newSchemaType, typeConfigBuilder.build());
}
- Set<String> newSchemaTypesName = newTypesToProto.keySet();
+ // newTypesToProto is modified below, so we need a copy first
+ RewrittenSchemaResults rewrittenSchemaResults = new RewrittenSchemaResults();
+ rewrittenSchemaResults.mRewrittenQualifiedTypes.addAll(newTypesToProto.keySet());
// Combine the existing schema (which may have types from other databases) with this
// database's new schema. Modifies the existingSchemaBuilder.
@@ -548,26 +672,26 @@
// All types existing before but not in newSchema should be removed.
existingSchema.removeTypes(i);
--i;
+ rewrittenSchemaResults.mDeletedQualifiedTypes.add(schemaType);
}
}
// We've been removing existing types from newTypesToProto, so everything that remains is
// new.
existingSchema.addAllTypes(newTypesToProto.values());
- return newSchemaTypesName;
+ return rewrittenSchemaResults;
}
/**
- * Prepends {@code prefix} to all types and namespaces mentioned anywhere in
- * {@code documentBuilder}.
+ * Prepends {@code prefix} to all types and namespaces mentioned anywhere in {@code
+ * documentBuilder}.
*
* @param documentBuilder The document to mutate
- * @param prefix The prefix to add
+ * @param prefix The prefix to add
*/
@VisibleForTesting
- void addPrefixToDocument(
- @NonNull DocumentProto.Builder documentBuilder,
- @NonNull String prefix) {
+ static void addPrefixToDocument(
+ @NonNull DocumentProto.Builder documentBuilder, @NonNull String prefix) {
// Rewrite the type name to include/remove the prefix.
String newSchema = prefix + documentBuilder.getSchema();
documentBuilder.setSchema(newSchema);
@@ -595,27 +719,17 @@
}
/**
- * Removes any database names from types and namespaces mentioned anywhere in
- * {@code documentBuilder}.
+ * Removes any database names from types and namespaces mentioned anywhere in {@code
+ * documentBuilder}.
*
* @param documentBuilder The document to mutate
*/
@VisibleForTesting
- void removeDatabasesFromDocument(@NonNull DocumentProto.Builder documentBuilder) {
- int delimiterIndex;
- if ((delimiterIndex = documentBuilder.getSchema().indexOf(DATABASE_DELIMITER)) != -1) {
- // Rewrite the type name to remove the prefix.
- // Add 1 to include the char size of the DATABASE_DELIMITER
- String newSchema = documentBuilder.getSchema().substring(delimiterIndex + 1);
- documentBuilder.setSchema(newSchema);
- }
-
- if ((delimiterIndex = documentBuilder.getNamespace().indexOf(DATABASE_DELIMITER)) != -1) {
- // Rewrite the namespace to remove the prefix.
- // Add 1 to include the char size of the DATABASE_DELIMITER
- String newNamespace = documentBuilder.getNamespace().substring(delimiterIndex + 1);
- documentBuilder.setNamespace(newNamespace);
- }
+ static void removeDatabasesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
+ throws AppSearchException {
+ // Rewrite the type name and namespace to remove the prefix.
+ documentBuilder.setSchema(removeDatabasePrefix(documentBuilder.getSchema()));
+ documentBuilder.setNamespace(removeDatabasePrefix(documentBuilder.getNamespace()));
// Recurse into derived documents
for (int propertyIdx = 0;
@@ -639,8 +753,9 @@
/**
* Rewrites the schemaTypeFilters and namespacesFilters that exist in {@code databaseNames}.
*
- * <p>If the searchSpec has empty filter lists, all existing databases from
- * {@code databaseNames} will be added.
+ * <p>If the searchSpec has empty filter lists, all existing databases from {@code
+ * databaseNames} will be added.
+ *
* <p>This method should be only called in query methods and get the READ lock to keep thread
* safety.
*
@@ -648,11 +763,11 @@
*/
@VisibleForTesting
@GuardedBy("mReadWriteLock")
- boolean rewriteSearchSpecForDatabases(
+ boolean rewriteSearchSpecForDatabasesLocked(
@NonNull SearchSpecProto.Builder searchSpecBuilder,
@NonNull Set<String> databaseNames) {
// Create a copy since retainAll() modifies the original set.
- Set<String> existingDatabases = new HashSet<>(mNamespaceMap.keySet());
+ Set<String> existingDatabases = new ArraySet<>(mNamespaceMapLocked.keySet());
existingDatabases.retainAll(databaseNames);
if (existingDatabases.isEmpty()) {
@@ -669,29 +784,29 @@
// Rewrite filters to include a database prefix.
for (String databaseName : existingDatabases) {
- Set<String> existingSchemaTypes = mSchemaMap.get(databaseName);
+ Set<String> existingSchemaTypes = mSchemaMapLocked.get(databaseName);
+ String databaseNamePrefix = getDatabasePrefix(databaseName);
if (schemaTypeFilters.isEmpty()) {
// Include all schema types
searchSpecBuilder.addAllSchemaTypeFilters(existingSchemaTypes);
} else {
// Qualify the given schema types
- for (String schemaType : schemaTypeFilters) {
- String qualifiedType = getDatabasePrefix(databaseName) + schemaType;
+ for (int i = 0; i < schemaTypeFilters.size(); i++) {
+ String qualifiedType = databaseNamePrefix + schemaTypeFilters.get(i);
if (existingSchemaTypes.contains(qualifiedType)) {
searchSpecBuilder.addSchemaTypeFilters(qualifiedType);
}
-
}
}
- Set<String> existingNamespaces = mNamespaceMap.get(databaseName);
+ Set<String> existingNamespaces = mNamespaceMapLocked.get(databaseName);
if (namespaceFilters.isEmpty()) {
// Include all namespaces
searchSpecBuilder.addAllNamespaceFilters(existingNamespaces);
} else {
// Qualify the given namespaces.
- for (String namespace : namespaceFilters) {
- String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace;
+ for (int i = 0; i < namespaceFilters.size(); i++) {
+ String qualifiedNamespace = databaseNamePrefix + namespaceFilters.get(i);
if (existingNamespaces.contains(qualifiedNamespace)) {
searchSpecBuilder.addNamespaceFilters(qualifiedNamespace);
}
@@ -703,35 +818,71 @@
}
@VisibleForTesting
- SchemaProto getSchemaProto() throws AppSearchException {
- GetSchemaResultProto schemaProto = mIcingSearchEngine.getSchema();
+ @GuardedBy("mReadWriteLock")
+ SchemaProto getSchemaProtoLocked() throws AppSearchException {
+ GetSchemaResultProto schemaProto = mIcingSearchEngineLocked.getSchema();
// TODO(b/161935693) check GetSchemaResultProto is success or not. Call reset() if it's not.
// TODO(b/161935693) only allow GetSchemaResultProto NOT_FOUND on first run
checkCodeOneOf(schemaProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
return schemaProto.getSchema();
}
+ /** Returns true if {@code databaseName} has a {@code schemaType} */
+ @GuardedBy("mReadWriteLock")
+ boolean hasSchemaTypeLocked(@NonNull String databaseName, @NonNull String schemaType) {
+ Preconditions.checkNotNull(databaseName);
+ Preconditions.checkNotNull(schemaType);
+
+ Set<String> schemaTypes = mSchemaMapLocked.get(databaseName);
+ if (schemaTypes == null) {
+ return false;
+ }
+
+ return schemaTypes.contains(getDatabasePrefix(databaseName) + schemaType);
+ }
+
+ /** Returns a set of all databases AppSearchImpl knows about. */
+ @GuardedBy("mReadWriteLock")
@NonNull
- private String getDatabasePrefix(@NonNull String databaseName) {
+ Set<String> getDatabasesLocked() {
+ return mSchemaMapLocked.keySet();
+ }
+
+ @NonNull
+ private static String getDatabasePrefix(@NonNull String databaseName) {
// TODO(b/170370381): Reconsider the way we separate database names for security reasons.
return databaseName + DATABASE_DELIMITER;
}
@NonNull
- private String getDatabaseName(@NonNull String prefixedValue) throws AppSearchException {
+ private static String removeDatabasePrefix(@NonNull String prefixedString)
+ throws AppSearchException {
+ int delimiterIndex;
+ if ((delimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER)) != -1) {
+ // Add 1 to include the char size of the DATABASE_DELIMITER
+ return prefixedString.substring(delimiterIndex + 1);
+ }
+ throw new AppSearchException(
+ AppSearchResult.RESULT_UNKNOWN_ERROR,
+ "The prefixed value doesn't contains a valid database name.");
+ }
+
+ @NonNull
+ private static String getDatabaseName(@NonNull String prefixedValue) throws AppSearchException {
int delimiterIndex = prefixedValue.indexOf(DATABASE_DELIMITER);
if (delimiterIndex == -1) {
- throw new AppSearchException(AppSearchResult.RESULT_UNKNOWN_ERROR,
+ throw new AppSearchException(
+ AppSearchResult.RESULT_UNKNOWN_ERROR,
"The databaseName prefixed value doesn't contains a valid database name.");
}
return prefixedValue.substring(0, delimiterIndex);
}
- @GuardedBy("mReadWriteLock")
- private void addToMap(Map<String, Set<String>> map, String databaseName, String prefixedValue) {
+ private static void addToMap(
+ Map<String, Set<String>> map, String databaseName, String prefixedValue) {
Set<String> values = map.get(databaseName);
if (values == null) {
- values = new HashSet<>();
+ values = new ArraySet<>();
map.put(databaseName, values);
}
values.add(prefixedValue);
@@ -742,15 +893,15 @@
*
* @throws AppSearchException on error codes.
*/
- private void checkSuccess(StatusProto statusProto) throws AppSearchException {
+ private static void checkSuccess(StatusProto statusProto) throws AppSearchException {
checkCodeOneOf(statusProto, StatusProto.Code.OK);
}
/**
- * Checks the given status code is one of the provided codes, and throws an
- * {@link AppSearchException} if it is not.
+ * Checks the given status code is one of the provided codes, and throws an {@link
+ * AppSearchException} if it is not.
*/
- private void checkCodeOneOf(StatusProto statusProto, StatusProto.Code... codes)
+ private static void checkCodeOneOf(StatusProto statusProto, StatusProto.Code... codes)
throws AppSearchException {
for (int i = 0; i < codes.length; i++) {
if (codes[i] == statusProto.getCode()) {
@@ -774,27 +925,28 @@
*
* <p>This method should be only called in mutate methods and get the WRITE lock to keep thread
* safety.
- * <p>{@link IcingSearchEngine#optimize()} should be called only if
- * {@link GetOptimizeInfoResultProto} shows there is enough resources could be released.
- * <p>{@link IcingSearchEngine#getOptimizeInfo()} should be called once per
- * {@link #CHECK_OPTIMIZE_INTERVAL} of remove executions.
+ *
+ * <p>{@link IcingSearchEngine#optimize()} should be called only if {@link
+ * GetOptimizeInfoResultProto} shows there is enough resources could be released.
+ *
+ * <p>{@link IcingSearchEngine#getOptimizeInfo()} should be called once per {@link
+ * #CHECK_OPTIMIZE_INTERVAL} of remove executions.
*
* @param force whether we should directly call {@link IcingSearchEngine#getOptimizeInfo()}.
*/
@GuardedBy("mReadWriteLock")
- private void checkForOptimize(boolean force) throws AppSearchException {
- ++mOptimizeIntervalCount;
- if (force || mOptimizeIntervalCount >= CHECK_OPTIMIZE_INTERVAL) {
- mOptimizeIntervalCount = 0;
- GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResult();
+ private void checkForOptimizeLocked(boolean force) throws AppSearchException {
+ ++mOptimizeIntervalCountLocked;
+ if (force || mOptimizeIntervalCountLocked >= CHECK_OPTIMIZE_INTERVAL) {
+ mOptimizeIntervalCountLocked = 0;
+ GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResultLocked();
checkSuccess(optimizeInfo.getStatus());
// Second threshold, decide when to call optimize().
if (optimizeInfo.getOptimizableDocs() >= OPTIMIZE_THRESHOLD_DOC_COUNT
- || optimizeInfo.getEstimatedOptimizableBytes()
- >= OPTIMIZE_THRESHOLD_BYTES) {
+ || optimizeInfo.getEstimatedOptimizableBytes() >= OPTIMIZE_THRESHOLD_BYTES) {
// TODO(b/155939114): call optimize in the same thread will slow down api calls
// significantly. Move this call to background.
- OptimizeResultProto optimizeResultProto = mIcingSearchEngine.optimize();
+ OptimizeResultProto optimizeResultProto = mIcingSearchEngineLocked.optimize();
checkSuccess(optimizeResultProto.getStatus());
}
// TODO(b/147699081): Return OptimizeResultProto & log lost data detail once we add
@@ -804,8 +956,8 @@
}
/** Remove the rewritten schema types from any result documents. */
- private SearchResultPage rewriteSearchResultProto(
- @NonNull SearchResultProto searchResultProto) {
+ private static SearchResultPage rewriteSearchResultProto(
+ @NonNull SearchResultProto searchResultProto) throws AppSearchException {
SearchResultProto.Builder resultsBuilder = searchResultProto.toBuilder();
for (int i = 0; i < searchResultProto.getResultsCount(); i++) {
if (searchResultProto.getResults(i).hasDocument()) {
@@ -820,40 +972,48 @@
return SearchResultToProtoConverter.convertToSearchResultPage(resultsBuilder);
}
+ @GuardedBy("mReadWriteLock")
@VisibleForTesting
- GetOptimizeInfoResultProto getOptimizeInfoResult() {
- return mIcingSearchEngine.getOptimizeInfo();
+ GetOptimizeInfoResultProto getOptimizeInfoResultLocked() {
+ return mIcingSearchEngineLocked.getOptimizeInfo();
+ }
+
+ @GuardedBy("mReadWriteLock")
+ @VisibleForTesting
+ VisibilityStore getVisibilityStoreLocked() {
+ return mVisibilityStoreLocked;
}
/**
- * Converts an erroneous status code to an AppSearchException. Callers should ensure that
- * the status code is not OK or WARNING_DATA_LOSS.
+ * Converts an erroneous status code to an AppSearchException. Callers should ensure that the
+ * status code is not OK or WARNING_DATA_LOSS.
*
* @param statusProto StatusProto with error code and message to translate into
- * AppSearchException.
+ * AppSearchException.
* @return AppSearchException with the parallel error code.
*/
- private AppSearchException statusProtoToAppSearchException(StatusProto statusProto) {
+ private static AppSearchException statusProtoToAppSearchException(StatusProto statusProto) {
switch (statusProto.getCode()) {
case INVALID_ARGUMENT:
- return new AppSearchException(AppSearchResult.RESULT_INVALID_ARGUMENT,
- statusProto.getMessage());
+ return new AppSearchException(
+ AppSearchResult.RESULT_INVALID_ARGUMENT, statusProto.getMessage());
case NOT_FOUND:
- return new AppSearchException(AppSearchResult.RESULT_NOT_FOUND,
- statusProto.getMessage());
+ return new AppSearchException(
+ AppSearchResult.RESULT_NOT_FOUND, statusProto.getMessage());
case FAILED_PRECONDITION:
// Fallthrough
case ABORTED:
// Fallthrough
case INTERNAL:
- return new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR,
- statusProto.getMessage());
+ return new AppSearchException(
+ AppSearchResult.RESULT_INTERNAL_ERROR, statusProto.getMessage());
case OUT_OF_SPACE:
- return new AppSearchException(AppSearchResult.RESULT_OUT_OF_SPACE,
- statusProto.getMessage());
+ return new AppSearchException(
+ AppSearchResult.RESULT_OUT_OF_SPACE, statusProto.getMessage());
default:
// Some unknown/unsupported error
- return new AppSearchException(AppSearchResult.RESULT_UNKNOWN_ERROR,
+ return new AppSearchException(
+ AppSearchResult.RESULT_UNKNOWN_ERROR,
"Unknown IcingSearchEngine status code: " + statusProto.getCode());
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java
new file mode 100644
index 0000000..4722822
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.external.localstorage;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages any visibility settings for all the databases that AppSearchImpl knows about. Persists
+ * the visibility settings and reloads them on initialization.
+ *
+ * <p>The VisibilityStore creates a document for each database. This document holds the visibility
+ * settings that apply to that database. The VisibilityStore also creates a schema for these
+ * documents and has its own database so that its data doesn't interfere with any clients' data. It
+ * persists the document and schema through AppSearchImpl.
+ *
+ * <p>These visibility settings are used to ensure AppSearch queries respect the clients' settings
+ * on who their data is visible to.
+ *
+ * <p>This class doesn't handle any locking itself. Its callers should handle the locking at a
+ * higher level.
+ *
+ * <p>NOTE: This class holds an instance of AppSearchImpl and AppSearchImpl holds an instance of
+ * this class. Take care to not cause any circular dependencies.
+ */
+class VisibilityStore {
+ // Schema type for documents that hold AppSearch's metadata, e.g. visibility settings
+ @VisibleForTesting static final String SCHEMA_TYPE = "Visibility";
+ // Property that holds the list of platform-hidden schemas, as part of the visibility
+ // settings.
+ @VisibleForTesting static final String PLATFORM_HIDDEN_PROPERTY = "platformHidden";
+ // Database name to prefix all visibility schemas and documents with. Special-cased to
+ // minimize the chance of collision with a client-supplied database.
+ @VisibleForTesting static final String DATABASE_NAME = "$$__AppSearch__Database";
+ // Namespace of documents that contain visibility settings
+ private static final String NAMESPACE = "namespace";
+ private final AppSearchImpl mAppSearchImpl;
+
+ // The map contains schemas that are platform-hidden for each database. All schemas in the map
+ // have a database name prefix.
+ private final Map<String, Set<String>> mPlatformHiddenMap = new ArrayMap<>();
+
+ /**
+ * Creates an uninitialized VisibilityStore object. Callers must also call {@link #initialize()}
+ * before using the object.
+ *
+ * @param appSearchImpl AppSearchImpl instance
+ */
+ VisibilityStore(@NonNull AppSearchImpl appSearchImpl) {
+ mAppSearchImpl = appSearchImpl;
+ }
+
+ /**
+ * Initializes schemas and member variables to track visibility settings.
+ *
+ * <p>This is kept separate from the constructor because this will call methods on
+ * AppSearchImpl. Some may even then recursively call back into VisibilityStore (for example,
+ * {@link AppSearchImpl#setSchema} will call {@link #updateSchemas}. We need to have both
+ * AppSearchImpl and VisibilityStore fully initialized for this call flow to work.
+ *
+ * @throws AppSearchException AppSearchException on AppSearchImpl error.
+ */
+ public void initialize() throws AppSearchException {
+ if (!mAppSearchImpl.hasSchemaTypeLocked(DATABASE_NAME, SCHEMA_TYPE)) {
+ // Schema type doesn't exist yet. Add it.
+ mAppSearchImpl.setSchema(
+ DATABASE_NAME,
+ Collections.singleton(
+ new AppSearchSchema.Builder(SCHEMA_TYPE)
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder(
+ PLATFORM_HIDDEN_PROPERTY)
+ .setDataType(
+ AppSearchSchema.PropertyConfig
+ .DATA_TYPE_STRING)
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_REPEATED)
+ .build())
+ .build()),
+ /*forceOverride=*/ false);
+ }
+
+ // Populate visibility settings map
+ for (String database : mAppSearchImpl.getDatabasesLocked()) {
+ if (database.equals(DATABASE_NAME)) {
+ // Our own database. Skip
+ continue;
+ }
+
+ try {
+ // Note: We use the other clients' database names as uris
+ GenericDocument document =
+ mAppSearchImpl.getDocument(DATABASE_NAME, NAMESPACE, /*uri=*/ database);
+
+ String[] schemas = document.getPropertyStringArray(PLATFORM_HIDDEN_PROPERTY);
+ mPlatformHiddenMap.put(database, new ArraySet<>(Arrays.asList(schemas)));
+ } catch (AppSearchException e) {
+ if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
+ // TODO(b/172068212): This indicates some desync error. We were expecting a
+ // document, but didn't find one. Should probably reset AppSearch instead of
+ // ignoring it.
+ continue;
+ }
+ // Otherwise, this is some other error we should pass up.
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Update visibility settings for the {@code databaseName}.
+ *
+ * @param schemasToRemove Database-prefixed schemas that should be removed
+ */
+ public void updateSchemas(@NonNull String databaseName, @NonNull Set<String> schemasToRemove)
+ throws AppSearchException {
+ Preconditions.checkNotNull(databaseName);
+ Preconditions.checkNotNull(schemasToRemove);
+
+ GenericDocument visibilityDocument;
+ try {
+ visibilityDocument =
+ mAppSearchImpl.getDocument(DATABASE_NAME, NAMESPACE, /*uri=*/ databaseName);
+ } catch (AppSearchException e) {
+ if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
+ // This might be the first time we're seeing visibility changes for a database.
+ // Create a new visibility document.
+ mAppSearchImpl.putDocument(
+ DATABASE_NAME,
+ new GenericDocument.Builder(/*uri=*/ databaseName, SCHEMA_TYPE)
+ .setNamespace(NAMESPACE)
+ .build());
+
+ // Since we know there was nothing that existed before, we don't need to remove
+ // anything either. Return early.
+ return;
+ }
+ // Otherwise, this is some real error we should pass up.
+ throw e;
+ }
+
+ String[] hiddenSchemas =
+ visibilityDocument.getPropertyStringArray(PLATFORM_HIDDEN_PROPERTY);
+ if (hiddenSchemas == null) {
+ // Nothing to remove.
+ return;
+ }
+
+ // Create a new set so we can remove from it.
+ Set<String> remainingSchemas = new ArraySet<>(Arrays.asList(hiddenSchemas));
+ boolean changed = remainingSchemas.removeAll(schemasToRemove);
+ if (!changed) {
+ // Nothing was actually removed. Can return early.
+ return;
+ }
+
+ // Update our persisted document
+ // TODO(b/171882200): Switch to a .toBuilder API when it's available.
+ GenericDocument.Builder newVisibilityDocument =
+ new GenericDocument.Builder(/*uri=*/ databaseName, SCHEMA_TYPE)
+ .setNamespace(NAMESPACE);
+ if (!remainingSchemas.isEmpty()) {
+ newVisibilityDocument.setPropertyString(
+ PLATFORM_HIDDEN_PROPERTY, remainingSchemas.toArray(new String[0]));
+ }
+ mAppSearchImpl.putDocument(DATABASE_NAME, newVisibilityDocument.build());
+
+ // Update derived data structures
+ mPlatformHiddenMap.put(databaseName, remainingSchemas);
+ }
+
+ /**
+ * Sets visibility settings for {@code databaseName}. Any previous visibility settings will be
+ * overwritten.
+ *
+ * @param databaseName Database name that owns the {@code platformHiddenSchemas}.
+ * @param platformHiddenSchemas Set of database-qualified schemas that should be hidden from the
+ * platform.
+ * @throws AppSearchException on AppSearchImpl error.
+ */
+ public void setVisibility(
+ @NonNull String databaseName, @NonNull Set<String> platformHiddenSchemas)
+ throws AppSearchException {
+ Preconditions.checkNotNull(databaseName);
+ Preconditions.checkNotNull(platformHiddenSchemas);
+
+ // Persist the document
+ GenericDocument.Builder visibilityDocument =
+ new GenericDocument.Builder(/*uri=*/ databaseName, SCHEMA_TYPE)
+ .setNamespace(NAMESPACE);
+ if (!platformHiddenSchemas.isEmpty()) {
+ visibilityDocument.setPropertyString(
+ PLATFORM_HIDDEN_PROPERTY, platformHiddenSchemas.toArray(new String[0]));
+ }
+ mAppSearchImpl.putDocument(DATABASE_NAME, visibilityDocument.build());
+
+ // Update derived data structures.
+ mPlatformHiddenMap.put(databaseName, platformHiddenSchemas);
+ }
+
+ /**
+ * Returns the set of database-qualified schemas in {@code databaseName} that are hidden from
+ * the platform.
+ *
+ * @param databaseName Database name to retrieve schemas for
+ * @return Set of database-qualified schemas that are hidden from the platform. Empty set if
+ * none exist.
+ */
+ @NonNull
+ public Set<String> getPlatformHiddenSchemas(@NonNull String databaseName) {
+ Preconditions.checkNotNull(databaseName);
+ Set<String> platformHiddenSchemas = mPlatformHiddenMap.get(databaseName);
+ if (platformHiddenSchemas == null) {
+ return Collections.emptySet();
+ }
+ return platformHiddenSchemas;
+ }
+
+ /**
+ * Handles an {@link AppSearchImpl#reset()} by clearing any cached state and resetting to a
+ * first-initialized state.
+ *
+ * @throws AppSearchException on AppSearchImpl error.
+ */
+ public void handleReset() throws AppSearchException {
+ mPlatformHiddenMap.clear();
+ initialize();
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
index 60684f0..8f4e7ff 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
@@ -17,8 +17,8 @@
package com.android.server.appsearch.external.localstorage.converter;
import android.annotation.NonNull;
-
import android.app.appsearch.GenericDocument;
+
import com.android.internal.util.Preconditions;
import com.google.android.icing.proto.DocumentProto;
@@ -30,9 +30,9 @@
/**
* Translates a {@link GenericDocument} into a {@link DocumentProto}.
+ *
* @hide
*/
-
public final class GenericDocumentToProtoConverter {
private GenericDocumentToProtoConverter() {}
@@ -42,7 +42,8 @@
public static DocumentProto convert(@NonNull GenericDocument document) {
Preconditions.checkNotNull(document);
DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
- mProtoBuilder.setUri(document.getUri())
+ mProtoBuilder
+ .setUri(document.getUri())
.setSchema(document.getSchemaType())
.setNamespace(document.getNamespace())
.setScore(document.getScore())
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
index 403711f..642c2a7 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
@@ -17,34 +17,34 @@
package com.android.server.appsearch.external.localstorage.converter;
import android.annotation.NonNull;
-
import android.app.appsearch.AppSearchSchema;
+
import com.android.internal.util.Preconditions;
-import com.google.android.icing.proto.IndexingConfig;
import com.google.android.icing.proto.PropertyConfigProto;
import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.StringIndexingConfig;
import com.google.android.icing.proto.TermMatchType;
import java.util.List;
/**
* Translates an {@link AppSearchSchema} into a {@link SchemaTypeConfigProto}.
+ *
* @hide
*/
-
public final class SchemaToProtoConverter {
private SchemaToProtoConverter() {}
/**
- * Converts an {@link android.app.appsearch.AppSearchSchema} into a
- * {@link SchemaTypeConfigProto}.
+ * Converts an {@link android.app.appsearch.AppSearchSchema} into a {@link
+ * SchemaTypeConfigProto}.
*/
@NonNull
public static SchemaTypeConfigProto convert(@NonNull AppSearchSchema schema) {
Preconditions.checkNotNull(schema);
SchemaTypeConfigProto.Builder protoBuilder =
- SchemaTypeConfigProto.newBuilder().setSchemaType(schema.getSchemaTypeName());
+ SchemaTypeConfigProto.newBuilder().setSchemaType(schema.getSchemaType());
List<AppSearchSchema.PropertyConfig> properties = schema.getProperties();
for (int i = 0; i < properties.size(); i++) {
PropertyConfigProto propertyProto = convertProperty(properties.get(i));
@@ -57,9 +57,9 @@
private static PropertyConfigProto convertProperty(
@NonNull AppSearchSchema.PropertyConfig property) {
Preconditions.checkNotNull(property);
- PropertyConfigProto.Builder propertyConfigProto = PropertyConfigProto.newBuilder()
- .setPropertyName(property.getName());
- IndexingConfig.Builder indexingConfig = IndexingConfig.newBuilder();
+ PropertyConfigProto.Builder propertyConfigProto =
+ PropertyConfigProto.newBuilder().setPropertyName(property.getName());
+ StringIndexingConfig.Builder indexingConfig = StringIndexingConfig.newBuilder();
// Set dataType
@AppSearchSchema.PropertyConfig.DataType int dataType = property.getDataType();
@@ -104,17 +104,17 @@
indexingConfig.setTermMatchType(termMatchTypeProto);
// Set tokenizerType
- @AppSearchSchema.PropertyConfig.TokenizerType int tokenizerType =
- property.getTokenizerType();
- IndexingConfig.TokenizerType.Code tokenizerTypeProto =
- IndexingConfig.TokenizerType.Code.forNumber(tokenizerType);
+ @AppSearchSchema.PropertyConfig.TokenizerType
+ int tokenizerType = property.getTokenizerType();
+ StringIndexingConfig.TokenizerType.Code tokenizerTypeProto =
+ StringIndexingConfig.TokenizerType.Code.forNumber(tokenizerType);
if (tokenizerTypeProto == null) {
throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType);
}
indexingConfig.setTokenizerType(tokenizerTypeProto);
// Build!
- propertyConfigProto.setIndexingConfig(indexingConfig);
+ propertyConfigProto.setStringIndexingConfig(indexingConfig);
return propertyConfigProto.build();
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
index 4310b42..b91a393 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
@@ -16,13 +16,11 @@
package com.android.server.appsearch.external.localstorage.converter;
-import android.os.Bundle;
-
import android.annotation.NonNull;
-
import android.app.appsearch.GenericDocument;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultPage;
+import android.os.Bundle;
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchResultProtoOrBuilder;
@@ -36,11 +34,9 @@
*
* @hide
*/
-
public class SearchResultToProtoConverter {
private SearchResultToProtoConverter() {}
-
/** Translate a {@link SearchResultProto} into {@link SearchResultPage}. */
@NonNull
public static SearchResultPage convertToSearchResultPage(
@@ -68,8 +64,9 @@
for (int i = 0; i < proto.getSnippet().getEntriesCount(); i++) {
SnippetProto.EntryProto entry = proto.getSnippet().getEntries(i);
for (int j = 0; j < entry.getSnippetMatchesCount(); j++) {
- Bundle matchInfoBundle = convertToMatchInfoBundle(
- entry.getSnippetMatches(j), entry.getPropertyName());
+ Bundle matchInfoBundle =
+ convertToMatchInfoBundle(
+ entry.getSnippetMatches(j), entry.getPropertyName());
matchList.add(matchInfoBundle);
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
index 14822dc..814ee4f 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
@@ -17,8 +17,8 @@
package com.android.server.appsearch.external.localstorage.converter;
import android.annotation.NonNull;
-
import android.app.appsearch.SearchSpec;
+
import com.android.internal.util.Preconditions;
import com.google.android.icing.proto.ResultSpecProto;
@@ -28,9 +28,9 @@
/**
* Translates a {@link SearchSpec} into icing search protos.
+ *
* @hide
*/
-
public final class SearchSpecToProtoConverter {
private SearchSpecToProtoConverter() {}
@@ -38,9 +38,10 @@
@NonNull
public static SearchSpecProto toSearchSpecProto(@NonNull SearchSpec spec) {
Preconditions.checkNotNull(spec);
- SearchSpecProto.Builder protoBuilder = SearchSpecProto.newBuilder()
- .addAllSchemaTypeFilters(spec.getSchemas())
- .addAllNamespaceFilters(spec.getNamespaces());
+ SearchSpecProto.Builder protoBuilder =
+ SearchSpecProto.newBuilder()
+ .addAllSchemaTypeFilters(spec.getSchemaTypes())
+ .addAllNamespaceFilters(spec.getNamespaces());
@SearchSpec.TermMatch int termMatchCode = spec.getTermMatch();
TermMatchType.Code termMatchCodeProto = TermMatchType.Code.forNumber(termMatchCode);
@@ -57,7 +58,7 @@
public static ResultSpecProto toResultSpecProto(@NonNull SearchSpec spec) {
Preconditions.checkNotNull(spec);
return ResultSpecProto.newBuilder()
- .setNumPerPage(spec.getNumPerPage())
+ .setNumPerPage(spec.getResultCountPerPage())
.setSnippetSpec(
ResultSpecProto.SnippetSpecProto.newBuilder()
.setNumToSnippet(spec.getSnippetCount())
@@ -84,8 +85,8 @@
ScoringSpecProto.RankingStrategy.Code rankingStrategyCodeProto =
ScoringSpecProto.RankingStrategy.Code.forNumber(rankingStrategyCode);
if (rankingStrategyCodeProto == null) {
- throw new IllegalArgumentException("Invalid result ranking strategy: "
- + rankingStrategyCode);
+ throw new IllegalArgumentException(
+ "Invalid result ranking strategy: " + rankingStrategyCode);
}
protoBuilder.setRankBy(rankingStrategyCodeProto);
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
new file mode 100644
index 0000000..a2bf0d5
--- /dev/null
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -0,0 +1 @@
+I2decd83fab4c4d58fe38c9970f804046479c942c
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index c2d530d..2c8a558 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1506,56 +1506,16 @@
* @return The job object to hand to the JobScheduler. This object is immutable.
*/
public JobInfo build() {
- // Check that network estimates require network type
- if ((mNetworkDownloadBytes > 0 || mNetworkUploadBytes > 0) && mNetworkRequest == null) {
- throw new IllegalArgumentException(
- "Can't provide estimated network usage without requiring a network");
- }
- // We can't serialize network specifiers
- if (mIsPersisted && mNetworkRequest != null
- && mNetworkRequest.networkCapabilities.getNetworkSpecifier() != null) {
- throw new IllegalArgumentException(
- "Network specifiers aren't supported for persistent jobs");
- }
- // Check that a deadline was not set on a periodic job.
- if (mIsPeriodic) {
- if (mMaxExecutionDelayMillis != 0L) {
- throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
- "periodic job.");
- }
- if (mMinLatencyMillis != 0L) {
- throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
- "periodic job");
- }
- if (mTriggerContentUris != null) {
- throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
- "periodic job");
- }
- }
- if (mIsPersisted) {
- if (mTriggerContentUris != null) {
- throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
- "persisted job");
- }
- if (!mTransientExtras.isEmpty()) {
- throw new IllegalArgumentException("Can't call setTransientExtras() on a " +
- "persisted job");
- }
- if (mClipData != null) {
- throw new IllegalArgumentException("Can't call setClipData() on a " +
- "persisted job");
- }
- }
- if ((mFlags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && mHasEarlyConstraint) {
- throw new IllegalArgumentException("An important while foreground job cannot "
- + "have a time delay");
- }
+ // This check doesn't need to be inside enforceValidity. It's an unnecessary legacy
+ // check that would ideally be phased out instead.
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
throw new IllegalArgumentException("An idle mode job will not respect any" +
" back-off policy, so calling setBackoffCriteria with" +
" setRequiresDeviceIdle is an error.");
}
- return new JobInfo(this);
+ JobInfo jobInfo = new JobInfo(this);
+ jobInfo.enforceValidity();
+ return jobInfo;
}
/**
@@ -1570,6 +1530,59 @@
}
/**
+ * @hide
+ */
+ public void enforceValidity() {
+ // Check that network estimates require network type
+ if ((networkDownloadBytes > 0 || networkUploadBytes > 0) && networkRequest == null) {
+ throw new IllegalArgumentException(
+ "Can't provide estimated network usage without requiring a network");
+ }
+
+ // Check that a deadline was not set on a periodic job.
+ if (isPeriodic) {
+ if (maxExecutionDelayMillis != 0L) {
+ throw new IllegalArgumentException(
+ "Can't call setOverrideDeadline() on a periodic job.");
+ }
+ if (minLatencyMillis != 0L) {
+ throw new IllegalArgumentException(
+ "Can't call setMinimumLatency() on a periodic job");
+ }
+ if (triggerContentUris != null) {
+ throw new IllegalArgumentException(
+ "Can't call addTriggerContentUri() on a periodic job");
+ }
+ }
+
+ if (isPersisted) {
+ // We can't serialize network specifiers
+ if (networkRequest != null
+ && networkRequest.networkCapabilities.getNetworkSpecifier() != null) {
+ throw new IllegalArgumentException(
+ "Network specifiers aren't supported for persistent jobs");
+ }
+ if (triggerContentUris != null) {
+ throw new IllegalArgumentException(
+ "Can't call addTriggerContentUri() on a persisted job");
+ }
+ if (!transientExtras.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Can't call setTransientExtras() on a persisted job");
+ }
+ if (clipData != null) {
+ throw new IllegalArgumentException(
+ "Can't call setClipData() on a persisted job");
+ }
+ }
+
+ if ((flags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && hasEarlyConstraint) {
+ throw new IllegalArgumentException(
+ "An important while foreground job cannot have a time delay");
+ }
+ }
+
+ /**
* Convert a priority integer into a human readable string for debugging.
* @hide
*/
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index ab94da8..3d43d20 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -17,7 +17,6 @@
package android.app.job;
import android.app.Service;
-import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
@@ -26,8 +25,6 @@
import android.os.RemoteException;
import android.util.Log;
-import com.android.internal.annotations.GuardedBy;
-
import java.lang.ref.WeakReference;
/**
@@ -206,8 +203,7 @@
/**
* Call in to engine to report that a job has finished executing. See
- * {@link JobService#jobFinished(JobParameters, boolean)} JobService.jobFinished} for more
- * information.
+ * {@link JobService#jobFinished(JobParameters, boolean)} for more information.
*/
public void jobFinished(JobParameters params, boolean needsReschedule) {
if (params == null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 9835e18..9989252 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -884,13 +884,12 @@
private static final String KEY_MAX_IDLE_TIMEOUT = "max_idle_to";
private static final String KEY_IDLE_FACTOR = "idle_factor";
private static final String KEY_MIN_TIME_TO_ALARM = "min_time_to_alarm";
- // TODO(166121524): update flag names
- private static final String KEY_MAX_TEMP_APP_WHITELIST_DURATION =
- "max_temp_app_whitelist_duration";
- private static final String KEY_MMS_TEMP_APP_WHITELIST_DURATION =
- "mms_temp_app_whitelist_duration";
- private static final String KEY_SMS_TEMP_APP_WHITELIST_DURATION =
- "sms_temp_app_whitelist_duration";
+ private static final String KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS =
+ "max_temp_app_allowlist_duration_ms";
+ private static final String KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS =
+ "mms_temp_app_allowlist_duration_ms";
+ private static final String KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS =
+ "sms_temp_app_allowlist_duration_ms";
private static final String KEY_NOTIFICATION_ALLOWLIST_DURATION_MS =
"notification_allowlist_duration_ms";
/**
@@ -949,9 +948,9 @@
private static final float DEFAULT_IDLE_FACTOR = 2f;
private static final long DEFAULT_MIN_TIME_TO_ALARM =
!COMPRESS_TIME ? 30 * 60 * 1000L : 6 * 60 * 1000L;
- private static final long DEFAULT_MAX_TEMP_APP_WHITELIST_DURATION = 5 * 60 * 1000L;
- private static final long DEFAULT_MMS_TEMP_APP_WHITELIST_DURATION = 60 * 1000L;
- private static final long DEFAULT_SMS_TEMP_APP_WHITELIST_DURATION = 20 * 1000L;
+ private static final long DEFAULT_MAX_TEMP_APP_ALLOWLIST_DURATION_MS = 5 * 60 * 1000L;
+ private static final long DEFAULT_MMS_TEMP_APP_ALLOWLIST_DURATION_MS = 60 * 1000L;
+ private static final long DEFAULT_SMS_TEMP_APP_ALLOWLIST_DURATION_MS = 20 * 1000L;
private static final long DEFAULT_NOTIFICATION_ALLOWLIST_DURATION_MS = 30 * 1000L;
private static final boolean DEFAULT_WAIT_FOR_UNLOCK = true;
private static final float DEFAULT_PRE_IDLE_FACTOR_LONG = 1.67f;
@@ -1142,21 +1141,21 @@
* Max amount of time to temporarily whitelist an app when it receives a high priority
* tickle.
*
- * @see #KEY_MAX_TEMP_APP_WHITELIST_DURATION
+ * @see #KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS
*/
- public long MAX_TEMP_APP_WHITELIST_DURATION = DEFAULT_MAX_TEMP_APP_WHITELIST_DURATION;
+ public long MAX_TEMP_APP_ALLOWLIST_DURATION_MS = DEFAULT_MAX_TEMP_APP_ALLOWLIST_DURATION_MS;
/**
* Amount of time we would like to whitelist an app that is receiving an MMS.
- * @see #KEY_MMS_TEMP_APP_WHITELIST_DURATION
+ * @see #KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS
*/
- public long MMS_TEMP_APP_WHITELIST_DURATION = DEFAULT_MMS_TEMP_APP_WHITELIST_DURATION;
+ public long MMS_TEMP_APP_ALLOWLIST_DURATION_MS = DEFAULT_MMS_TEMP_APP_ALLOWLIST_DURATION_MS;
/**
* Amount of time we would like to whitelist an app that is receiving an SMS.
- * @see #KEY_SMS_TEMP_APP_WHITELIST_DURATION
+ * @see #KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS
*/
- public long SMS_TEMP_APP_WHITELIST_DURATION = DEFAULT_SMS_TEMP_APP_WHITELIST_DURATION;
+ public long SMS_TEMP_APP_ALLOWLIST_DURATION_MS = DEFAULT_SMS_TEMP_APP_ALLOWLIST_DURATION_MS;
/**
* Amount of time we would like to whitelist an app that is handling a
@@ -1303,20 +1302,20 @@
MIN_TIME_TO_ALARM = properties.getLong(
KEY_MIN_TIME_TO_ALARM, DEFAULT_MIN_TIME_TO_ALARM);
break;
- case KEY_MAX_TEMP_APP_WHITELIST_DURATION:
- MAX_TEMP_APP_WHITELIST_DURATION = properties.getLong(
- KEY_MAX_TEMP_APP_WHITELIST_DURATION,
- DEFAULT_MAX_TEMP_APP_WHITELIST_DURATION);
+ case KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS:
+ MAX_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
+ KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS,
+ DEFAULT_MAX_TEMP_APP_ALLOWLIST_DURATION_MS);
break;
- case KEY_MMS_TEMP_APP_WHITELIST_DURATION:
- MMS_TEMP_APP_WHITELIST_DURATION = properties.getLong(
- KEY_MMS_TEMP_APP_WHITELIST_DURATION,
- DEFAULT_MMS_TEMP_APP_WHITELIST_DURATION);
+ case KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS:
+ MMS_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
+ KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS,
+ DEFAULT_MMS_TEMP_APP_ALLOWLIST_DURATION_MS);
break;
- case KEY_SMS_TEMP_APP_WHITELIST_DURATION:
- SMS_TEMP_APP_WHITELIST_DURATION = properties.getLong(
- KEY_SMS_TEMP_APP_WHITELIST_DURATION,
- DEFAULT_SMS_TEMP_APP_WHITELIST_DURATION);
+ case KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS:
+ SMS_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
+ KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS,
+ DEFAULT_SMS_TEMP_APP_ALLOWLIST_DURATION_MS);
break;
case KEY_NOTIFICATION_ALLOWLIST_DURATION_MS:
NOTIFICATION_ALLOWLIST_DURATION_MS = properties.getLong(
@@ -1438,16 +1437,16 @@
TimeUtils.formatDuration(MIN_TIME_TO_ALARM, pw);
pw.println();
- pw.print(" "); pw.print(KEY_MAX_TEMP_APP_WHITELIST_DURATION); pw.print("=");
- TimeUtils.formatDuration(MAX_TEMP_APP_WHITELIST_DURATION, pw);
+ pw.print(" "); pw.print(KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS); pw.print("=");
+ TimeUtils.formatDuration(MAX_TEMP_APP_ALLOWLIST_DURATION_MS, pw);
pw.println();
- pw.print(" "); pw.print(KEY_MMS_TEMP_APP_WHITELIST_DURATION); pw.print("=");
- TimeUtils.formatDuration(MMS_TEMP_APP_WHITELIST_DURATION, pw);
+ pw.print(" "); pw.print(KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS); pw.print("=");
+ TimeUtils.formatDuration(MMS_TEMP_APP_ALLOWLIST_DURATION_MS, pw);
pw.println();
- pw.print(" "); pw.print(KEY_SMS_TEMP_APP_WHITELIST_DURATION); pw.print("=");
- TimeUtils.formatDuration(SMS_TEMP_APP_WHITELIST_DURATION, pw);
+ pw.print(" "); pw.print(KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS); pw.print("=");
+ TimeUtils.formatDuration(SMS_TEMP_APP_ALLOWLIST_DURATION_MS, pw);
pw.println();
pw.print(" "); pw.print(KEY_NOTIFICATION_ALLOWLIST_DURATION_MS); pw.print("=");
@@ -1804,29 +1803,29 @@
public long whitelistAppTemporarily(String packageName, int userId, String reason)
throws RemoteException {
// At least 10 seconds.
- long duration = Math.max(10_000L, mConstants.MAX_TEMP_APP_WHITELIST_DURATION / 2);
- addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
- return duration;
+ long durationMs = Math.max(10_000L, mConstants.MAX_TEMP_APP_ALLOWLIST_DURATION_MS / 2);
+ addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason);
+ return durationMs;
}
@Override
public void addPowerSaveTempWhitelistApp(String packageName, long duration,
int userId, String reason) throws RemoteException {
- addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
+ addPowerSaveTempAllowlistAppChecked(packageName, duration, userId, reason);
}
@Override public long addPowerSaveTempWhitelistAppForMms(String packageName,
int userId, String reason) throws RemoteException {
- long duration = mConstants.MMS_TEMP_APP_WHITELIST_DURATION;
- addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
- return duration;
+ long durationMs = mConstants.MMS_TEMP_APP_ALLOWLIST_DURATION_MS;
+ addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason);
+ return durationMs;
}
@Override public long addPowerSaveTempWhitelistAppForSms(String packageName,
int userId, String reason) throws RemoteException {
- long duration = mConstants.SMS_TEMP_APP_WHITELIST_DURATION;
- addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
- return duration;
+ long durationMs = mConstants.SMS_TEMP_APP_ALLOWLIST_DURATION_MS;
+ addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason);
+ return durationMs;
}
@Override public void exitIdle(String reason) {
@@ -1900,7 +1899,7 @@
@Override
public void addPowerSaveTempWhitelistApp(int callingUid, String packageName,
long duration, int userId, boolean sync, String reason) {
- addPowerSaveTempWhitelistAppInternal(callingUid, packageName, duration,
+ addPowerSaveTempAllowlistAppInternal(callingUid, packageName, duration,
userId, sync, reason);
}
@@ -2617,7 +2616,7 @@
}
}
- void addPowerSaveTempWhitelistAppChecked(String packageName, long duration,
+ void addPowerSaveTempAllowlistAppChecked(String packageName, long duration,
int userId, String reason) throws RemoteException {
getContext().enforceCallingPermission(
Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
@@ -2632,14 +2631,14 @@
"addPowerSaveTempWhitelistApp", null);
final long token = Binder.clearCallingIdentity();
try {
- addPowerSaveTempWhitelistAppInternal(callingUid,
+ addPowerSaveTempAllowlistAppInternal(callingUid,
packageName, duration, userId, true, reason);
} finally {
Binder.restoreCallingIdentity(token);
}
}
- void removePowerSaveTempWhitelistAppChecked(String packageName, int userId)
+ void removePowerSaveTempAllowlistAppChecked(String packageName, int userId)
throws RemoteException {
getContext().enforceCallingPermission(
Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
@@ -2654,7 +2653,7 @@
"removePowerSaveTempWhitelistApp", null);
final long token = Binder.clearCallingIdentity();
try {
- removePowerSaveTempWhitelistAppInternal(packageName, userId);
+ removePowerSaveTempAllowlistAppInternal(packageName, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2664,7 +2663,7 @@
* Adds an app to the temporary whitelist and resets the endTime for granting the
* app an exemption to access network and acquire wakelocks.
*/
- void addPowerSaveTempWhitelistAppInternal(int callingUid, String packageName,
+ void addPowerSaveTempAllowlistAppInternal(int callingUid, String packageName,
long duration, int userId, boolean sync, String reason) {
try {
int uid = getContext().getPackageManager().getPackageUidAsUser(packageName, userId);
@@ -2690,7 +2689,7 @@
+ " is not on whitelist");
}
}
- duration = Math.min(duration, mConstants.MAX_TEMP_APP_WHITELIST_DURATION);
+ duration = Math.min(duration, mConstants.MAX_TEMP_APP_ALLOWLIST_DURATION_MS);
Pair<MutableLong, String> entry = mTempWhitelistAppIdEndTimes.get(appId);
final boolean newEntry = entry == null;
// Set the new end time
@@ -2728,7 +2727,7 @@
/**
* Removes an app from the temporary whitelist and notifies the observers.
*/
- private void removePowerSaveTempWhitelistAppInternal(String packageName, int userId) {
+ private void removePowerSaveTempAllowlistAppInternal(String packageName, int userId) {
try {
final int uid = getContext().getPackageManager().getPackageUidAsUser(
packageName, userId);
@@ -4354,9 +4353,9 @@
if (arg != null) {
try {
if (removePkg) {
- removePowerSaveTempWhitelistAppChecked(arg, shell.userId);
+ removePowerSaveTempAllowlistAppChecked(arg, shell.userId);
} else {
- addPowerSaveTempWhitelistAppChecked(arg, duration, shell.userId, "shell");
+ addPowerSaveTempAllowlistAppChecked(arg, duration, shell.userId, "shell");
}
} catch (Exception e) {
pw.println("Failed: " + e);
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 fe96952..255e2f6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1054,6 +1054,7 @@
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
int userId, String tag) {
+ // Rate limit excessive schedule() calls.
final String servicePkg = job.getService().getPackageName();
if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) {
// Only limit schedule calls for persisted jobs scheduled by the app itself.
@@ -1358,8 +1359,7 @@
for (int i=0; i<mActiveServices.size(); i++) {
JobServiceContext jsc = mActiveServices.get(i);
final JobStatus executing = jsc.getRunningJobLocked();
- if (executing != null
- && (executing.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0) {
+ if (executing != null && !executing.canRunInDoze()) {
jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE,
"cancelled due to doze");
}
@@ -1411,7 +1411,7 @@
final JobServiceContext jsc = mActiveServices.get(i);
final JobStatus job = jsc.getRunningJobLocked();
if (job != null
- && (job.getJob().getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0
+ && !job.canRunInDoze()
&& !job.dozeWhitelisted
&& !job.uidActive) {
// We will report active if we have a job running and it is not an exception
@@ -2600,6 +2600,7 @@
// job that runs one of the app's services, as well as verifying that the
// named service properly requires the BIND_JOB_SERVICE permission
private void enforceValidJobRequest(int uid, JobInfo job) {
+ job.enforceValidity();
final PackageManager pm = getContext()
.createContextAsUser(UserHandle.getUserHandleForUid(uid), 0)
.getPackageManager();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 1e72062..cc20213 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -20,10 +20,11 @@
import android.app.AppGlobals;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.os.BasicShellCommandHandler;
import android.os.Binder;
import android.os.UserHandle;
+import com.android.modules.utils.BasicShellCommandHandler;
+
import java.io.PrintWriter;
public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index 44c8fcf..f647db9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -189,8 +189,7 @@
final String packageName = jobStatus.getSourcePackageName();
final boolean canRun = !mAppStateTracker.areJobsRestricted(uid, packageName,
- (jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION)
- != 0);
+ jobStatus.canRunInBatterySaver());
final boolean isActive;
if (activeState == UNKNOWN) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 67997cf..6ddafad 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -464,7 +464,7 @@
NetworkCapabilities capabilities) {
// TODO: consider matching against non-active networks
- final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+ final boolean ignoreBlocked = jobStatus.shouldIgnoreNetworkBlocking();
final NetworkInfo info = mConnManager.getNetworkInfoForUid(network,
jobStatus.getSourceUid(), ignoreBlocked);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 00dbb82..8f6c68d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -470,7 +470,7 @@
}
this.requiredConstraints = requiredConstraints;
mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
- mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+ mReadyNotDozing = canRunInDoze();
if (standbyBucket == RESTRICTED_INDEX) {
addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
} else {
@@ -1036,6 +1036,22 @@
mPersistedUtcTimes = null;
}
+ /**
+ * @return true if the job is exempted from Doze restrictions and therefore allowed to run
+ * in Doze.
+ */
+ public boolean canRunInDoze() {
+ return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+ }
+
+ boolean canRunInBatterySaver() {
+ return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0;
+ }
+
+ boolean shouldIgnoreNetworkBlocking() {
+ return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+ }
+
/** @return true if the constraint was changed, false otherwise. */
boolean setChargingConstraintSatisfied(boolean state) {
return setConstraintSatisfied(CONSTRAINT_CHARGING, state);
@@ -1086,7 +1102,7 @@
dozeWhitelisted = whitelisted;
if (setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state)) {
// The constraint was changed. Update the ready flag.
- mReadyNotDozing = state || (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+ mReadyNotDozing = state || canRunInDoze();
return true;
}
return false;
@@ -1771,9 +1787,11 @@
pw.print(prefix); pw.print(" readyDeadlineSatisfied: ");
pw.println(mReadyDeadlineSatisfied);
}
- pw.print(prefix);
- pw.print(" readyDynamicSatisfied: ");
- pw.println(mReadyDynamicSatisfied);
+ if (mDynamicConstraints != 0) {
+ pw.print(prefix);
+ pw.print(" readyDynamicSatisfied: ");
+ pw.println(mReadyDynamicSatisfied);
+ }
pw.print(prefix);
pw.print(" readyComponentEnabled: ");
pw.println(serviceInfo != null);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 7d7de3b..1d72b42 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -1679,42 +1679,47 @@
// Update job bookkeeping out of band.
JobSchedulerBackgroundThread.getHandler().post(() -> {
final int bucketIndex = JobSchedulerService.standbyBucketToBucketIndex(bucket);
- if (DEBUG) {
- Slog.i(TAG, "Moving pkg " + string(userId, packageName) + " to bucketIndex "
- + bucketIndex);
- }
- List<JobStatus> restrictedChanges = new ArrayList<>();
- synchronized (mLock) {
- ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
- if (jobs == null || jobs.size() == 0) {
- return;
- }
- for (int i = jobs.size() - 1; i >= 0; i--) {
- JobStatus js = jobs.valueAt(i);
- // Effective standby bucket can change after this in some situations so
- // use the real bucket so that the job is tracked by the controllers.
- if ((bucketIndex == RESTRICTED_INDEX
- || js.getStandbyBucket() == RESTRICTED_INDEX)
- && bucketIndex != js.getStandbyBucket()) {
- restrictedChanges.add(js);
- }
- js.setStandbyBucket(bucketIndex);
- }
- Timer timer = mPkgTimers.get(userId, packageName);
- if (timer != null && timer.isActive()) {
- timer.rescheduleCutoff();
- }
- if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
- if (restrictedChanges.size() > 0) {
- mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
- }
+ updateStandbyBucket(userId, packageName, bucketIndex);
});
}
}
+ @VisibleForTesting
+ void updateStandbyBucket(
+ final int userId, final @NonNull String packageName, final int bucketIndex) {
+ if (DEBUG) {
+ Slog.i(TAG, "Moving pkg " + string(userId, packageName)
+ + " to bucketIndex " + bucketIndex);
+ }
+ List<JobStatus> restrictedChanges = new ArrayList<>();
+ synchronized (mLock) {
+ ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
+ if (jobs == null || jobs.size() == 0) {
+ return;
+ }
+ for (int i = jobs.size() - 1; i >= 0; i--) {
+ JobStatus js = jobs.valueAt(i);
+ // Effective standby bucket can change after this in some situations so
+ // use the real bucket so that the job is tracked by the controllers.
+ if ((bucketIndex == RESTRICTED_INDEX || js.getStandbyBucket() == RESTRICTED_INDEX)
+ && bucketIndex != js.getStandbyBucket()) {
+ restrictedChanges.add(js);
+ }
+ js.setStandbyBucket(bucketIndex);
+ }
+ Timer timer = mPkgTimers.get(userId, packageName);
+ if (timer != null && timer.isActive()) {
+ timer.rescheduleCutoff();
+ }
+ if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+ if (restrictedChanges.size() > 0) {
+ mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
+ }
+ }
+
private final class DeleteTimingSessionsFunctor implements Consumer<List<TimingSession>> {
private final Predicate<TimingSession> mTooOld = new Predicate<TimingSession>() {
public boolean test(TimingSession ts) {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 0f60eae..8363e9f 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -42,6 +42,7 @@
import "frameworks/base/core/proto/android/server/location/enums.proto";
import "frameworks/base/core/proto/android/service/procstats_enum.proto";
import "frameworks/base/core/proto/android/service/usb.proto";
+import "frameworks/base/core/proto/android/stats/camera/camera.proto";
import "frameworks/base/core/proto/android/stats/connectivity/network_stack.proto";
import "frameworks/base/core/proto/android/stats/connectivity/tethering.proto";
import "frameworks/base/core/proto/android/stats/dnsresolver/dns_resolver.proto";
@@ -60,6 +61,7 @@
import "frameworks/base/core/proto/android/stats/style/style_enums.proto";
import "frameworks/base/core/proto/android/stats/sysui/notification_enums.proto";
import "frameworks/base/core/proto/android/stats/tls/enums.proto";
+import "frameworks/base/core/proto/android/stats/tv/tif_enums.proto";
import "frameworks/base/core/proto/android/telecomm/enums.proto";
import "frameworks/base/core/proto/android/telephony/enums.proto";
import "frameworks/base/core/proto/android/view/enums.proto";
@@ -510,6 +512,8 @@
MediaPlaybackTrackChanged media_playback_track_changed = 324;
WifiScanReported wifi_scan_reported = 325 [(module) = "wifi"];
WifiPnoScanReported wifi_pno_scan_reported = 326 [(module) = "wifi"];
+ TifTuneStateChanged tif_tune_changed = 327 [(module) = "framework"];
+ AutoRotateReported auto_rotate_reported = 328 [(module) = "framework"];
// StatsdStats tracks platform atoms with ids upto 500.
// Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
@@ -3766,6 +3770,7 @@
WARM = 1;
HOT = 2;
COLD = 3;
+ RELAUNCH = 4;
}
// The transition type.
optional TransitionType type = 3;
@@ -3827,6 +3832,7 @@
WARM = 1;
HOT = 2;
COLD = 3;
+ RELAUNCH = 4;
}
// The transition type.
optional TransitionType type = 3;
@@ -9946,10 +9952,12 @@
* frameworks/base/services/core/java/com/android/server/camera/CameraServiceProxy.java
*/
message CameraActionEvent {
- // Camera session duration
+ // Camera session duration in milliseconds if action is SESSION.
+ // 0 if action is OPEN or CLOSE.
optional int64 duration_millis = 1;
- // Camera API level used
+ // Camera API level used.
+ // 1 for camera1 API, and 2 for camera2 API.
optional int32 api_level = 2;
// Name of client package
@@ -9963,6 +9971,56 @@
EXTERNAL = 3;
}
optional Facing facing = 4;
+
+ // Camera ID
+ optional string camera_id = 5;
+
+ // Camera action type
+ enum Action {
+ UNKNOWN_ACTION = 0;
+ OPEN = 1;
+ CLOSE = 2;
+ SESSION = 3;
+ }
+ optional Action action = 6;
+
+ // Whether the client is accessing camera using ndk
+ optional bool is_ndk = 7;
+
+ // Action OPEN: Open latency
+ // Action CLOSE: Close latency
+ // Action SESSION: Camera session creation duration.
+ // If this entry is reusing an existing session, the value is -1.
+ optional int32 latency_millis = 8;
+
+ // session type: 0 for normal mode, 1 for constrained high speed mode
+ optional int32 operating_mode = 9;
+
+ // If actioh is SESSION: number of internal reconfigurations
+ // Else: 0
+ optional int32 internal_reconfig = 10;
+
+ // Number of requests for this capture session. Only applicable to SESSION
+ // action.
+ optional int64 request_count = 11;
+ // Number of result errors. Only applicable to SESSION action.
+ optional int64 result_error_count = 12;
+ // Whether the device runs into error state.
+ optional bool device_error = 13;
+
+ // If action is SESSION: Stream states
+ // Else: stream_count = 0
+ optional int32 stream_count = 14;
+ optional android.stats.camera.CameraStreamProto stream_1 = 15
+ [(android.os.statsd.log_mode) = MODE_BYTES];
+ optional android.stats.camera.CameraStreamProto stream_2 = 16
+ [(android.os.statsd.log_mode) = MODE_BYTES];
+ optional android.stats.camera.CameraStreamProto stream_3 = 17
+ [(android.os.statsd.log_mode) = MODE_BYTES];
+ optional android.stats.camera.CameraStreamProto stream_4 = 18
+ [(android.os.statsd.log_mode) = MODE_BYTES];
+ optional android.stats.camera.CameraStreamProto stream_5 = 19
+ [(android.os.statsd.log_mode) = MODE_BYTES];
}
/**
@@ -10352,6 +10410,39 @@
}
/**
+ * Logs when a TV Input Service Session changes tune state
+ * This is atom ID 327.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
+ */
+message TifTuneStateChanged {
+
+ // Tv Input Service uid, TV Player uid
+ repeated AttributionNode attribution_node = 1 [
+ (state_field_option).primary_field_first_uid = true
+ ];
+ optional android.stats.tv.TifTuneState state = 2 [
+ (state_field_option).exclusive_state = true,
+ (state_field_option).default_state_value = 0,
+ (state_field_option).nested = false
+ ];
+ // This a globally unique 128 bit random number created by TvInputManagerService when
+ // android.media.tv.TvInputManager#createSession is called.
+ // It is has no device or user association.
+ // See android.media.tv.TvInputService.onCreateSession(java.lang.String, java.lang.String)
+ // WARNING: Any changes to this field should be carefully reviewed for privacy.
+ // Inspect the code at
+ // framework/base/cmds/statsd/src/atoms.proto
+ // TifTuneState
+ // frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
+ // logTuneStateChanged
+ // BinderService.createSession
+ // SessionState.sessionId
+ optional string tif_session_id = 3 [(state_field_option).primary_field = true];
+}
+
+/**
* Logs when a tune occurs through device's Frontend.
* This is atom ID 276.
*
@@ -12077,6 +12168,33 @@
}
/**
+ * Logs when an auto rotate event occurs while smart auto rotate is enabled.
+ */
+message AutoRotateReported {
+ enum Orientation {
+ UNKNOWN = 1;
+ ROTATION_0 = 2;
+ ROTATION_90 = 3;
+ ROTATION_180 = 4;
+ ROTATION_270 = 5;
+ DISABLED = 6;
+ UNAVAILABLE = 7;
+ FAILURE = 8;
+ }
+
+ // Orientation of the device when a rotation was detected.
+ optional Orientation current_orientation = 1;
+ // The orientation of the phone after rotation before going through the recommendation service.
+ optional Orientation proposed_orientation = 2;
+ // Orientation recommended by the smart autorotate service component outside of the platform. It
+ // may or may not match the proposed_orientation. Can be disabled or unavailable if the
+ // recommendation service is disabled or unavailable. Will be unknown if the service failed.
+ optional Orientation recommended_orientation = 3;
+ // Time taken to calculate the rotation recommendation.
+ optional int64 recommendation_process_duration_millis = 4;
+}
+
+/**
* Pushes TLS handshake counters from Conscrypt.
* Pulled from:
* external/conscrypt/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 822b40b..8d2f143 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -5575,7 +5575,6 @@
android.os.BadParcelableException
android.os.BaseBundle$NoImagePreloadHolder
android.os.BaseBundle
-android.os.BasicShellCommandHandler
android.os.BatteryManager
android.os.BatteryManagerInternal
android.os.BatteryProperty$1
@@ -6750,6 +6749,7 @@
android.sysprop.-$$Lambda$TelephonyProperties$klELuV5zVSqFveC5l6c3FSJmLAU
android.sysprop.-$$Lambda$TelephonyProperties$pFU8zg4eHAdooeRLJg1WBG52cKk
android.sysprop.-$$Lambda$TelephonyProperties$sXc3eBCFirzHWb9pvClH7EsiM_Q
+android.stats.camera.nano.CameraStreamProto
android.sysprop.AdbProperties
android.sysprop.ApexProperties
android.sysprop.ContactsProperties
@@ -11756,6 +11756,7 @@
com.android.net.module.annotation.VisibleForTesting
com.android.net.module.annotation.WorkerThread
com.android.net.module.util.InetAddressUtils
+com.android.modules.utils.BasicShellCommandHandler
com.android.okhttp.Address
com.android.okhttp.AndroidShimResponseCache
com.android.okhttp.Authenticator
diff --git a/core/api/current.txt b/core/api/current.txt
index f665512..e15c559 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10158,6 +10158,7 @@
method public abstract void grantUriPermission(String, android.net.Uri, int);
method public abstract boolean isDeviceProtectedStorage();
method public boolean isRestricted();
+ method public static boolean isUiContext(@NonNull android.content.Context);
method public abstract boolean moveDatabaseFrom(android.content.Context, String);
method public abstract boolean moveSharedPreferencesFrom(android.content.Context, String);
method @NonNull public final android.content.res.TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[]);
@@ -12332,7 +12333,7 @@
field public static final int SIGNATURE_SECOND_NOT_SIGNED = -2; // 0xfffffffe
field public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; // 0xfffffffc
field public static final int SYNCHRONOUS = 2; // 0x2
- field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
+ field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
field public static final int VERIFICATION_ALLOW = 1; // 0x1
field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
@@ -12344,6 +12345,23 @@
ctor public PackageManager.NameNotFoundException(String);
}
+ public static final class PackageManager.Property implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean getBoolean();
+ method public float getFloat();
+ method public int getInteger();
+ method @NonNull public String getName();
+ method public int getResourceId();
+ method @Nullable public String getString();
+ method public boolean isBoolean();
+ method public boolean isFloat();
+ method public boolean isInteger();
+ method public boolean isResourceId();
+ method public boolean isString();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.Property> CREATOR;
+ }
+
@Deprecated public class PackageStats implements android.os.Parcelable {
ctor @Deprecated public PackageStats(String);
ctor @Deprecated public PackageStats(android.os.Parcel);
@@ -35793,6 +35811,7 @@
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectCredentialProtectedWhileLocked();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectFileUriExposure();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectImplicitDirectBoot();
+ method @NonNull public android.os.StrictMode.VmPolicy.Builder detectIncorrectContextUse();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectLeakedClosableObjects();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectLeakedRegistrationObjects();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectLeakedSqlLiteObjects();
@@ -46190,12 +46209,6 @@
field public static final int SCAN_TYPE_PERIODIC = 1; // 0x1
}
- public final class PhoneCapability implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PhoneCapability> CREATOR;
- }
-
public class PhoneNumberFormattingTextWatcher implements android.text.TextWatcher {
ctor public PhoneNumberFormattingTextWatcher();
ctor public PhoneNumberFormattingTextWatcher(String);
@@ -46264,10 +46277,10 @@
public class PhoneStateListener {
ctor public PhoneStateListener();
- ctor @Deprecated public PhoneStateListener(@NonNull java.util.concurrent.Executor);
+ ctor public PhoneStateListener(@NonNull java.util.concurrent.Executor);
method public void onActiveDataSubscriptionIdChanged(int);
method public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo);
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallDisconnectCauseChanged(int, int);
+ method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onCallDisconnectCauseChanged(int, int);
method public void onCallForwardingIndicatorChanged(boolean);
method public void onCallStateChanged(int, String);
method public void onCellInfoChanged(java.util.List<android.telephony.CellInfo>);
@@ -46275,144 +46288,36 @@
method public void onDataActivity(int);
method public void onDataConnectionStateChanged(int);
method public void onDataConnectionStateChanged(int, int);
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo);
+ method @RequiresPermission("android.permission.READ_PHONE_STATE") public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo);
method public void onEmergencyNumberListChanged(@NonNull java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>>);
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
+ method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
method public void onMessageWaitingIndicatorChanged(boolean);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
+ method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
method public void onRegistrationFailed(@NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
method public void onServiceStateChanged(android.telephony.ServiceState);
method @Deprecated public void onSignalStrengthChanged(int);
method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
method public void onUserMobileDataStateChanged(boolean);
- field @Deprecated public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
- field @Deprecated public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8
- field @Deprecated public static final int LISTEN_CALL_STATE = 32; // 0x20
- field @Deprecated public static final int LISTEN_CELL_INFO = 1024; // 0x400
- field @Deprecated public static final int LISTEN_CELL_LOCATION = 16; // 0x10
- field @Deprecated public static final int LISTEN_DATA_ACTIVITY = 128; // 0x80
- field @Deprecated public static final int LISTEN_DATA_CONNECTION_STATE = 64; // 0x40
- field @Deprecated public static final int LISTEN_DISPLAY_INFO_CHANGED = 1048576; // 0x100000
- field @Deprecated public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
- field @Deprecated public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
+ field public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000
+ field @RequiresPermission(android.Manifest.permission.READ_PRECISE_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
+ field public static final int LISTEN_CELL_INFO = 1024; // 0x400
+ field public static final int LISTEN_CELL_LOCATION = 16; // 0x10
+ field public static final int LISTEN_DATA_ACTIVITY = 128; // 0x80
+ field public static final int LISTEN_DATA_CONNECTION_STATE = 64; // 0x40
+ field public static final int LISTEN_DISPLAY_INFO_CHANGED = 1048576; // 0x100000
+ field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
+ field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
+ field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
field public static final int LISTEN_NONE = 0; // 0x0
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000
- field @Deprecated public static final int LISTEN_SERVICE_STATE = 1; // 0x1
+ field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
+ field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000
+ field public static final int LISTEN_SERVICE_STATE = 1; // 0x1
field @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 2; // 0x2
- field @Deprecated public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
- field @Deprecated public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000
- }
-
- public static interface PhoneStateListener.ActiveDataSubscriptionIdChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onActiveDataSubscriptionIdChanged(int);
- }
-
- public static interface PhoneStateListener.AlwaysReportedSignalStrengthsChangedListener {
- method @RequiresPermission("android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH") public void onSignalStrengthsChanged(@NonNull android.telephony.SignalStrength);
- }
-
- public static interface PhoneStateListener.BarringInfoChangedListener {
- method @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo);
- }
-
- public static interface PhoneStateListener.CallDisconnectCauseChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallDisconnectCauseChanged(int, int);
- }
-
- public static interface PhoneStateListener.CallForwardingIndicatorChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onCallForwardingIndicatorChanged(boolean);
- }
-
- public static interface PhoneStateListener.CallStateChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public void onCallStateChanged(int, @Nullable String);
- }
-
- public static interface PhoneStateListener.CarrierNetworkChangeListener {
- method public void onCarrierNetworkChange(boolean);
- }
-
- public static interface PhoneStateListener.CellInfoChangedListener {
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void onCellInfoChanged(@NonNull java.util.List<android.telephony.CellInfo>);
- }
-
- public static interface PhoneStateListener.CellLocationChangedListener {
- method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void onCellLocationChanged(@NonNull android.telephony.CellLocation);
- }
-
- public static interface PhoneStateListener.DataActivationStateChangedListener {
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataActivationStateChanged(int);
- }
-
- public static interface PhoneStateListener.DataActivityListener {
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataActivity(int);
- }
-
- public static interface PhoneStateListener.DataConnectionStateChangedListener {
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataConnectionStateChanged(int, int);
- }
-
- public static interface PhoneStateListener.DisplayInfoChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo);
- }
-
- public static interface PhoneStateListener.EmergencyNumberListChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onEmergencyNumberListChanged(@NonNull java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>>);
- }
-
- public static interface PhoneStateListener.ImsCallDisconnectCauseChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
- }
-
- public static interface PhoneStateListener.MessageWaitingIndicatorChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onMessageWaitingIndicatorChanged(boolean);
- }
-
- public static interface PhoneStateListener.PhoneCapabilityChangedListener {
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onPhoneCapabilityChanged(@NonNull android.telephony.PhoneCapability);
- }
-
- public static interface PhoneStateListener.PhysicalChannelConfigChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPhysicalChannelConfigChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>);
- }
-
- public static interface PhoneStateListener.PreciseDataConnectionStateChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
- }
-
- public static interface PhoneStateListener.RegistrationFailedListener {
- method @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void onRegistrationFailed(@NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
- }
-
- public static interface PhoneStateListener.ServiceStateChangedListener {
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onServiceStateChanged(@NonNull android.telephony.ServiceState);
- }
-
- public static interface PhoneStateListener.SignalStrengthsChangedListener {
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onSignalStrengthsChanged(@NonNull android.telephony.SignalStrength);
- }
-
- public static interface PhoneStateListener.UserMobileDataStateChangedListener {
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onUserMobileDataStateChanged(boolean);
- }
-
- public final class PhysicalChannelConfig implements android.os.Parcelable {
- method public int describeContents();
- method public int getCellBandwidthDownlink();
- method public int getChannelNumber();
- method public int getConnectionStatus();
- method public int getNetworkType();
- method @IntRange(from=0, to=1007) public int getPhysicalCellId();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int CHANNEL_NUMBER_UNKNOWN = -1; // 0xffffffff
- field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1
- field public static final int CONNECTION_SECONDARY_SERVING = 2; // 0x2
- field public static final int CONNECTION_UNKNOWN = -1; // 0xffffffff
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PhysicalChannelConfig> CREATOR;
- field public static final int PHYSICAL_CELL_ID_UNKNOWN = -1; // 0xffffffff
+ field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
+ field public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000
}
public final class PreciseDataConnectionState implements android.os.Parcelable {
@@ -46595,6 +46500,8 @@
field public static final int RESULT_RECEIVE_WHILE_ENCRYPTED = 504; // 0x1f8
field public static final int RESULT_REMOTE_EXCEPTION = 31; // 0x1f
field public static final int RESULT_REQUEST_NOT_SUPPORTED = 24; // 0x18
+ field public static final int RESULT_RIL_ACCESS_BARRED = 122; // 0x7a
+ field public static final int RESULT_RIL_BLOCKED_DUE_TO_CALL = 123; // 0x7b
field public static final int RESULT_RIL_CANCELLED = 119; // 0x77
field public static final int RESULT_RIL_ENCODING_ERR = 109; // 0x6d
field public static final int RESULT_RIL_INTERNAL_ERR = 113; // 0x71
@@ -46613,6 +46520,7 @@
field public static final int RESULT_RIL_RADIO_NOT_AVAILABLE = 100; // 0x64
field public static final int RESULT_RIL_REQUEST_NOT_SUPPORTED = 114; // 0x72
field public static final int RESULT_RIL_REQUEST_RATE_LIMITED = 106; // 0x6a
+ field public static final int RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED = 121; // 0x79
field public static final int RESULT_RIL_SIM_ABSENT = 120; // 0x78
field public static final int RESULT_RIL_SMS_SEND_FAIL_RETRY = 101; // 0x65
field public static final int RESULT_RIL_SYSTEM_ERR = 108; // 0x6c
@@ -46921,8 +46829,7 @@
method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
method public boolean isWorldPhone();
method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
- method @Deprecated public void listen(long, @NonNull android.telephony.PhoneStateListener);
- method public void registerPhoneStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.PhoneStateListener);
+ method public void listen(long, @NonNull android.telephony.PhoneStateListener);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
method public void sendDialerSpecialCode(String);
@@ -46944,7 +46851,6 @@
method @Deprecated public void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri);
method @Deprecated public void setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void switchMultiSimConfig(int);
- method public void unregisterPhoneStateListener(@NonNull android.telephony.PhoneStateListener);
method public void updateAvailableNetworks(@NonNull java.util.List<android.telephony.AvailableNetworkInfo>, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
field public static final String ACTION_CARRIER_MESSAGING_CLIENT_SERVICE = "android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE";
field public static final String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL";
@@ -52924,6 +52830,7 @@
method public boolean performHapticFeedback(int, int);
method public boolean performLongClick();
method public boolean performLongClick(float, float);
+ method @Nullable public android.view.OnReceiveContentListener.Payload performReceiveContent(@NonNull android.view.OnReceiveContentListener.Payload);
method public void playSoundEffect(int);
method public boolean post(Runnable);
method public boolean postDelayed(Runnable, long);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6cf93fc..2ebbbf5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2066,6 +2066,10 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR;
}
+ public static class LauncherApps.ShortcutQuery {
+ field public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800
+ }
+
public class PackageInstaller {
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2
@@ -5027,14 +5031,15 @@
method public void clearResourceLostListener();
method public void close();
method public int connectCiCam(int);
+ method public int connectFrontendToCiCam(int);
method public int disconnectCiCam();
+ method public int disconnectFrontendToCiCam(int);
method public int getAvSyncHwId(@NonNull android.media.tv.tuner.filter.Filter);
method public long getAvSyncTime(int);
method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
method @Nullable public java.util.List<android.media.tv.tuner.frontend.FrontendInfo> getFrontendInfoList();
method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
- method public int linkFrontendToCiCam(int);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
method @Nullable public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
@@ -5048,7 +5053,6 @@
method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
- method public int unlinkFrontendToCiCam(int);
method public void updateResourcePriority(int, int);
field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
field public static final int INVALID_FILTER_ID = -1; // 0xffffffff
@@ -5249,7 +5253,7 @@
public class Filter implements java.lang.AutoCloseable {
method public void close();
method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
- method public int configureScramblingStatusEvent(int);
+ method public int configureMonitorEvent(int);
method public int flush();
method public int getId();
method public long getId64Bit();
@@ -5257,6 +5261,8 @@
method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
method public int start();
method public int stop();
+ field public static final int MONITOR_EVENT_IP_CID_CHANGE = 2; // 0x2
+ field public static final int MONITOR_EVENT_SCRAMBLING_STATUS = 1; // 0x1
field public static final int SCRAMBLING_STATUS_NOT_SCRAMBLED = 2; // 0x2
field public static final int SCRAMBLING_STATUS_SCRAMBLED = 4; // 0x4
field public static final int SCRAMBLING_STATUS_UNKNOWN = 1; // 0x1
@@ -5303,6 +5309,10 @@
ctor public FilterEvent();
}
+ public final class IpCidChangeEvent extends android.media.tv.tuner.filter.FilterEvent {
+ method public int getIpCid();
+ }
+
public final class IpFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration {
method @NonNull public static android.media.tv.tuner.filter.IpFilterConfiguration.Builder builder();
method @NonNull @Size(min=4, max=16) public byte[] getDstIpAddress();
@@ -8953,11 +8963,8 @@
public abstract class ImpressionAttestationService extends android.app.Service {
ctor public ImpressionAttestationService();
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @Nullable public abstract android.service.attestation.ImpressionToken onGenerateImpressionToken(@NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String);
- method public abstract int onVerifyImpressionToken(@NonNull android.service.attestation.ImpressionToken);
- field public static final int VERIFICATION_STATUS_APP_DECLARED = 2; // 0x2
- field public static final int VERIFICATION_STATUS_OS_VERIFIED = 1; // 0x1
- field public static final int VERIFICATION_STATUS_UNKNOWN = 0; // 0x0
+ method @Nullable public abstract android.service.attestation.ImpressionToken onGenerateImpressionToken(@NonNull String, @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String);
+ method public abstract boolean onVerifyImpressionToken(@NonNull String, @NonNull android.service.attestation.ImpressionToken);
}
public final class ImpressionToken implements android.os.Parcelable {
@@ -10340,83 +10347,35 @@
method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
method @Deprecated public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int);
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
+ method public void onPhysicalChannelConfigurationChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>);
+ method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
method public void onRadioPowerStateChanged(int);
method public void onSrvccStateChanged(int);
method public void onVoiceActivationStateChanged(int);
- field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED = 23; // 0x17
- field @RequiresPermission("android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH") public static final int EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED = 10; // 0xa
- field @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_BARRING_INFO_CHANGED = 32; // 0x20
- field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_CALL_ATTRIBUTES_CHANGED = 27; // 0x1b
- field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_CALL_DISCONNECT_CAUSE_CHANGED = 26; // 0x1a
- field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4; // 0x4
- field @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public static final int EVENT_CALL_STATE_CHANGED = 6; // 0x6
- field public static final int EVENT_CARRIER_NETWORK_CHANGED = 17; // 0x11
- field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_INFO_CHANGED = 11; // 0xb
- field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_LOCATION_CHANGED = 5; // 0x5
- field public static final int EVENT_DATA_ACTIVATION_STATE_CHANGED = 19; // 0x13
- field public static final int EVENT_DATA_ACTIVITY_CHANGED = 8; // 0x8
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED = 14; // 0xe
- field public static final int EVENT_DATA_CONNECTION_STATE_CHANGED = 7; // 0x7
- field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_DATA_ENABLED_CHANGED = 34; // 0x22
- field public static final int EVENT_DISPLAY_INFO_CHANGED = 21; // 0x15
- field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_EMERGENCY_NUMBER_LIST_CHANGED = 25; // 0x19
- field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = 28; // 0x1c
- field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_MESSAGE_WAITING_INDICATOR_CHANGED = 3; // 0x3
- field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_OEM_HOOK_RAW = 15; // 0xf
- field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int EVENT_OUTGOING_EMERGENCY_CALL = 29; // 0x1d
- field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int EVENT_OUTGOING_EMERGENCY_SMS = 30; // 0x1e
- field public static final int EVENT_PHONE_CAPABILITY_CHANGED = 22; // 0x16
- field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 33; // 0x21
- field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PRECISE_CALL_STATE_CHANGED = 12; // 0xc
- field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED = 13; // 0xd
- field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_RADIO_POWER_STATE_CHANGED = 24; // 0x18
- field @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_REGISTRATION_FAILURE = 31; // 0x1f
- field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1
- field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9
- field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2
- field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SRVCC_STATE_CHANGED = 16; // 0x10
- field public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; // 0x14
- field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; // 0x12
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
- field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
+ field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000
+ field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
+ field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
+ field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final long LISTEN_PHYSICAL_CHANNEL_CONFIGURATION = 4294967296L; // 0x100000000L
+ field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
+ field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
+ field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
+ field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
}
- public static interface PhoneStateListener.CallAttributesChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
- }
-
- public static interface PhoneStateListener.DataEnabledChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onDataEnabledChanged(boolean, int);
- }
-
- public static interface PhoneStateListener.OutgoingEmergencyCallListener {
- method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
- }
-
- public static interface PhoneStateListener.OutgoingEmergencySmsListener {
- method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int);
- }
-
- public static interface PhoneStateListener.PreciseCallStateChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
- }
-
- public static interface PhoneStateListener.RadioPowerStateChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int);
- }
-
- public static interface PhoneStateListener.SrvccStateChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSrvccStateChanged(int);
- }
-
- public static interface PhoneStateListener.VoiceActivationStateChangedListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onVoiceActivationStateChanged(int);
+ public final class PhysicalChannelConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getCellBandwidthDownlink();
+ method public int getChannelNumber();
+ method public int getConnectionStatus();
+ method public int getNetworkType();
+ method @IntRange(from=0, to=1007) public int getPhysicalCellId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CHANNEL_NUMBER_UNKNOWN = -1; // 0xffffffff
+ field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1
+ field public static final int CONNECTION_SECONDARY_SERVING = 2; // 0x2
+ field public static final int CONNECTION_UNKNOWN = -1; // 0xffffffff
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PhysicalChannelConfig> CREATOR;
+ field public static final int PHYSICAL_CELL_ID_UNKNOWN = -1; // 0xffffffff
}
public final class PinResult implements android.os.Parcelable {
@@ -10826,6 +10785,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int);
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Locale getSimLocale();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getSupportedRadioAccessFamily();
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.telephony.RadioAccessSpecifier> getSystemSelectionChannels();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.UiccSlotInfo[] getUiccSlotsInfo();
method @Nullable public android.os.Bundle getVisualVoicemailSettings();
@@ -11082,6 +11042,35 @@
field public static final String TYPE_XCAP_STRING = "xcap";
}
+ public final class ApnThrottleStatus implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getApnType();
+ method public int getRetryType();
+ method public int getSlotIndex();
+ method public long getThrottleExpiryTimeMillis();
+ method public int getThrottleType();
+ method public int getTransportType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.ApnThrottleStatus> CREATOR;
+ field public static final int RETRY_TYPE_HANDOVER = 3; // 0x3
+ field public static final int RETRY_TYPE_NEW_CONNECTION = 2; // 0x2
+ field public static final int RETRY_TYPE_NONE = 1; // 0x1
+ field public static final int THROTTLE_TYPE_ELAPSED_TIME = 2; // 0x2
+ field public static final int THROTTLE_TYPE_NONE = 1; // 0x1
+ }
+
+ public static final class ApnThrottleStatus.Builder {
+ ctor public ApnThrottleStatus.Builder();
+ method @NonNull public android.telephony.data.ApnThrottleStatus build();
+ method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setApnType(int);
+ method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setNoThrottle();
+ method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setRetryType(int);
+ method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setSlotIndex(int);
+ method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setThrottleExpiryTimeMillis(long);
+ method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setTransportType(int);
+ field public static final long NO_THROTTLE_EXPIRY_TIME = -1L; // 0xffffffffffffffffL
+ }
+
public final class DataCallResponse implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.List<android.net.LinkAddress> getAddresses();
@@ -11199,6 +11188,7 @@
method public abstract void close();
method public void deactivateDataCall(int, int, @Nullable android.telephony.data.DataServiceCallback);
method public final int getSlotIndex();
+ method public final void notifyApnUnthrottled(@NonNull String);
method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>);
method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback);
method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback);
@@ -11209,6 +11199,7 @@
}
public class DataServiceCallback {
+ method public void onApnUnthrottled(@NonNull String);
method public void onDataCallListChanged(@NonNull java.util.List<android.telephony.data.DataCallResponse>);
method public void onDeactivateDataCallComplete(int);
method public void onHandoverCancelled(int);
@@ -11234,6 +11225,7 @@
ctor public QualifiedNetworksService.NetworkAvailabilityProvider(int);
method public abstract void close();
method public final int getSlotIndex();
+ method public void reportApnThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ApnThrottleStatus>);
method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f695adf..52a79ba 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -302,8 +302,10 @@
method public void destroy();
method @NonNull public android.os.ParcelFileDescriptor[] executeShellCommandRwe(@NonNull String);
method @Deprecated public boolean grantRuntimePermission(String, String, android.os.UserHandle);
+ method public boolean injectInputEvent(@NonNull android.view.InputEvent, boolean, boolean);
method @Deprecated public boolean revokeRuntimePermission(String, String, android.os.UserHandle);
method public void syncInputTransactions();
+ method public void syncInputTransactions(boolean);
}
public class UiModeManager {
@@ -1227,7 +1229,6 @@
}
public static final class StrictMode.VmPolicy.Builder {
- method @NonNull public android.os.StrictMode.VmPolicy.Builder detectIncorrectContextUse();
method @NonNull public android.os.StrictMode.VmPolicy.Builder permitIncorrectContextUse();
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index b1b9f41..b15fa27 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -650,7 +650,6 @@
0);
flags = asAttributes.getInt(
com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
- flags |= FLAG_REQUEST_2_FINGER_PASSTHROUGH;
mSettingsActivityName = asAttributes.getString(
com.android.internal.R.styleable.AccessibilityService_settingsActivity);
if (asAttributes.getBoolean(com.android.internal.R.styleable
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d9f34d8..cdfe41e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -34,6 +34,7 @@
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.database.DatabaseUtils;
@@ -7617,7 +7618,7 @@
// Only collect app-ops when the proxy is trusted
&& (mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1,
myUid) == PackageManager.PERMISSION_GRANTED
- || isTrustedVoiceServiceProxy(mContext.getOpPackageName(), op))) {
+ || isTrustedVoiceServiceProxy(mContext, mContext.getOpPackageName(), op))) {
collectNotedOpSync(op, proxiedAttributionTag);
}
}
@@ -7628,30 +7629,43 @@
}
}
- private boolean isTrustedVoiceServiceProxy(String packageName, int code) {
+ /**
+ * Checks if the voice recognition service is a trust proxy.
+ *
+ * @return {@code true} if the package is a trust voice recognition service proxy
+ * @hide
+ */
+ public static boolean isTrustedVoiceServiceProxy(Context context, String packageName,
+ int code) {
// This is a workaround for R QPR, new API change is not allowed. We only allow the current
// voice recognizer is also the voice interactor to noteproxy op.
if (code != OP_RECORD_AUDIO) {
return false;
}
final String voiceRecognitionComponent = Settings.Secure.getString(
- mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
- final String voiceInteractionComponent = Settings.Secure.getString(
- mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE);
+ context.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
final String voiceRecognitionServicePackageName =
getComponentPackageNameFromString(voiceRecognitionComponent);
- final String voiceInteractionServicePackageName =
- getComponentPackageNameFromString(voiceInteractionComponent);
- return Objects.equals(packageName, voiceRecognitionServicePackageName) && Objects.equals(
- voiceRecognitionServicePackageName, voiceInteractionServicePackageName);
+ return (Objects.equals(packageName, voiceRecognitionServicePackageName))
+ && isPackagePreInstalled(context, packageName);
}
- private String getComponentPackageNameFromString(String from) {
+ private static String getComponentPackageNameFromString(String from) {
ComponentName componentName = from != null ? ComponentName.unflattenFromString(from) : null;
return componentName != null ? componentName.getPackageName() : "";
}
+ private static boolean isPackagePreInstalled(Context context, String packageName) {
+ try {
+ final PackageManager pm = context.getPackageManager();
+ final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+ return ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
/**
* Do a quick check for whether an application might be able to perform an operation.
* This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 9eeb9f6..ec7d783 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -36,8 +36,8 @@
interface IUiAutomationConnection {
void connect(IAccessibilityServiceClient client, int flags);
void disconnect();
- boolean injectInputEvent(in InputEvent event, boolean sync);
- void syncInputTransactions();
+ boolean injectInputEvent(in InputEvent event, boolean sync, boolean waitForAnimations);
+ void syncInputTransactions(boolean waitForAnimations);
boolean setRotation(int rotation);
Bitmap takeScreenshot(in Rect crop);
boolean clearWindowContentFrameStats(int windowId);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 3e249bb..e9d63d2 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1119,7 +1119,8 @@
}
try {
WindowManagerGlobal.getWindowManagerService().injectInputAfterTransactionsApplied(event,
- InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+ InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH,
+ true /* waitForAnimations */);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 75f7cec..2bf5368 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -67,6 +67,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.provider.Settings;
import android.text.BidiFormatter;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -4887,12 +4888,6 @@
mN.mUsesStandardHeader = false;
}
- private RemoteViews applyStandardTemplate(int resId, int viewType,
- TemplateBindResult result) {
- return applyStandardTemplate(resId,
- mParams.reset().viewType(viewType).fillTextsFrom(this), result);
- }
-
private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
TemplateBindResult result) {
p.headerless(resId == getBaseLayoutResource()
@@ -4906,7 +4901,7 @@
bindNotificationHeader(contentView, p);
bindLargeIconAndApplyMargin(contentView, p, result);
boolean showProgress = handleProgressBar(contentView, ex, p);
- if (p.title != null && p.title.length() > 0) {
+ if (p.title != null && p.title.length() > 0 && !p.mHasCustomContent) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
contentView.setTextViewText(R.id.title, processTextSpans(p.title));
setTextViewColorPrimary(contentView, R.id.title, p);
@@ -5301,6 +5296,12 @@
contentView.setViewVisibility(R.id.app_name_text, View.GONE);
return false;
}
+ if (p.mHeaderless && !p.mHasCustomContent) {
+ contentView.setViewVisibility(R.id.app_name_text, View.GONE);
+ // the headerless template will have the TITLE in this position; return true to
+ // keep the divider visible between that title and the next text element.
+ return true;
+ }
contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
if (isColorized(p)) {
@@ -5348,14 +5349,11 @@
big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
big.setTextViewText(R.id.notification_material_reply_text_3, null);
+ final boolean snoozeEnabled = mContext.getContentResolver() != null
+ && (Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1);
big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
- R.dimen.notification_content_margin);
- }
-
- private RemoteViews applyStandardTemplateWithActions(int layoutId, int viewType,
- TemplateBindResult result) {
- return applyStandardTemplateWithActions(layoutId,
- mParams.reset().viewType(viewType).fillTextsFrom(this), result);
+ snoozeEnabled ? 0 : R.dimen.notification_content_margin);
}
private static List<Notification.Action> filterOutContextualActions(
@@ -5495,8 +5493,10 @@
return styleView;
}
}
- return applyStandardTemplate(getBaseLayoutResource(),
- StandardTemplateParams.VIEW_TYPE_NORMAL, null /* result */);
+ StandardTemplateParams p = mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+ .fillTextsFrom(this);
+ return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */);
}
private boolean useExistingRemoteView() {
@@ -5516,14 +5516,27 @@
result = mStyle.makeBigContentView();
hideLine1Text(result);
}
- if (result == null) {
- result = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
- StandardTemplateParams.VIEW_TYPE_BIG, null /* result */);
+ if (result == null && bigContentViewRequired()) {
+ StandardTemplateParams p = mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .fillTextsFrom(this);
+ result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
+ null /* result */);
}
makeHeaderExpanded(result);
return result;
}
+ private boolean bigContentViewRequired() {
+ // If the big content view has no content, we can exempt the app from having to show it.
+ // TODO(b/173550917): add an UNDO style then force this requirement on apps targeting S
+ boolean exempt = mN.contentView != null && mN.bigContentView == null
+ && mStyle == null && mActions.size() == 0
+ && mN.extras.getCharSequence(EXTRA_TITLE) == null
+ && mN.extras.getCharSequence(EXTRA_TEXT) == null;
+ return !exempt;
+ }
+
/**
* Construct a RemoteViews for the final notification header only. This will not be
* colorized.
@@ -8689,18 +8702,24 @@
return makeStandardTemplateWithCustomContent(headsUpContentView);
}
TemplateBindResult result = new TemplateBindResult();
+ StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
+ .hasCustomContent(headsUpContentView != null)
+ .fillTextsFrom(mBuilder);
RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
- mBuilder.getHeadsUpBaseLayoutResource(),
- StandardTemplateParams.VIEW_TYPE_HEADS_UP, result);
+ mBuilder.getHeadsUpBaseLayoutResource(), p, result);
buildIntoRemoteViewContent(remoteViews, headsUpContentView, result, true);
return remoteViews;
}
private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
TemplateBindResult result = new TemplateBindResult();
+ StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+ .hasCustomContent(customContent != null)
+ .fillTextsFrom(mBuilder);
RemoteViews remoteViews = mBuilder.applyStandardTemplate(
- mBuilder.getBaseLayoutResource(),
- StandardTemplateParams.VIEW_TYPE_NORMAL, result);
+ mBuilder.getBaseLayoutResource(), p, result);
buildIntoRemoteViewContent(remoteViews, customContent, result, true);
return remoteViews;
}
@@ -8710,9 +8729,12 @@
? mBuilder.mN.contentView
: mBuilder.mN.bigContentView;
TemplateBindResult result = new TemplateBindResult();
+ StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .hasCustomContent(bigContentView != null)
+ .fillTextsFrom(mBuilder);
RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
- mBuilder.getBigBaseLayoutResource(),
- StandardTemplateParams.VIEW_TYPE_BIG, result);
+ mBuilder.getBigBaseLayoutResource(), p, result);
buildIntoRemoteViewContent(remoteViews, bigContentView, result, false);
return remoteViews;
}
@@ -11025,6 +11047,7 @@
int mViewType = VIEW_TYPE_UNSPECIFIED;
boolean mHeaderless;
+ boolean mHasCustomContent;
boolean hasProgress = true;
CharSequence title;
CharSequence text;
@@ -11038,6 +11061,7 @@
final StandardTemplateParams reset() {
mViewType = VIEW_TYPE_UNSPECIFIED;
mHeaderless = false;
+ mHasCustomContent = false;
hasProgress = true;
title = null;
text = null;
@@ -11064,6 +11088,11 @@
return this;
}
+ final StandardTemplateParams hasCustomContent(boolean hasCustomContent) {
+ this.mHasCustomContent = hasCustomContent;
+ return this;
+ }
+
final StandardTemplateParams title(CharSequence title) {
this.title = title;
return this;
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index f76a757..9dbf1ff6 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -179,6 +179,12 @@
* extras change, and don't care that any entities that received your
* previous PendingIntent will be able to launch it with your new
* extras even if they are not explicitly given to it.
+ *
+ * <p>{@link #FLAG_UPDATE_CURRENT} still works even if {@link
+ * #FLAG_IMMUTABLE} is set - the creator of the PendingIntent can always
+ * update the PendingIntent itself. The IMMUTABLE flag only limits the
+ * ability to alter the semantics of the intent that is sent by {@link
+ * #send} by the invoker of {@link #send}.
*/
public static final int FLAG_UPDATE_CURRENT = 1<<27;
@@ -187,6 +193,11 @@
* This means that the additional intent argument passed to the send
* methods to fill in unpopulated properties of this intent will be
* ignored.
+ *
+ * <p>{@link #FLAG_IMMUTABLE} only limits the ability to alter the
+ * semantics of the intent that is sent by {@link #send} by the invoker of
+ * {@link #send}. The creator of the PendingIntent can always update the
+ * PendingIntent itself via {@link #FLAG_UPDATE_CURRENT}.
*/
public static final int FLAG_IMMUTABLE = 1<<26;
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index b0307d1..d2be8a4 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -33,10 +33,10 @@
},
{
"file_patterns": ["(/|^)AppOpsManager.java"],
- "name": "CtsStatsdHostTestCases",
+ "name": "CtsStatsdAtomHostTestCases",
"options": [
{
- "include-filter": "android.cts.statsd.atom.UidAtomTests#testAppOps"
+ "include-filter": "android.cts.statsdatom.appops.AppOpsTests#testAppOps"
}
]
},
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 7fdf06f7..ca67dba 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -36,6 +36,7 @@
import android.window.WindowContainerToken;
import java.util.ArrayList;
+import java.util.Objects;
/**
* Stores information about a particular Task.
@@ -220,6 +221,12 @@
*/
public Rect parentBounds;
+ /**
+ * Whether this task is focused.
+ * @hide
+ */
+ public boolean isFocused;
+
TaskInfo() {
// Do nothing
}
@@ -288,6 +295,37 @@
}
/**
+ * Returns {@code true} if parameters that are important for task organizers have changed
+ * and {@link com.android.server.wm.TaskOrginizerController} needs to notify listeners
+ * about that.
+ * @hide
+ */
+ public boolean equalsForTaskOrganizer(@Nullable TaskInfo that) {
+ if (that == null) {
+ return false;
+ }
+ return topActivityType == that.topActivityType
+ && isResizeable == that.isResizeable
+ && Objects.equals(positionInParent, that.positionInParent)
+ && equalsLetterboxParams(that)
+ && pictureInPictureParams == that.pictureInPictureParams
+ && getWindowingMode() == that.getWindowingMode()
+ && Objects.equals(taskDescription, that.taskDescription)
+ && isFocused == that.isFocused;
+ }
+
+ private boolean equalsLetterboxParams(TaskInfo that) {
+ return Objects.equals(letterboxActivityBounds, that.letterboxActivityBounds)
+ && Objects.equals(
+ getConfiguration().windowConfiguration.getBounds(),
+ that.getConfiguration().windowConfiguration.getBounds())
+ && Objects.equals(
+ getConfiguration().windowConfiguration.getMaxBounds(),
+ that.getConfiguration().windowConfiguration.getMaxBounds())
+ && Objects.equals(parentBounds, that.parentBounds);
+ }
+
+ /**
* Reads the TaskInfo from a parcel.
*/
void readFromParcel(Parcel source) {
@@ -319,6 +357,7 @@
positionInParent = source.readTypedObject(Point.CREATOR);
parentTaskId = source.readInt();
parentBounds = source.readTypedObject(Rect.CREATOR);
+ isFocused = source.readBoolean();
}
/**
@@ -354,6 +393,7 @@
dest.writeTypedObject(positionInParent, flags);
dest.writeInt(parentTaskId);
dest.writeTypedObject(parentBounds, flags);
+ dest.writeBoolean(isFocused);
}
@Override
@@ -376,8 +416,9 @@
+ " launchCookies" + launchCookies
+ " letterboxActivityBounds=" + letterboxActivityBounds
+ " positionInParent=" + positionInParent
- + " parentTaskId: " + parentTaskId
- + " parentBounds: " + parentBounds
+ + " parentTaskId=" + parentTaskId
+ + " parentBounds=" + parentBounds
+ + " isFocused=" + isFocused
+ "}";
}
}
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 1b0fd9e..787393e 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -695,6 +695,9 @@
/**
* A method for injecting an arbitrary input event.
+ *
+ * This method waits for all window container animations and surface operations to complete.
+ *
* <p>
* <strong>Note:</strong> It is caller's responsibility to recycle the event.
* </p>
@@ -704,12 +707,34 @@
* @return Whether event injection succeeded.
*/
public boolean injectInputEvent(InputEvent event, boolean sync) {
+ return injectInputEvent(event, sync, true /* waitForAnimations */);
+ }
+
+ /**
+ * A method for injecting an arbitrary input event, optionally waiting for window animations to
+ * complete.
+ * <p>
+ * <strong>Note:</strong> It is caller's responsibility to recycle the event.
+ * </p>
+ *
+ * @param event The event to inject.
+ * @param sync Whether to inject the event synchronously.
+ * @param waitForAnimations Whether to wait for all window container animations and surface
+ * operations to complete.
+ * @return Whether event injection succeeded.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean injectInputEvent(@NonNull InputEvent event, boolean sync,
+ boolean waitForAnimations) {
try {
if (DEBUG) {
- Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
+ Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync + " waitForAnimations: "
+ + waitForAnimations);
}
// Calling out without a lock held.
- return mUiAutomationConnection.injectInputEvent(event, sync);
+ return mUiAutomationConnection.injectInputEvent(event, sync, waitForAnimations);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while injecting input event!", re);
}
@@ -726,7 +751,26 @@
public void syncInputTransactions() {
try {
// Calling out without a lock held.
- mUiAutomationConnection.syncInputTransactions();
+ mUiAutomationConnection.syncInputTransactions(true /* waitForAnimations */);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while syncing input transactions!", re);
+ }
+ }
+
+ /**
+ * A request for WindowManagerService to wait until all input information has been sent from
+ * WindowManager to native InputManager and optionally wait for animations to complete.
+ *
+ * @param waitForAnimations Whether to wait for all window container animations and surface
+ * operations to complete.
+ *
+ * @hide
+ */
+ @TestApi
+ public void syncInputTransactions(boolean waitForAnimations) {
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.syncInputTransactions(waitForAnimations);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while syncing input transactions!", re);
}
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 290e121..7036b6e 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -124,7 +124,7 @@
}
@Override
- public boolean injectInputEvent(InputEvent event, boolean sync) {
+ public boolean injectInputEvent(InputEvent event, boolean sync, boolean waitForAnimations) {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
@@ -134,7 +134,8 @@
: InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
final long identity = Binder.clearCallingIdentity();
try {
- return mWindowManager.injectInputAfterTransactionsApplied(event, mode);
+ return mWindowManager.injectInputAfterTransactionsApplied(event, mode,
+ waitForAnimations);
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(identity);
@@ -143,7 +144,7 @@
}
@Override
- public void syncInputTransactions() {
+ public void syncInputTransactions(boolean waitForAnimations) {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
@@ -151,12 +152,11 @@
}
try {
- mWindowManager.syncInputTransactions();
+ mWindowManager.syncInputTransactions(waitForAnimations);
} catch (RemoteException e) {
}
}
-
@Override
public boolean setRotation(int rotation) {
synchronized (mLock) {
diff --git a/core/java/android/app/WaitResult.java b/core/java/android/app/WaitResult.java
index d65be9b..77891e0 100644
--- a/core/java/android/app/WaitResult.java
+++ b/core/java/android/app/WaitResult.java
@@ -40,14 +40,21 @@
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"LAUNCH_STATE_"}, value = {
+ LAUNCH_STATE_UNKNOWN,
LAUNCH_STATE_COLD,
LAUNCH_STATE_WARM,
- LAUNCH_STATE_HOT
+ LAUNCH_STATE_HOT,
+ LAUNCH_STATE_RELAUNCH
})
public @interface LaunchState {
}
/**
+ * Not considered as a launch event, e.g. the activity is already on top.
+ */
+ public static final int LAUNCH_STATE_UNKNOWN = 0;
+
+ /**
* Cold launch sequence: a new process has started.
*/
public static final int LAUNCH_STATE_COLD = 1;
@@ -62,6 +69,13 @@
*/
public static final int LAUNCH_STATE_HOT = 3;
+ /**
+ * Relaunch launch sequence: process reused, but activity has to be destroyed and created.
+ * E.g. the current device configuration is different from the background activity that will be
+ * brought to foreground, and the activity doesn't declare to handle the change.
+ */
+ public static final int LAUNCH_STATE_RELAUNCH = 4;
+
public static final int INVALID_DELAY = -1;
public int result;
public boolean timeout;
@@ -124,6 +138,8 @@
return "WARM";
case LAUNCH_STATE_HOT:
return "HOT";
+ case LAUNCH_STATE_RELAUNCH:
+ return "RELAUNCH";
default:
return "UNKNOWN (" + type + ")";
}
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
new file mode 100644
index 0000000..f5674e5
--- /dev/null
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.people;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.notification.StatusBarNotification;
+
+/**
+ * The People Space tile contains all relevant information to render a tile in People Space: namely
+ * the data of any visible conversation notification associated, associated statuses, and the last
+ * interaction time.
+ *
+ * @hide
+ */
+public class PeopleSpaceTile implements Parcelable {
+
+ private String mId;
+ private CharSequence mUserName;
+ private Icon mUserIcon;
+ private int mUid;
+ private Uri mContactUri;
+ private String mPackageName;
+ private long mLastInteractionTimestamp;
+ private boolean mIsImportantConversation;
+ private boolean mIsHiddenConversation;
+ private StatusBarNotification mNotification;
+ private Intent mIntent;
+ // TODO: add a List of the Status objects once created
+
+ private PeopleSpaceTile(Builder b) {
+ mId = b.mId;
+ mUserName = b.mUserName;
+ mUserIcon = b.mUserIcon;
+ mContactUri = b.mContactUri;
+ mUid = b.mUid;
+ mPackageName = b.mPackageName;
+ mLastInteractionTimestamp = b.mLastInteractionTimestamp;
+ mIsImportantConversation = b.mIsImportantConversation;
+ mIsHiddenConversation = b.mIsHiddenConversation;
+ mNotification = b.mNotification;
+ mIntent = b.mIntent;
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ public CharSequence getUserName() {
+ return mUserName;
+ }
+
+ public Icon getUserIcon() {
+ return mUserIcon;
+ }
+
+ /** Returns the Uri associated with the user in Android Contacts database. */
+ public Uri getContactUri() {
+ return mContactUri;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /** Returns the timestamp of the last interaction. */
+ public long getLastInteractionTimestamp() {
+ return mLastInteractionTimestamp;
+ }
+
+ /**
+ * Whether the conversation is important.
+ */
+ public boolean isImportantConversation() {
+ return mIsImportantConversation;
+ }
+
+ /**
+ * Whether the conversation should be hidden.
+ */
+ public boolean isHiddenConversation() {
+ return mIsHiddenConversation;
+ }
+
+ /**
+ * If a notification is currently active that maps to the relevant shortcut ID, provides the
+ * {@link StatusBarNotification} associated.
+ */
+ public StatusBarNotification getNotification() {
+ return mNotification;
+ }
+
+ /**
+ * Provides an intent to launch. If present, we should manually launch the intent on tile
+ * click, rather than calling {@link android.content.pm.LauncherApps} to launch the shortcut ID.
+ *
+ * <p>This field should only be used if manually constructing a tile without an associated
+ * shortcut to launch (i.e. birthday tiles).
+ */
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /** Builder to create a {@link PeopleSpaceTile}. */
+ public static class Builder {
+ private String mId;
+ private CharSequence mUserName;
+ private Icon mUserIcon;
+ private Uri mContactUri;
+ private int mUid;
+ private String mPackageName;
+ private long mLastInteractionTimestamp;
+ private boolean mIsImportantConversation;
+ private boolean mIsHiddenConversation;
+ private StatusBarNotification mNotification;
+ private Intent mIntent;
+
+ /** Builder for use only if a shortcut is not available for the tile. */
+ public Builder(String id, String userName, Icon userIcon, Intent intent) {
+ mId = id;
+ mUserName = userName;
+ mUserIcon = userIcon;
+ mIntent = intent;
+ mPackageName = intent == null ? null : intent.getPackage();
+ }
+
+ public Builder(ShortcutInfo info) {
+ mId = info.getId();
+ mUserName = info.getLabel();
+ mUserIcon = info.getIcon();
+ mUid = info.getUserId();
+ mPackageName = info.getPackage();
+ }
+
+ /** Sets the ID for the tile. */
+ public Builder setId(String id) {
+ mId = id;
+ return this;
+ }
+
+ /** Sets the user name. */
+ public Builder setUserName(CharSequence userName) {
+ mUserName = userName;
+ return this;
+ }
+
+ /** Sets the icon shown for the user. */
+ public Builder setUserIcon(Icon userIcon) {
+ mUserIcon = userIcon;
+ return this;
+ }
+
+ /** Sets the Uri associated with the user in Android Contacts database. */
+ public Builder setContactUri(Uri uri) {
+ mContactUri = uri;
+ return this;
+ }
+
+ /** Sets the associated uid. */
+ public Builder setUid(int uid) {
+ mUid = uid;
+ return this;
+ }
+
+ /** Sets the package shown that provided the information. */
+ public Builder setPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ /** Sets the last interaction timestamp. */
+ public Builder setLastInteractionTimestamp(long lastInteractionTimestamp) {
+ mLastInteractionTimestamp = lastInteractionTimestamp;
+ return this;
+ }
+
+ /** Sets whether the conversation is important. */
+ public Builder setIsImportantConversation(boolean isImportantConversation) {
+ mIsImportantConversation = isImportantConversation;
+ return this;
+ }
+
+ /** Sets whether the conversation is hidden. */
+ public Builder setIsHiddenConversation(boolean isHiddenConversation) {
+ mIsHiddenConversation = isHiddenConversation;
+ return this;
+ }
+
+ /** Sets the associated notification. */
+ public Builder setNotification(StatusBarNotification notification) {
+ mNotification = notification;
+ return this;
+ }
+
+ /** Sets an intent to launch on click. */
+ public Builder setIntent(Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /** Builds a {@link PeopleSpaceTile}. */
+ @NonNull
+ public PeopleSpaceTile build() {
+ return new PeopleSpaceTile(this);
+ }
+ }
+
+ private PeopleSpaceTile(Parcel in) {
+ mId = in.readString();
+ mUserName = in.readCharSequence();
+ mUserIcon = in.readParcelable(Icon.class.getClassLoader());
+ mUid = in.readInt();
+ mPackageName = in.readString();
+ mLastInteractionTimestamp = in.readLong();
+ mIsImportantConversation = in.readBoolean();
+ mIsHiddenConversation = in.readBoolean();
+ mNotification = in.readParcelable(StatusBarNotification.class.getClassLoader());
+ mIntent = in.readParcelable(Intent.class.getClassLoader());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeCharSequence(mUserName);
+ dest.writeParcelable(mUserIcon, flags);
+ dest.writeInt(mUid);
+ dest.writeString(mPackageName);
+ dest.writeLong(mLastInteractionTimestamp);
+ dest.writeParcelable(mNotification, flags);
+ dest.writeBoolean(mIsImportantConversation);
+ dest.writeBoolean(mIsHiddenConversation);
+ dest.writeParcelable(mIntent, flags);
+ }
+
+ public static final @android.annotation.NonNull
+ Creator<PeopleSpaceTile> CREATOR = new Creator<PeopleSpaceTile>() {
+ public PeopleSpaceTile createFromParcel(Parcel source) {
+ return new PeopleSpaceTile(source);
+ }
+
+ public PeopleSpaceTile[] newArray(int size) {
+ return new PeopleSpaceTile[size];
+ }
+ };
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 37e0a44..6a3f6b4 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -63,6 +63,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.StatFs;
+import android.os.StrictMode;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
@@ -6285,4 +6286,25 @@
public boolean isUiContext() {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
+
+ /**
+ * Returns {@code true} if the context is a UI context which can access UI components such as
+ * {@link WindowManager}, {@link android.view.LayoutInflater LayoutInflater} or
+ * {@link android.app.WallpaperManager WallpaperManager}. Accessing UI components from non-UI
+ * contexts throws {@link android.os.strictmode.Violation} if
+ * {@link StrictMode.VmPolicy.Builder#detectIncorrectContextUse()} is enabled.
+ * <p>
+ * Examples of UI contexts are
+ * an {@link android.app.Activity Activity}, a context created from
+ * {@link #createWindowContext(int, Bundle)} or
+ * {@link android.inputmethodservice.InputMethodService InputMethodService}
+ * </p>
+ *
+ * @see #getDisplay()
+ * @see #getSystemService(String)
+ * @see StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
+ */
+ public static boolean isUiContext(@NonNull Context context) {
+ return context.isUiContext();
+ }
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index c964b4b..4b7bbbf 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -433,6 +433,16 @@
*/
public static final int FLAG_GET_KEY_FIELDS_ONLY = 1 << 2;
+ /**
+ * Populate the persons field in the result. See {@link ShortcutInfo#getPersons()}.
+ *
+ * <p>The caller must have the system {@code ACCESS_SHORTCUTS} permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_GET_PERSONS_DATA = 1 << 11;
+
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_MATCH_DYNAMIC,
@@ -440,6 +450,7 @@
FLAG_MATCH_MANIFEST,
FLAG_MATCH_CACHED,
FLAG_GET_KEY_FIELDS_ONLY,
+ FLAG_GET_PERSONS_DATA,
})
@Retention(RetentionPolicy.SOURCE)
public @interface QueryFlags {}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index cc484de..9e0c30a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -64,6 +64,8 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -75,6 +77,7 @@
import android.util.AndroidException;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import dalvik.system.VMRuntime;
@@ -116,6 +119,213 @@
}
/**
+ * A property value set within the manifest.
+ * <p>
+ * The value of a property will only have a single type, as defined by
+ * the property itself.
+ */
+ public static final class Property implements Parcelable {
+ private static final int TYPE_BOOLEAN = 1;
+ private static final int TYPE_FLOAT = 2;
+ private static final int TYPE_INTEGER = 3;
+ private static final int TYPE_RESOURCE = 4;
+ private static final int TYPE_STRING = 5;
+ private final String mName;
+ private boolean mBooleanValue;
+ private float mFloatValue;
+ private int mIntegerValue;
+ private String mStringValue;
+ private final int mType;
+
+ /** @hide */
+ @VisibleForTesting
+ public Property(@NonNull String name, int type) {
+ assert name != null;
+ assert type >= TYPE_BOOLEAN && type <= TYPE_STRING;
+ this.mName = name;
+ this.mType = type;
+ }
+ /** @hide */
+ public Property(@NonNull String name, boolean value) {
+ this(name, TYPE_BOOLEAN);
+ mBooleanValue = value;
+ }
+ /** @hide */
+ public Property(@NonNull String name, float value) {
+ this(name, TYPE_FLOAT);
+ mFloatValue = value;
+ }
+ /** @hide */
+ public Property(@NonNull String name, int value, boolean isResource) {
+ this(name, isResource ? TYPE_RESOURCE : TYPE_INTEGER);
+ mIntegerValue = value;
+ }
+ /** @hide */
+ public Property(@NonNull String name, String value) {
+ this(name, TYPE_STRING);
+ mStringValue = value;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the name of the property.
+ */
+ @NonNull public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the boolean value set for the property.
+ * <p>If the property is not of a boolean type, returns {@code false}.
+ */
+ public boolean getBoolean() {
+ return mBooleanValue;
+ }
+
+ /**
+ * Returns {@code true} if the property is a boolean type. Otherwise {@code false}.
+ */
+ public boolean isBoolean() {
+ return mType == TYPE_BOOLEAN;
+ }
+
+ /**
+ * Returns the float value set for the property.
+ * <p>If the property is not of a float type, returns {@code 0.0}.
+ */
+ public float getFloat() {
+ return mFloatValue;
+ }
+
+ /**
+ * Returns {@code true} if the property is a float type. Otherwise {@code false}.
+ */
+ public boolean isFloat() {
+ return mType == TYPE_FLOAT;
+ }
+
+ /**
+ * Returns the integer value set for the property.
+ * <p>If the property is not of an integer type, returns {@code 0}.
+ */
+ public int getInteger() {
+ return mType == TYPE_INTEGER ? mIntegerValue : 0;
+ }
+
+ /**
+ * Returns {@code true} if the property is an integer type. Otherwise {@code false}.
+ */
+ public boolean isInteger() {
+ return mType == TYPE_INTEGER;
+ }
+
+ /**
+ * Returns the a resource id set for the property.
+ * <p>If the property is not of a resource id type, returns {@code 0}.
+ */
+ public int getResourceId() {
+ return mType == TYPE_RESOURCE ? mIntegerValue : 0;
+ }
+
+ /**
+ * Returns {@code true} if the property is a resource id type. Otherwise {@code false}.
+ */
+ public boolean isResourceId() {
+ return mType == TYPE_RESOURCE;
+ }
+
+ /**
+ * Returns the a String value set for the property.
+ * <p>If the property is not a String type, returns {@code null}.
+ */
+ @Nullable public String getString() {
+ return mStringValue;
+ }
+
+ /**
+ * Returns {@code true} if the property is a String type. Otherwise {@code false}.
+ */
+ public boolean isString() {
+ return mType == TYPE_STRING;
+ }
+
+ /**
+ * Adds a mapping from the given key to this property's value in the provided
+ * {@link android.os.Bundle}. If the provided {@link android.os.Bundle} is
+ * {@code null}, creates a new {@link android.os.Bundle}.
+ * @hide
+ */
+ public Bundle toBundle(Bundle outBundle) {
+ final Bundle b = outBundle == null ? new Bundle() : outBundle;
+ if (mType == TYPE_BOOLEAN) {
+ b.putBoolean(mName, mBooleanValue);
+ } else if (mType == TYPE_FLOAT) {
+ b.putFloat(mName, mFloatValue);
+ } else if (mType == TYPE_INTEGER) {
+ b.putInt(mName, mIntegerValue);
+ } else if (mType == TYPE_RESOURCE) {
+ b.putInt(mName, mIntegerValue);
+ } else if (mType == TYPE_STRING) {
+ b.putString(mName, mStringValue);
+ }
+ return b;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mName);
+ dest.writeInt(mType);
+ if (mType == TYPE_BOOLEAN) {
+ dest.writeBoolean(mBooleanValue);
+ } else if (mType == TYPE_FLOAT) {
+ dest.writeFloat(mFloatValue);
+ } else if (mType == TYPE_INTEGER) {
+ dest.writeInt(mIntegerValue);
+ } else if (mType == TYPE_RESOURCE) {
+ dest.writeInt(mIntegerValue);
+ } else if (mType == TYPE_STRING) {
+ dest.writeString(mStringValue);
+ }
+ }
+
+ @NonNull
+ public static final Creator<Property> CREATOR = new Creator<Property>() {
+ @Override
+ public Property createFromParcel(@NonNull Parcel source) {
+ final String name = source.readString();
+ final int type = source.readInt();
+ if (type == TYPE_BOOLEAN) {
+ return new Property(name, source.readBoolean());
+ } else if (type == TYPE_FLOAT) {
+ return new Property(name, source.readFloat());
+ } else if (type == TYPE_INTEGER) {
+ return new Property(name, source.readInt(), false);
+ } else if (type == TYPE_RESOURCE) {
+ return new Property(name, source.readInt(), true);
+ } else if (type == TYPE_STRING) {
+ return new Property(name, source.readString());
+ }
+ return null;
+ }
+
+ @Override
+ public Property[] newArray(int size) {
+ return new Property[size];
+ }
+ };
+ }
+
+ /**
* Listener for changes in permissions granted to a UID.
*
* @hide
@@ -8124,7 +8334,7 @@
* Trust any Installer to provide checksums for the package.
* @see #requestChecksums
*/
- public static final @Nullable List<Certificate> TRUST_ALL = Collections.singletonList(null);
+ public static final @NonNull List<Certificate> TRUST_ALL = Collections.singletonList(null);
/**
* Don't trust any Installer to provide checksums for the package.
diff --git a/core/java/android/hardware/CameraSessionStats.java b/core/java/android/hardware/CameraSessionStats.java
new file mode 100644
index 0000000..f34e2bf
--- /dev/null
+++ b/core/java/android/hardware/CameraSessionStats.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+/**
+ * The camera action state used for passing camera usage information from
+ * camera service to camera service proxy .
+ *
+ * Include camera id, facing, state, client apk name, apiLevel, isNdk,
+ * and session/stream statistics.
+ *
+ * @hide
+ */
+public class CameraSessionStats implements Parcelable {
+ public static final int CAMERA_STATE_OPEN = 0;
+ public static final int CAMERA_STATE_ACTIVE = 1;
+ public static final int CAMERA_STATE_IDLE = 2;
+ public static final int CAMERA_STATE_CLOSED = 3;
+
+ /**
+ * Values for notifyCameraState facing
+ */
+ public static final int CAMERA_FACING_BACK = 0;
+ public static final int CAMERA_FACING_FRONT = 1;
+ public static final int CAMERA_FACING_EXTERNAL = 2;
+
+ /**
+ * Values for notifyCameraState api level
+ */
+ public static final int CAMERA_API_LEVEL_1 = 1;
+ public static final int CAMERA_API_LEVEL_2 = 2;
+
+ private String mCameraId;
+ private int mFacing;
+ private int mNewCameraState;
+ private String mClientName;
+ private int mApiLevel;
+ private boolean mIsNdk;
+ private int mLatencyMs;
+ private int mSessionType;
+ private int mInternalReconfigure;
+ private long mRequestCount;
+ private long mResultErrorCount;
+ private boolean mDeviceError;
+ private ArrayList<CameraStreamStats> mStreamStats;
+
+ public CameraSessionStats() {
+ mFacing = -1;
+ mNewCameraState = -1;
+ mApiLevel = -1;
+ mIsNdk = false;
+ mLatencyMs = -1;
+ mSessionType = -1;
+ mInternalReconfigure = -1;
+ mRequestCount = 0;
+ mResultErrorCount = 0;
+ mDeviceError = false;
+ mStreamStats = new ArrayList<CameraStreamStats>();
+ }
+
+ public CameraSessionStats(String cameraId, int facing, int newCameraState,
+ String clientName, int apiLevel, boolean isNdk, int creationDuration,
+ int sessionType, int internalReconfigure) {
+ mCameraId = cameraId;
+ mFacing = facing;
+ mNewCameraState = newCameraState;
+ mClientName = clientName;
+ mApiLevel = apiLevel;
+ mIsNdk = isNdk;
+ mLatencyMs = creationDuration;
+ mSessionType = sessionType;
+ mInternalReconfigure = internalReconfigure;
+ mStreamStats = new ArrayList<CameraStreamStats>();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<CameraSessionStats> CREATOR =
+ new Parcelable.Creator<CameraSessionStats>() {
+ @Override
+ public CameraSessionStats createFromParcel(Parcel in) {
+ return new CameraSessionStats(in);
+ }
+
+ @Override
+ public CameraSessionStats[] newArray(int size) {
+ return new CameraSessionStats[size];
+ }
+ };
+
+ private CameraSessionStats(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mCameraId);
+ dest.writeInt(mFacing);
+ dest.writeInt(mNewCameraState);
+ dest.writeString(mClientName);
+ dest.writeInt(mApiLevel);
+ dest.writeBoolean(mIsNdk);
+ dest.writeInt(mLatencyMs);
+ dest.writeInt(mSessionType);
+ dest.writeInt(mInternalReconfigure);
+ dest.writeLong(mRequestCount);
+ dest.writeLong(mResultErrorCount);
+ dest.writeBoolean(mDeviceError);
+ dest.writeTypedList(mStreamStats);
+ }
+
+ public void readFromParcel(Parcel in) {
+ mCameraId = in.readString();
+ mFacing = in.readInt();
+ mNewCameraState = in.readInt();
+ mClientName = in.readString();
+ mApiLevel = in.readInt();
+ mIsNdk = in.readBoolean();
+ mLatencyMs = in.readInt();
+ mSessionType = in.readInt();
+ mInternalReconfigure = in.readInt();
+ mRequestCount = in.readLong();
+ mResultErrorCount = in.readLong();
+ mDeviceError = in.readBoolean();
+
+ ArrayList<CameraStreamStats> streamStats = new ArrayList<CameraStreamStats>();
+ in.readTypedList(streamStats, CameraStreamStats.CREATOR);
+ mStreamStats = streamStats;
+ }
+
+ public String getCameraId() {
+ return mCameraId;
+ }
+
+ public int getFacing() {
+ return mFacing;
+ }
+
+ public int getNewCameraState() {
+ return mNewCameraState;
+ }
+
+ public String getClientName() {
+ return mClientName;
+ }
+
+ public int getApiLevel() {
+ return mApiLevel;
+ }
+
+ public boolean isNdk() {
+ return mIsNdk;
+ }
+
+ public int getLatencyMs() {
+ return mLatencyMs;
+ }
+
+ public int getSessionType() {
+ return mSessionType;
+ }
+
+ public int getInternalReconfigureCount() {
+ return mInternalReconfigure;
+ }
+
+ public long getRequestCount() {
+ return mRequestCount;
+ }
+
+ public long getResultErrorCount() {
+ return mResultErrorCount;
+ }
+
+ public boolean getDeviceErrorFlag() {
+ return mDeviceError;
+ }
+
+ public List<CameraStreamStats> getStreamStats() {
+ return mStreamStats;
+ }
+}
diff --git a/core/java/android/hardware/CameraStreamStats.java b/core/java/android/hardware/CameraStreamStats.java
new file mode 100644
index 0000000..ae801b6
--- /dev/null
+++ b/core/java/android/hardware/CameraStreamStats.java
@@ -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 android.hardware;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+/**
+ * The camera stream statistics used for passing camera stream information from
+ * camera service to camera service proxy.
+ *
+ * Include camera stream configuration, request/error counts, startup latency,
+ * and latency/jitter histograms.
+ *
+ * @hide
+ */
+public class CameraStreamStats implements Parcelable {
+
+ private int mWidth;
+ private int mHeight;
+ private int mFormat;
+ private int mDataSpace;
+ private long mUsage;
+ private long mRequestCount;
+ private long mErrorCount;
+ private int mStartLatencyMs;
+ private int mMaxHalBuffers;
+ private int mMaxAppBuffers;
+
+ private static final String TAG = "CameraStreamStats";
+
+ public CameraStreamStats() {
+ mWidth = 0;
+ mHeight = 0;
+ mFormat = 0;
+ mDataSpace = 0;
+ mUsage = 0;
+ mRequestCount = 0;
+ mErrorCount = 0;
+ mStartLatencyMs = 0;
+ mMaxHalBuffers = 0;
+ mMaxAppBuffers = 0;
+ }
+
+ public CameraStreamStats(int width, int height, int format,
+ int dataSpace, long usage, long requestCount, long errorCount,
+ int startLatencyMs, int maxHalBuffers, int maxAppBuffers) {
+ mWidth = width;
+ mHeight = height;
+ mFormat = format;
+ mDataSpace = dataSpace;
+ mUsage = usage;
+ mRequestCount = requestCount;
+ mErrorCount = errorCount;
+ mStartLatencyMs = startLatencyMs;
+ mMaxHalBuffers = maxHalBuffers;
+ mMaxAppBuffers = maxAppBuffers;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<CameraStreamStats> CREATOR =
+ new Parcelable.Creator<CameraStreamStats>() {
+ @Override
+ public CameraStreamStats createFromParcel(Parcel in) {
+ try {
+ CameraStreamStats streamStats = new CameraStreamStats(in);
+ return streamStats;
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating CameraStreamStats from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public CameraStreamStats[] newArray(int size) {
+ return new CameraStreamStats[size];
+ }
+ };
+
+ private CameraStreamStats(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mWidth);
+ dest.writeInt(mHeight);
+ dest.writeInt(mFormat);
+ dest.writeInt(mDataSpace);
+ dest.writeLong(mUsage);
+ dest.writeLong(mRequestCount);
+ dest.writeLong(mErrorCount);
+ dest.writeInt(mStartLatencyMs);
+ dest.writeInt(mMaxHalBuffers);
+ dest.writeInt(mMaxAppBuffers);
+ }
+
+ public void readFromParcel(Parcel in) {
+ mWidth = in.readInt();
+ mHeight = in.readInt();
+ mFormat = in.readInt();
+ mDataSpace = in.readInt();
+ mUsage = in.readLong();
+ mRequestCount = in.readLong();
+ mErrorCount = in.readLong();
+ mStartLatencyMs = in.readInt();
+ mMaxHalBuffers = in.readInt();
+ mMaxAppBuffers = in.readInt();
+ }
+
+ public int getWidth() {
+ return mWidth;
+ }
+
+ public int getHeight() {
+ return mHeight;
+ }
+
+ public int getFormat() {
+ return mFormat;
+ }
+
+ public int getDataSpace() {
+ return mDataSpace;
+ }
+
+ public long getUsage() {
+ return mUsage;
+ }
+
+ public long getRequestCount() {
+ return mRequestCount;
+ }
+
+ public long getErrorCount() {
+ return mErrorCount;
+ }
+
+ public int getStartLatencyMs() {
+ return mStartLatencyMs;
+ }
+
+ public int getMaxHalBuffers() {
+ return mMaxHalBuffers;
+ }
+
+ public int getMaxAppBuffers() {
+ return mMaxAppBuffers;
+ }
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index b6f4bd3..9a9163c 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -27,6 +27,7 @@
import android.hardware.camera2.utils.TaskSingleDrainer;
import android.os.Binder;
import android.os.Handler;
+import android.os.SystemClock;
import android.util.Log;
import android.view.Surface;
@@ -1002,7 +1003,7 @@
// begin transition to unconfigured
mDeviceImpl.configureStreamsChecked(/*inputConfig*/null, /*outputs*/null,
/*operatingMode*/ ICameraDeviceUser.NORMAL_MODE,
- /*sessionParams*/ null);
+ /*sessionParams*/ null, SystemClock.uptimeMillis());
} catch (CameraAccessException e) {
// OK: do not throw checked exceptions.
Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e);
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 819d966..f564ad7 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -45,6 +45,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.os.SystemClock;
import android.util.Log;
import android.util.Range;
import android.util.Size;
@@ -374,7 +375,8 @@
outputConfigs.add(new OutputConfiguration(s));
}
configureStreamsChecked(/*inputConfig*/null, outputConfigs,
- /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
+ /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null,
+ SystemClock.uptimeMillis());
}
@@ -395,12 +397,15 @@
* @param operatingMode If the stream configuration is for a normal session,
* a constrained high speed session, or something else.
* @param sessionParams Session parameters.
+ * @param createSessionStartTimeMs The timestamp when session creation starts, measured by
+ * uptimeMillis().
* @return whether or not the configuration was successful
*
* @throws CameraAccessException if there were any unexpected problems during configuration
*/
public boolean configureStreamsChecked(InputConfiguration inputConfig,
- List<OutputConfiguration> outputs, int operatingMode, CaptureRequest sessionParams)
+ List<OutputConfiguration> outputs, int operatingMode, CaptureRequest sessionParams,
+ long createSessionStartTime)
throws CameraAccessException {
// Treat a null input the same an empty list
if (outputs == null) {
@@ -479,9 +484,10 @@
int offlineStreamIds[];
if (sessionParams != null) {
offlineStreamIds = mRemoteDevice.endConfigure(operatingMode,
- sessionParams.getNativeCopy());
+ sessionParams.getNativeCopy(), createSessionStartTime);
} else {
- offlineStreamIds = mRemoteDevice.endConfigure(operatingMode, null);
+ offlineStreamIds = mRemoteDevice.endConfigure(operatingMode, null,
+ createSessionStartTime);
}
mOfflineSupport.clear();
@@ -650,6 +656,7 @@
List<OutputConfiguration> outputConfigurations,
CameraCaptureSession.StateCallback callback, Executor executor,
int operatingMode, CaptureRequest sessionParams) throws CameraAccessException {
+ long createSessionStartTime = SystemClock.uptimeMillis();
synchronized(mInterfaceLock) {
if (DEBUG) {
Log.d(TAG, "createCaptureSessionInternal");
@@ -677,7 +684,7 @@
try {
// configure streams and then block until IDLE
configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
- operatingMode, sessionParams);
+ operatingMode, sessionParams, createSessionStartTime);
if (configureSuccess == true && inputConfig != null) {
input = mRemoteDevice.getInputSurface();
}
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index fa7301b..ba4395f 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -110,11 +110,11 @@
}
}
- public int[] endConfigure(int operatingMode, CameraMetadataNative sessionParams)
- throws CameraAccessException {
+ public int[] endConfigure(int operatingMode, CameraMetadataNative sessionParams,
+ long startTimeMs) throws CameraAccessException {
try {
return mRemoteDevice.endConfigure(operatingMode, (sessionParams == null) ?
- new CameraMetadataNative() : sessionParams);
+ new CameraMetadataNative() : sessionParams, startTimeMs);
} catch (Throwable t) {
CameraManager.throwAsPublicException(t);
throw new UnsupportedOperationException("Unexpected exception", t);
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index defcab7..5a03ade 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -65,6 +65,12 @@
public abstract boolean isProximitySensorAvailable();
/**
+ * Returns the id of the {@link com.android.server.display.DisplayGroup} to which the provided
+ * display belongs.
+ */
+ public abstract int getDisplayGroupId(int displayId);
+
+ /**
* Screenshot for internal system-only use such as rotation, etc. This method includes
* secure layers and the result should never be exposed to non-system applications.
* This method does not apply any rotation and provides the output in natural orientation.
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index f1534d9..eef4089 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -832,6 +832,22 @@
}
/**
+ * For CEC source devices (OTT/STB/Audio system): toggle the power status of the HDMI-connected
+ * display and follow the display's new power status.
+ * For all other devices: no functionality.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public void toggleAndFollowTvPower() {
+ try {
+ mService.toggleAndFollowTvPower();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Controls whether volume control commands via HDMI CEC are enabled.
*
* <p>When disabled:
diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
index fab56b8..202e090 100644
--- a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
+++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
@@ -67,6 +67,11 @@
}
@Override
+ public void toggleAndFollowTvPower() {
+ HdmiControlServiceWrapper.this.toggleAndFollowTvPower();
+ }
+
+ @Override
public void queryDisplayStatus(IHdmiControlCallback callback) {
HdmiControlServiceWrapper.this.queryDisplayStatus(callback);
}
@@ -360,6 +365,9 @@
public void oneTouchPlay(IHdmiControlCallback callback) {}
/** @hide */
+ public void toggleAndFollowTvPower() {}
+
+ /** @hide */
public void queryDisplayStatus(IHdmiControlCallback callback) {}
/** @hide */
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index af9d3ac..6d0c688 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -42,6 +42,7 @@
int[] getSupportedTypes();
HdmiDeviceInfo getActiveSource();
void oneTouchPlay(IHdmiControlCallback callback);
+ void toggleAndFollowTvPower();
void queryDisplayStatus(IHdmiControlCallback callback);
void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 923b9b76..81e6e78 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -81,8 +81,10 @@
private final transient boolean mParcelSensitiveFields;
private static final int MIN_MTU = 68;
- /* package-visibility - Used in other files (such as Ikev2VpnProfile) as minimum iface MTU. */
- static final int MIN_MTU_V6 = 1280;
+
+ /** @hide */
+ public static final int MIN_MTU_V6 = 1280;
+
private static final int MAX_MTU = 10000;
private static final int INET6_ADDR_LENGTH = 16;
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index f806b56..40bb8bf 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -339,10 +339,14 @@
public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24;
/**
+ * Indicates that this network is temporarily unmetered.
+ * <p>
* This capability will be set for networks that are generally metered, but are currently
* unmetered, e.g., because the user is in a particular area. This capability can be changed at
* any time. When it is removed, applications are responsible for stopping any data transfer
* that should not occur on a metered network.
+ * Note that most apps should use {@link #NET_CAPABILITY_NOT_METERED} instead. For more
+ * information, see https://developer.android.com/about/versions/11/features/5g#meteredness.
*/
public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25;
@@ -370,8 +374,8 @@
| (1 << NET_CAPABILITY_FOREGROUND)
| (1 << NET_CAPABILITY_NOT_CONGESTED)
| (1 << NET_CAPABILITY_NOT_SUSPENDED)
- | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY
- | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+ | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY)
+ | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED);
/**
* Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -1802,20 +1806,26 @@
sb.append(" OwnerUid: ").append(mOwnerUid);
}
- if (mAdministratorUids.length == 0) {
- sb.append(" AdministratorUids: ").append(Arrays.toString(mAdministratorUids));
+ if (!ArrayUtils.isEmpty(mAdministratorUids)) {
+ sb.append(" AdminUids: ").append(Arrays.toString(mAdministratorUids));
+ }
+
+ if (mRequestorUid != Process.INVALID_UID) {
+ sb.append(" RequestorUid: ").append(mRequestorUid);
+ }
+
+ if (mRequestorPackageName != null) {
+ sb.append(" RequestorPkg: ").append(mRequestorPackageName);
}
if (null != mSSID) {
sb.append(" SSID: ").append(mSSID);
}
- if (mPrivateDnsBroken) {
- sb.append(" Private DNS is broken");
- }
- sb.append(" RequestorUid: ").append(mRequestorUid);
- sb.append(" RequestorPackageName: ").append(mRequestorPackageName);
+ if (mPrivateDnsBroken) {
+ sb.append(" PrivateDnsBroken");
+ }
sb.append("]");
return sb.toString();
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
new file mode 100644
index 0000000..8160edc
--- /dev/null
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import android.annotation.NonNull;
+
+/**
+ * This class represents a configuration for a connection to a Virtual Carrier Network gateway.
+ *
+ * <p>Each VcnGatewayConnectionConfig represents a single logical connection to a carrier gateway,
+ * and may provide one or more telephony services (as represented by network capabilities). Each
+ * gateway is expected to provide mobility for a given session as the device roams across {@link
+ * Network}s.
+ *
+ * <p>A VCN connection based on this configuration will be brought up dynamically based on device
+ * settings, and filed NetworkRequests. Underlying networks will be selected based on the services
+ * required by this configuration (as represented by network capabilities), and must be part of the
+ * subscription group under which this configuration is registered (see {@link
+ * VcnManager#setVcnConfig}).
+ *
+ * <p>Services that can be provided by a VCN network, or required for underlying networks are
+ * limited to services provided by cellular networks:
+ *
+ * <ul>
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MMS}
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_SUPL}
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_DUN}
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_FOTA}
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IMS}
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_CBS}
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IA}
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_RCS}
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_XCAP}
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_EIMS}
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET}
+ * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MCX}
+ * </ul>
+ *
+ * @hide
+ */
+public final class VcnGatewayConnectionConfig {
+ private VcnGatewayConnectionConfig() {
+ validate();
+ }
+
+ // TODO: Implement getters, validators, etc
+
+ /**
+ * Validates this configuration
+ *
+ * @hide
+ */
+ private void validate() {
+ // TODO: implement validation logic
+ }
+
+ // Parcelable methods
+
+ /** This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects */
+ public static class Builder {
+ // TODO: Implement this builder
+
+ /**
+ * Builds and validates the VcnGatewayConnectionConfig
+ *
+ * @return an immutable VcnGatewayConnectionConfig instance
+ */
+ @NonNull
+ public VcnGatewayConnectionConfig build() {
+ return new VcnGatewayConnectionConfig();
+ }
+ }
+}
diff --git a/core/java/android/os/BasicShellCommandHandler.java b/core/java/android/os/BasicShellCommandHandler.java
deleted file mode 100644
index 366da3d..0000000
--- a/core/java/android/os/BasicShellCommandHandler.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-
-/**
- * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}. This is meant to
- * be copied into mainline modules, so this class must not use any hidden APIs.
- *
- * @hide
- */
-public abstract class BasicShellCommandHandler {
- static final String TAG = "ShellCommand";
- static final boolean DEBUG = false;
-
- private Binder mTarget;
- private FileDescriptor mIn;
- private FileDescriptor mOut;
- private FileDescriptor mErr;
- private String[] mArgs;
-
- private String mCmd;
- private int mArgPos;
- private String mCurArgData;
-
- private FileInputStream mFileIn;
- private FileOutputStream mFileOut;
- private FileOutputStream mFileErr;
-
- private PrintWriter mOutPrintWriter;
- private PrintWriter mErrPrintWriter;
- private InputStream mInputStream;
-
- public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, int firstArgPos) {
- mTarget = target;
- mIn = in;
- mOut = out;
- mErr = err;
- mArgs = args;
- mCmd = null;
- mArgPos = firstArgPos;
- mCurArgData = null;
- mFileIn = null;
- mFileOut = null;
- mFileErr = null;
- mOutPrintWriter = null;
- mErrPrintWriter = null;
- mInputStream = null;
- }
-
- public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args) {
- String cmd;
- int start;
- if (args != null && args.length > 0) {
- cmd = args[0];
- start = 1;
- } else {
- cmd = null;
- start = 0;
- }
- init(target, in, out, err, args, start);
- mCmd = cmd;
-
- if (DEBUG) {
- RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
- Log.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
- Log.d(TAG, "Calling uid=" + Binder.getCallingUid()
- + " pid=" + Binder.getCallingPid());
- }
- int res = -1;
- try {
- res = onCommand(mCmd);
- if (DEBUG) Log.d(TAG, "Executed command " + mCmd + " on " + mTarget);
- } catch (Throwable e) {
- // Unlike usual calls, in this case if an exception gets thrown
- // back to us we want to print it back in to the dump data, since
- // that is where the caller expects all interesting information to
- // go.
- PrintWriter eout = getErrPrintWriter();
- eout.println();
- eout.println("Exception occurred while executing '" + mCmd + "':");
- e.printStackTrace(eout);
- } finally {
- if (DEBUG) Log.d(TAG, "Flushing output streams on " + mTarget);
- if (mOutPrintWriter != null) {
- mOutPrintWriter.flush();
- }
- if (mErrPrintWriter != null) {
- mErrPrintWriter.flush();
- }
- if (DEBUG) Log.d(TAG, "Sending command result on " + mTarget);
- }
- if (DEBUG) Log.d(TAG, "Finished command " + mCmd + " on " + mTarget);
- return res;
- }
-
- /**
- * Return the raw FileDescriptor for the output stream.
- */
- public FileDescriptor getOutFileDescriptor() {
- return mOut;
- }
-
- /**
- * Return direct raw access (not buffered) to the command's output data stream.
- */
- public OutputStream getRawOutputStream() {
- if (mFileOut == null) {
- mFileOut = new FileOutputStream(mOut);
- }
- return mFileOut;
- }
-
- /**
- * Return a PrintWriter for formatting output to {@link #getRawOutputStream()}.
- */
- public PrintWriter getOutPrintWriter() {
- if (mOutPrintWriter == null) {
- mOutPrintWriter = new PrintWriter(getRawOutputStream());
- }
- return mOutPrintWriter;
- }
-
- /**
- * Return the raw FileDescriptor for the error stream.
- */
- public FileDescriptor getErrFileDescriptor() {
- return mErr;
- }
-
- /**
- * Return direct raw access (not buffered) to the command's error output data stream.
- */
- public OutputStream getRawErrorStream() {
- if (mFileErr == null) {
- mFileErr = new FileOutputStream(mErr);
- }
- return mFileErr;
- }
-
- /**
- * Return a PrintWriter for formatting output to {@link #getRawErrorStream()}.
- */
- public PrintWriter getErrPrintWriter() {
- if (mErr == null) {
- return getOutPrintWriter();
- }
- if (mErrPrintWriter == null) {
- mErrPrintWriter = new PrintWriter(getRawErrorStream());
- }
- return mErrPrintWriter;
- }
-
- /**
- * Return the raw FileDescriptor for the input stream.
- */
- public FileDescriptor getInFileDescriptor() {
- return mIn;
- }
-
- /**
- * Return direct raw access (not buffered) to the command's input data stream.
- */
- public InputStream getRawInputStream() {
- if (mFileIn == null) {
- mFileIn = new FileInputStream(mIn);
- }
- return mFileIn;
- }
-
- /**
- * Return buffered access to the command's {@link #getRawInputStream()}.
- */
- public InputStream getBufferedInputStream() {
- if (mInputStream == null) {
- mInputStream = new BufferedInputStream(getRawInputStream());
- }
- return mInputStream;
- }
-
- /**
- * Return the next option on the command line -- that is an argument that
- * starts with '-'. If the next argument is not an option, null is returned.
- */
- public String getNextOption() {
- if (mCurArgData != null) {
- String prev = mArgs[mArgPos - 1];
- throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
- }
- if (mArgPos >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mArgPos];
- if (!arg.startsWith("-")) {
- return null;
- }
- mArgPos++;
- if (arg.equals("--")) {
- return null;
- }
- if (arg.length() > 1 && arg.charAt(1) != '-') {
- if (arg.length() > 2) {
- mCurArgData = arg.substring(2);
- return arg.substring(0, 2);
- } else {
- mCurArgData = null;
- return arg;
- }
- }
- mCurArgData = null;
- return arg;
- }
-
- /**
- * Return the next argument on the command line, whatever it is; if there are
- * no arguments left, return null.
- */
- public String getNextArg() {
- if (mCurArgData != null) {
- String arg = mCurArgData;
- mCurArgData = null;
- return arg;
- } else if (mArgPos < mArgs.length) {
- return mArgs[mArgPos++];
- } else {
- return null;
- }
- }
-
- public String peekNextArg() {
- if (mCurArgData != null) {
- return mCurArgData;
- } else if (mArgPos < mArgs.length) {
- return mArgs[mArgPos];
- } else {
- return null;
- }
- }
-
- /**
- * @return all the remaining arguments in the command without moving the current position.
- */
- public String[] peekRemainingArgs() {
- int remaining = getRemainingArgsCount();
- String[] args = new String[remaining];
- for (int pos = mArgPos; pos < mArgs.length; pos++) {
- args[pos - mArgPos] = mArgs[pos];
- }
- return args;
- }
-
- /**
- * Returns number of arguments that haven't been processed yet.
- */
- public int getRemainingArgsCount() {
- if (mArgPos >= mArgs.length) {
- return 0;
- }
- return mArgs.length - mArgPos;
- }
-
- /**
- * Return the next argument on the command line, whatever it is; if there are
- * no arguments left, throws an IllegalArgumentException to report this to the user.
- */
- public String getNextArgRequired() {
- String arg = getNextArg();
- if (arg == null) {
- String prev = mArgs[mArgPos - 1];
- throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
- }
- return arg;
- }
-
- public int handleDefaultCommands(String cmd) {
- if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
- onHelp();
- } else {
- getOutPrintWriter().println("Unknown command: " + cmd);
- }
- return -1;
- }
-
- public Binder getTarget() {
- return mTarget;
- }
-
- public String[] getAllArgs() {
- return mArgs;
- }
-
- /**
- * Implement parsing and execution of a command. If it isn't a command you understand,
- * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
- * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
- * to process additional command line arguments. Command output can be written to
- * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
- *
- * <p class="caution">Note that no permission checking has been done before entering this
- * function, so you need to be sure to do your own security verification for any commands you
- * are executing. The easiest way to do this is to have the ShellCommand contain
- * only a reference to your service's aidl interface, and do all of your command
- * implementations on top of that -- that way you can rely entirely on your executing security
- * code behind that interface.</p>
- *
- * @param cmd The first command line argument representing the name of the command to execute.
- * @return Return the command result; generally 0 or positive indicates success and
- * negative values indicate error.
- */
- public abstract int onCommand(String cmd);
-
- /**
- * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
- */
- public abstract void onHelp();
-}
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index 3358ce1..a2173a6 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -19,15 +19,9 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.util.Slog;
-import com.android.internal.util.FastPrintWriter;
+import com.android.modules.utils.BasicShellCommandHandler;
-import java.io.BufferedInputStream;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
/**
* Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index c89adad..086180e 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -85,8 +85,6 @@
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
@@ -1045,22 +1043,22 @@
/**
* Detect attempts to invoke a method on a {@link Context} that is not suited for such
* operation.
- * <p>An example of this is trying to obtain an instance of visual service (e.g.
+ * <p>An example of this is trying to obtain an instance of UI service (e.g.
* {@link android.view.WindowManager}) from a non-visual {@link Context}. This is not
* allowed, since a non-visual {@link Context} is not adjusted to any visual area, and
* therefore can report incorrect metrics or resources.
* @see Context#getDisplay()
* @see Context#getSystemService(String)
- * @hide
*/
- @TestApi
public @NonNull Builder detectIncorrectContextUse() {
return enable(DETECT_VM_INCORRECT_CONTEXT_USE);
}
/**
* Disable detection of incorrect context use.
- * TODO(b/149790106): Fix usages and remove.
+ *
+ * @see #detectIncorrectContextUse()
+ *
* @hide
*/
@TestApi
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 97c9f4b..714bcea 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -457,6 +457,20 @@
*/
public static final String NAMESPACE_CONFIGURATION = "configuration";
+ /**
+ * LatencyTracker properties definitions.
+ *
+ * @hide
+ */
+ public static final String NAMESPACE_LATENCY_TRACKER = "latency_tracker";
+
+ /**
+ * InteractionJankMonitor properties definitions.
+ *
+ * @hide
+ */
+ public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/OWNERS b/core/java/android/provider/OWNERS
index 8b7d6ad..97e0156 100644
--- a/core/java/android/provider/OWNERS
+++ b/core/java/android/provider/OWNERS
@@ -1,4 +1,5 @@
per-file DeviceConfig.java = svetoslavganov@google.com
per-file DeviceConfig.java = hackbod@google.com
+per-file DeviceConfig.java = schfan@google.com
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a50a835..884f8cc 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14584,19 +14584,6 @@
*/
public static final String MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH =
"maximum_obscuring_opacity_for_touch";
-
- /**
- * LatencyTracker settings.
- *
- * The following strings are supported as keys:
- * <pre>
- * enabled (boolean)
- * sampling_interval (int)
- * </pre>
- *
- * @hide
- */
- public static final String LATENCY_TRACKER = "latency_tracker";
}
/**
diff --git a/core/java/android/service/attestation/IImpressionAttestationService.aidl b/core/java/android/service/attestation/IImpressionAttestationService.aidl
index 8e858b8..fcbc51f 100644
--- a/core/java/android/service/attestation/IImpressionAttestationService.aidl
+++ b/core/java/android/service/attestation/IImpressionAttestationService.aidl
@@ -18,8 +18,8 @@
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.service.attestation.ImpressionToken;
import android.os.RemoteCallback;
+import android.service.attestation.ImpressionToken;
/**
* Service used to handle impression attestation requests.
@@ -31,22 +31,26 @@
* Generates the impression token that can be used to validate that the system generated the
* token.
*
- * @param screenshot The token for the window where the view is shown.
+ * @param salt The salt to use when generating the hmac. This should be unique to the caller so
+ * the token cannot be verified by any other process.
+ * @param screenshot The screenshot to generate the hash and add to the token.
* @param bounds The size and position of the content being attested in the window.
* @param hashAlgorithm The String for the hashing algorithm to use based on values in
* {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS}.
* @param Callback The callback invoked to send back the impression token.
*/
- void generateImpressionToken(in HardwareBuffer screenshot, in Rect bounds,
+ void generateImpressionToken(in String salt, in HardwareBuffer screenshot, in Rect bounds,
in String hashAlgorithm, in RemoteCallback callback);
/**
* Call to verify that the impressionToken passed in was generated by the system. The result
- * will be sent in the callback as an integer with the key {@link #EXTRA_VERIFICATION_STATUS}
- * and will be one of the values in {@link VerificationStatus}.
+ * will be sent in the callback as a boolean with the key {@link #EXTRA_VERIFICATION_STATUS}.
*
+ * @param salt The salt value to use when verifying the hmac. This should be the same value that
+ * was passed to {@link generateImpressionToken()} to generate the token.
* @param impressionToken The token to verify that it was generated by the system.
* @param callback The callback invoked to send back the verification status.
*/
- void verifyImpressionToken(in ImpressionToken impressionToken, in RemoteCallback callback);
+ void verifyImpressionToken(in String salt, in ImpressionToken impressionToken,
+ in RemoteCallback callback);
}
diff --git a/core/java/android/service/attestation/ImpressionAttestationService.java b/core/java/android/service/attestation/ImpressionAttestationService.java
index 4919f5d..05ad5f0 100644
--- a/core/java/android/service/attestation/ImpressionAttestationService.java
+++ b/core/java/android/service/attestation/ImpressionAttestationService.java
@@ -18,7 +18,6 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -50,27 +49,24 @@
public static final String EXTRA_VERIFICATION_STATUS =
"android.service.attestation.extra.VERIFICATION_STATUS";
- /** @hide */
- @IntDef(prefix = {"VERIFICATION_STATUS_"}, value = {
- VERIFICATION_STATUS_UNKNOWN,
- VERIFICATION_STATUS_OS_VERIFIED,
- VERIFICATION_STATUS_APP_DECLARED
- })
- public @interface VerificationStatus {
- }
-
- public static final int VERIFICATION_STATUS_UNKNOWN = 0;
- public static final int VERIFICATION_STATUS_OS_VERIFIED = 1;
- public static final int VERIFICATION_STATUS_APP_DECLARED = 2;
-
/**
* Manifest metadata key for the resource string array containing the names of all impression
* attestation algorithms provided by the service.
+ *
* @hide
*/
public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
"android.attestation.available_algorithms";
+ /**
+ * The {@link Intent} action that must be declared as handled by a service in its manifest
+ * for the system to recognize it as an impression attestation providing service.
+ *
+ * @hide
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.attestation.ImpressionAttestationService";
+
private ImpressionAttestationServiceWrapper mWrapper;
private Handler mHandler;
@@ -94,6 +90,8 @@
* Generates the impression token that can be used to validate that the system
* generated the token.
*
+ * @param salt The salt to use when generating the hmac. This should be unique to the
+ * caller so the token cannot be verified by any other process.
* @param screenshot The screenshot buffer for the content to attest.
* @param bounds The size and position of the content being attested in the window.
* @param hashAlgorithm The String for the hashing algorithm to use based values in
@@ -102,51 +100,57 @@
* Returns null when the arguments sent are invalid.
*/
@Nullable
- public abstract ImpressionToken onGenerateImpressionToken(@NonNull HardwareBuffer screenshot,
- @NonNull Rect bounds, @NonNull String hashAlgorithm);
+ public abstract ImpressionToken onGenerateImpressionToken(@NonNull String salt,
+ @NonNull HardwareBuffer screenshot, @NonNull Rect bounds,
+ @NonNull String hashAlgorithm);
/**
* Call to verify that the impressionToken passed in was generated by the system.
*
+ * @param salt The salt value to use when verifying the hmac. This should be the
+ * same value that was passed to
+ * {@link #onGenerateImpressionToken(String,
+ * HardwareBuffer, Rect, String)} to
+ * generate the token.
* @param impressionToken The token to verify that it was generated by the system.
- * @return A {@link VerificationStatus} about whether the token was generated by the system.
+ * @return true if the token can be verified that it was generated by the system.
*/
- public abstract @VerificationStatus int onVerifyImpressionToken(
+ public abstract boolean onVerifyImpressionToken(@NonNull String salt,
@NonNull ImpressionToken impressionToken);
- private void generateImpressionToken(HardwareBuffer screenshot, Rect bounds,
+ private void generateImpressionToken(String salt, HardwareBuffer screenshot, Rect bounds,
String hashAlgorithm, RemoteCallback callback) {
- ImpressionToken impressionToken = onGenerateImpressionToken(screenshot, bounds,
+ ImpressionToken impressionToken = onGenerateImpressionToken(salt, screenshot, bounds,
hashAlgorithm);
final Bundle data = new Bundle();
data.putParcelable(EXTRA_IMPRESSION_TOKEN, impressionToken);
callback.sendResult(data);
}
- private void verifyImpressionToken(ImpressionToken impressionToken,
+ private void verifyImpressionToken(String salt, ImpressionToken impressionToken,
RemoteCallback callback) {
- @VerificationStatus int verificationStatus = onVerifyImpressionToken(impressionToken);
+ boolean verificationStatus = onVerifyImpressionToken(salt, impressionToken);
final Bundle data = new Bundle();
- data.putInt(EXTRA_VERIFICATION_STATUS, verificationStatus);
+ data.putBoolean(EXTRA_VERIFICATION_STATUS, verificationStatus);
callback.sendResult(data);
}
private final class ImpressionAttestationServiceWrapper extends
IImpressionAttestationService.Stub {
@Override
- public void generateImpressionToken(HardwareBuffer screenshot, Rect bounds,
+ public void generateImpressionToken(String salt, HardwareBuffer screenshot, Rect bounds,
String hashAlgorithm, RemoteCallback callback) {
mHandler.sendMessage(
obtainMessage(ImpressionAttestationService::generateImpressionToken,
- ImpressionAttestationService.this, screenshot, bounds, hashAlgorithm,
- callback));
+ ImpressionAttestationService.this, salt, screenshot, bounds,
+ hashAlgorithm, callback));
}
@Override
- public void verifyImpressionToken(ImpressionToken impressionToken,
+ public void verifyImpressionToken(String salt, ImpressionToken impressionToken,
RemoteCallback callback) {
mHandler.sendMessage(obtainMessage(ImpressionAttestationService::verifyImpressionToken,
- ImpressionAttestationService.this, impressionToken, callback));
+ ImpressionAttestationService.this, salt, impressionToken, callback));
}
}
}
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index b4b5819..53290e2 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -176,6 +176,9 @@
@Override
public void onDestroy() {
+ // As this call will come asynchronously in the main thread, prevent calls from the binder
+ // being processed after this.
+ mHandler.removeCallbacksAndMessages(null);
if (mListening) {
onStopListening();
mListening = false;
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index bfa3123..479a0c1 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -17,10 +17,7 @@
package android.telephony;
import android.Manifest;
-import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.compat.annotation.ChangeId;
@@ -31,27 +28,17 @@
import android.os.HandlerExecutor;
import android.os.Looper;
import android.telephony.Annotation.CallState;
-import android.telephony.Annotation.DataActivityType;
-import android.telephony.Annotation.DisconnectCauses;
-import android.telephony.Annotation.NetworkType;
-import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SimActivationState;
import android.telephony.Annotation.SrvccState;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
-import android.telephony.NetworkRegistrationInfo.Domain;
-import android.telephony.TelephonyManager.DataEnabledReason;
-import android.telephony.TelephonyManager.DataState;
-import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
import dalvik.system.VMRuntime;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
@@ -126,9 +113,7 @@
*
* @see #onServiceStateChanged
* @see ServiceState
- * @deprecated Use {@link ServiceStateChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_SERVICE_STATE = 0x00000001;
/**
@@ -136,7 +121,8 @@
* {@more}
*
* @see #onSignalStrengthChanged
- * @deprecated Use {@link SignalStrengthsChangedListener} instead.
+ *
+ * @deprecated by {@link #LISTEN_SIGNAL_STRENGTHS}
*/
@Deprecated
public static final int LISTEN_SIGNAL_STRENGTH = 0x00000002;
@@ -152,9 +138,7 @@
* voicemail icon.
*
* @see #onMessageWaitingIndicatorChanged
- * @deprecated Use {@link MessageWaitingIndicatorChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 0x00000004;
/**
@@ -165,9 +149,7 @@
* {@link TelephonyManager#hasCarrierPrivileges}).
*
* @see #onCallForwardingIndicatorChanged
- * @deprecated Use {@link CallForwardingIndicatorChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_CALL_FORWARDING_INDICATOR = 0x00000008;
/**
@@ -183,9 +165,7 @@
* instead.
*
* @see #onCellLocationChanged
- * @deprecated Use {@link CellLocationChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_CELL_LOCATION = 0x00000010;
/**
@@ -193,18 +173,14 @@
* {@more}
*
* @see #onCallStateChanged
- * @deprecated Use {@link CallStateChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_CALL_STATE = 0x00000020;
/**
* Listen for changes to the data connection state (cellular).
*
* @see #onDataConnectionStateChanged
- * @deprecated Use {@link DataConnectionStateChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_DATA_CONNECTION_STATE = 0x00000040;
/**
@@ -215,9 +191,7 @@
* data-traffic icon.
*
* @see #onDataActivity
- * @deprecated Use {@link DataActivityListener} instead.
*/
- @Deprecated
public static final int LISTEN_DATA_ACTIVITY = 0x00000080;
/**
@@ -227,9 +201,7 @@
* icon.
*
* @see #onSignalStrengthsChanged
- * @deprecated Use {@link SignalStrengthsChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_SIGNAL_STRENGTHS = 0x00000100;
/**
@@ -239,9 +211,7 @@
* @see #onSignalStrengthsChanged
*
* @hide
- * @deprecated Use {@link AlwaysReportedSignalStrengthsChangedListener} instead.
*/
- @Deprecated
@RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
public static final int LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH = 0x00000200;
@@ -252,9 +222,7 @@
* permission.
*
* @see #onCellInfoChanged
- * @deprecated Use {@link CellInfoChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_CELL_INFO = 0x00000400;
/**
@@ -266,10 +234,8 @@
* (see {@link TelephonyManager#hasCarrierPrivileges}).
*
* @hide
- * @deprecated Use {@link PreciseCallStateChangedListener} instead.
*/
- @Deprecated
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+ @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
@SystemApi
public static final int LISTEN_PRECISE_CALL_STATE = 0x00000800;
@@ -281,10 +247,8 @@
* (see {@link TelephonyManager#hasCarrierPrivileges}).
*
* @see #onPreciseDataConnectionStateChanged
- * @deprecated Use {@link PreciseDataConnectionStateChangedListener} instead.
*/
- @Deprecated
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+ @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 0x00001000;
/**
@@ -294,7 +258,7 @@
* READ_PRECISE_PHONE_STATE}
* @see #onDataConnectionRealTimeInfoChanged(DataConnectionRealTimeInfo)
*
- * @deprecated Use {@link TelephonyManager#requestModemActivityInfo} instead.
+ * @deprecated Use {@link TelephonyManager#getModemActivityInfo()}
* @hide
*/
@Deprecated
@@ -307,9 +271,7 @@
*
* @see #onServiceStateChanged(ServiceState)
* @hide
- * @deprecated Use {@link SrvccStateChangedListener} instead.
*/
- @Deprecated
@SystemApi
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public static final int LISTEN_SRVCC_STATE_CHANGED = 0x00004000;
@@ -327,11 +289,10 @@
/**
* Listen for carrier network changes indicated by a carrier app.
*
- * @see android.service.carrier.CarrierService#notifyCarrierNetworkChange(boolean)
+ * @see #onCarrierNetworkRequest
+ * @see TelephonyManager#notifyCarrierNetworkChange(boolean)
* @hide
- * @deprecated Use {@link CarrierNetworkChangeListener} instead.
*/
- @Deprecated
public static final int LISTEN_CARRIER_NETWORK_CHANGE = 0x00010000;
/**
@@ -350,9 +311,7 @@
*
* @see #onVoiceActivationStateChanged
* @hide
- * @deprecated Use {@link VoiceActivationStateChangedListener} instead.
*/
- @Deprecated
@SystemApi
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public static final int LISTEN_VOICE_ACTIVATION_STATE = 0x00020000;
@@ -364,24 +323,20 @@
* @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
* @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
* @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
- *
+ * {@more}
* Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been
* fully activated
*
* @see #onDataActivationStateChanged
* @hide
- * @deprecated Use {@link DataActivationStateChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_DATA_ACTIVATION_STATE = 0x00040000;
/**
* Listen for changes to the user mobile data state
*
* @see #onUserMobileDataStateChanged
- * @deprecated Use {@link UserMobileDataStateChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_USER_MOBILE_DATA_STATE = 0x00080000;
/**
@@ -392,9 +347,7 @@
* {@link TelephonyManager#hasCarrierPrivileges}).
*
* @see #onDisplayInfoChanged
- * @deprecated Use {@link DisplayInfoChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_DISPLAY_INFO_CHANGED = 0x00100000;
/**
@@ -402,9 +355,7 @@
*
* @see #onPhoneCapabilityChanged
* @hide
- * @deprecated Use {@link PhoneCapabilityChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_PHONE_CAPABILITY_CHANGE = 0x00200000;
/**
@@ -414,19 +365,17 @@
* subscription user selected as default data subscription in DSDS mode.
*
* @see #onActiveDataSubscriptionIdChanged
- * @deprecated Use {@link ActiveDataSubscriptionIdChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 0x00400000;
/**
* Listen for changes to the radio power state.
*
+ * <p>Requires permission {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
+ *
* @see #onRadioPowerStateChanged
* @hide
- * @deprecated Use {@link RadioPowerStateChangedListener} instead.
*/
- @Deprecated
@SystemApi
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 0x00800000;
@@ -436,10 +385,7 @@
*
* <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
* app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @deprecated Use {@link EmergencyNumberListChangedListener} instead.
*/
- @Deprecated
public static final int LISTEN_EMERGENCY_NUMBER_LIST = 0x01000000;
/**
@@ -450,10 +396,8 @@
* or the calling app has carrier privileges
* (see {@link TelephonyManager#hasCarrierPrivileges}).
*
- * @deprecated Use {@link CallDisconnectCauseChangedListener} instead.
*/
- @Deprecated
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+ @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
public static final int LISTEN_CALL_DISCONNECT_CAUSES = 0x02000000;
/**
@@ -465,11 +409,9 @@
*
* @see #onCallAttributesChanged
* @hide
- * @deprecated Use {@link CallAttributesChangedListener} instead.
*/
- @Deprecated
@SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+ @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 0x04000000;
/**
@@ -481,20 +423,18 @@
* (see {@link TelephonyManager#hasCarrierPrivileges}).
*
* @see #onImsCallDisconnectCauseChanged(ImsReasonInfo)
- * @deprecated Use {@link ImsCallDisconnectCauseChangedListener} instead.
*/
- @Deprecated
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+ @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 0x08000000;
/**
* Listen for the emergency number placed from an outgoing call.
*
+ * <p>Requires permission {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
+ *
* @see #onOutgoingEmergencyCall
* @hide
- * @deprecated Use {@link OutgoingEmergencyCallListener} instead.
*/
- @Deprecated
@SystemApi
@RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 0x10000000;
@@ -502,11 +442,11 @@
/**
* Listen for the emergency number placed from an outgoing SMS.
*
+ * <p>Requires permission {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
+ *
* @see #onOutgoingEmergencySms
* @hide
- * @deprecated Use {@link OutgoingEmergencySmsListener} instead.
*/
- @Deprecated
@SystemApi
@RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 0x20000000;
@@ -525,9 +465,7 @@
* of whether the calling app has carrier privileges.
*
* @see #onRegistrationFailed
- * @deprecated Use {@link RegistrationFailedListener} instead.
*/
- @Deprecated
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public static final int LISTEN_REGISTRATION_FAILURE = 0x40000000;
@@ -541,525 +479,19 @@
* of whether the calling app has carrier privileges.
*
* @see #onBarringInfoChanged
- * @deprecated Use {@link BarringInfoChangedListener} instead.
*/
- @Deprecated
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public static final int LISTEN_BARRING_INFO = 0x80000000;
/**
- * Event for changes to the network service state (cellular).
+ * Listen for changes to the physical channel configuration.
*
- * @see ServiceStateChangedListener#onServiceStateChanged
- * @see ServiceState
- *
- * @hide
- */
- @SystemApi
- public static final int EVENT_SERVICE_STATE_CHANGED = 1;
-
- /**
- * Event for changes to the network signal strength (cellular).
- *
- * @see SignalStrengthsChangedListener#onSignalStrengthsChanged
- *
- * @hide
- */
- @SystemApi
- public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2;
-
- /**
- * Event for changes to the message-waiting indicator.
- *
- * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that
- * the calling app has carrier privileges (see
- * {@link TelephonyManager#hasCarrierPrivileges}).
- * <p>
- * Example: The status bar uses this to determine when to display the
- * voicemail icon.
- *
- * @see MessageWaitingIndicatorChangedListener#onMessageWaitingIndicatorChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public static final int EVENT_MESSAGE_WAITING_INDICATOR_CHANGED = 3;
-
- /**
- * Event for changes to the call-forwarding indicator.
- *
- * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that
- * the calling app has carrier privileges (see
- * {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @see CallForwardingIndicatorChangedListener#onCallForwardingIndicatorChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4;
-
- /**
- * Event for changes to the device's cell location. Note that
- * this will result in frequent callbacks to the listener.
- *
- * If you need regular location updates but want more control over
- * the update interval or location precision, you can set up a listener
- * through the {@link android.location.LocationManager location manager}
- * instead.
- *
- * @see CellLocationChangedListener#onCellLocationChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
- public static final int EVENT_CELL_LOCATION_CHANGED = 5;
-
- /**
- * Event for changes to the device call state.
- *
- * @see CallStateChangedListener#onCallStateChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_CALL_LOG)
- public static final int EVENT_CALL_STATE_CHANGED = 6;
-
- /**
- * Event for changes to the data connection state (cellular).
- *
- * @see DataConnectionStateChangedListener#onDataConnectionStateChanged
- *
- * @hide
- */
- @SystemApi
- public static final int EVENT_DATA_CONNECTION_STATE_CHANGED = 7;
-
- /**
- * Event for changes to the direction of data traffic on the data
- * connection (cellular).
- *
- * Example: The status bar uses this to display the appropriate
- * data-traffic icon.
- *
- * @see DataActivityListener#onDataActivity
- *
- * @hide
- */
- @SystemApi
- public static final int EVENT_DATA_ACTIVITY_CHANGED = 8;
-
- /**
- * Event for changes to the network signal strengths (cellular).
- * <p>
- * Example: The status bar uses this to control the signal-strength
- * icon.
- *
- * @see SignalStrengthsChangedListener#onSignalStrengthsChanged
- *
- * @hide
- */
- @SystemApi
- public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9;
-
- /**
- * Event for changes of the network signal strengths (cellular) always reported from modem,
- * even in some situations such as the screen of the device is off.
- *
- * @see AlwaysReportedSignalStrengthsChangedListener#onSignalStrengthsChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
- public static final int EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED = 10;
-
- /**
- * Event for changes to observed cell info.
- *
- * @see CellInfoChangedListener#onCellInfoChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
- public static final int EVENT_CELL_INFO_CHANGED = 11;
-
- /**
- * Event for {@link android.telephony.Annotation.PreciseCallStates} of ringing,
- * background and foreground calls.
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
- * or the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @see PreciseCallStateChangedListener#onPreciseCallStateChanged
- *
+ * @see #onPhysicalChannelConfigurationChanged
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
- public static final int EVENT_PRECISE_CALL_STATE_CHANGED = 12;
-
- /**
- * Event for {@link PreciseDataConnectionState} on the data connection (cellular).
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
- * or the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @see PreciseDataConnectionStateChangedListener#onPreciseDataConnectionStateChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
- public static final int EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED = 13;
-
- /**
- * Event for real time info for all data connections (cellular)).
- *
- * @see #onDataConnectionRealTimeInfoChanged(DataConnectionRealTimeInfo)
- *
- * @deprecated Use {@link TelephonyManager#requestModemActivityInfo}
- * @hide
- */
- @Deprecated
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
- public static final int EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED = 14;
-
- /**
- * Event for OEM hook raw event
- *
- * @see #onOemHookRawEvent
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public static final int EVENT_OEM_HOOK_RAW = 15;
-
- /**
- * Event for changes to the SRVCC state of the active call.
- *
- * @see SrvccStateChangedListener#onSrvccStateChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public static final int EVENT_SRVCC_STATE_CHANGED = 16;
-
- /**
- * Event for carrier network changes indicated by a carrier app.
- *
- * @see android.service.carrier.CarrierService#notifyCarrierNetworkChange(boolean)
- * @see CarrierNetworkChangeListener#onCarrierNetworkChange
- *
- * @hide
- */
- @SystemApi
- public static final int EVENT_CARRIER_NETWORK_CHANGED = 17;
-
- /**
- * Event for changes to the sim voice activation state
- *
- * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
- * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
- *
- * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates voice service has been
- * fully activated
- *
- * @see VoiceActivationStateChangedListener#onVoiceActivationStateChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18;
-
- /**
- * Event for changes to the sim data activation state
- * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
- * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
- *
- * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been
- * fully activated
- *
- * @see DataActivationStateChangedListener#onDataActivationStateChanged
- * @hide
- */
- @SystemApi
- public static final int EVENT_DATA_ACTIVATION_STATE_CHANGED = 19;
-
- /**
- * Event for changes to the user mobile data state
- *
- * @see UserMobileDataStateChangedListener#onUserMobileDataStateChanged
- *
- * @hide
- */
- @SystemApi
- public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20;
-
- /**
- * Event for display info changed event.
- *
- * @see DisplayInfoChangedListener#onDisplayInfoChanged
- *
- * @hide
- */
- @SystemApi
- public static final int EVENT_DISPLAY_INFO_CHANGED = 21;
-
- /**
- * Event for changes to the phone capability.
- *
- * @see PhoneCapabilityChangedListener#onPhoneCapabilityChanged
- *
- * @hide
- */
- @SystemApi
- public static final int EVENT_PHONE_CAPABILITY_CHANGED = 22;
-
- /**
- * Event for changes to active data subscription ID. Active data subscription is
- * the current subscription used to setup Cellular Internet data. For example,
- * it could be the current active opportunistic subscription in use, or the
- * subscription user selected as default data subscription in DSDS mode.
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
- * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @see ActiveDataSubscriptionIdChangedListener#onActiveDataSubscriptionIdChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public static final int EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED = 23;
-
- /**
- * Event for changes to the radio power state.
- *
- * @see RadioPowerStateChangedListener#onRadioPowerStateChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public static final int EVENT_RADIO_POWER_STATE_CHANGED = 24;
-
- /**
- * Event for changes to emergency number list based on all active subscriptions.
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
- * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @see EmergencyNumberListChangedListener#onEmergencyNumberListChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public static final int EVENT_EMERGENCY_NUMBER_LIST_CHANGED = 25;
-
- /**
- * Event for call disconnect causes which contains {@link DisconnectCause} and
- * {@link PreciseDisconnectCause}.
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
- * or the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @see CallDisconnectCauseChangedListener#onCallDisconnectCauseChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
- public static final int EVENT_CALL_DISCONNECT_CAUSE_CHANGED = 26;
-
- /**
- * Event for changes to the call attributes of a currently active call.
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
- * or the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @see CallAttributesChangedListener#onCallAttributesChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
- public static final int EVENT_CALL_ATTRIBUTES_CHANGED = 27;
-
- /**
- * Event for IMS call disconnect causes which contains
- * {@link android.telephony.ims.ImsReasonInfo}
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
- * or the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @see ImsCallDisconnectCauseChangedListener#onImsCallDisconnectCauseChanged(ImsReasonInfo)
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
- public static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = 28;
-
- /**
- * Event for the emergency number placed from an outgoing call.
- *
- * @see OutgoingEmergencyCallListener#onOutgoingEmergencyCall
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
- public static final int EVENT_OUTGOING_EMERGENCY_CALL = 29;
-
- /**
- * Event for the emergency number placed from an outgoing SMS.
- *
- * @see OutgoingEmergencySmsListener#onOutgoingEmergencySms
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
- public static final int EVENT_OUTGOING_EMERGENCY_SMS = 30;
-
- /**
- * Event for registration failures.
- *
- * Event for indications that a registration procedure has failed in either the CS or PS
- * domain. This indication does not necessarily indicate a change of service state, which should
- * be tracked via {@link #EVENT_SERVICE_STATE_CHANGED}.
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
- * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless
- * of whether the calling app has carrier privileges.
- *
- * @see RegistrationFailedListener#onRegistrationFailed
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(allOf = {
- Manifest.permission.READ_PRECISE_PHONE_STATE,
- Manifest.permission.ACCESS_FINE_LOCATION
- })
- public static final int EVENT_REGISTRATION_FAILURE = 31;
-
- /**
- * Event for Barring Information for the current registered / camped cell.
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
- * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless
- * of whether the calling app has carrier privileges.
- *
- * @see BarringInfoChangedListener#onBarringInfoChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(allOf = {
- Manifest.permission.READ_PRECISE_PHONE_STATE,
- Manifest.permission.ACCESS_FINE_LOCATION
- })
- public static final int EVENT_BARRING_INFO_CHANGED = 32;
-
- /**
- * Event for changes to the physical channel configuration.
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
- * or the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @see PhysicalChannelConfigChangedListener#onPhysicalChannelConfigChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
- public static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 33;
-
-
- /**
- * Event for changes to the data enabled.
- *
- * Event for indications that the enabled status of current data has changed.
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
- * or the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @see DataEnabledChangedListener#onDataEnabledChanged
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
- public static final int EVENT_DATA_ENABLED_CHANGED = 34;
-
- /** @hide */
- @IntDef(prefix = { "EVENT_" }, value = {
- EVENT_SERVICE_STATE_CHANGED,
- EVENT_SIGNAL_STRENGTH_CHANGED,
- EVENT_MESSAGE_WAITING_INDICATOR_CHANGED,
- EVENT_CALL_FORWARDING_INDICATOR_CHANGED,
- EVENT_CELL_LOCATION_CHANGED,
- EVENT_CALL_STATE_CHANGED,
- EVENT_DATA_CONNECTION_STATE_CHANGED,
- EVENT_DATA_ACTIVITY_CHANGED,
- EVENT_SIGNAL_STRENGTHS_CHANGED,
- EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED,
- EVENT_CELL_INFO_CHANGED,
- EVENT_PRECISE_CALL_STATE_CHANGED,
- EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED,
- EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED,
- EVENT_OEM_HOOK_RAW,
- EVENT_SRVCC_STATE_CHANGED,
- EVENT_CARRIER_NETWORK_CHANGED,
- EVENT_VOICE_ACTIVATION_STATE_CHANGED,
- EVENT_DATA_ACTIVATION_STATE_CHANGED,
- EVENT_USER_MOBILE_DATA_STATE_CHANGED,
- EVENT_DISPLAY_INFO_CHANGED,
- EVENT_PHONE_CAPABILITY_CHANGED,
- EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED,
- EVENT_RADIO_POWER_STATE_CHANGED,
- EVENT_EMERGENCY_NUMBER_LIST_CHANGED,
- EVENT_CALL_DISCONNECT_CAUSE_CHANGED,
- EVENT_CALL_ATTRIBUTES_CHANGED,
- EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED,
- EVENT_OUTGOING_EMERGENCY_CALL,
- EVENT_OUTGOING_EMERGENCY_SMS,
- EVENT_REGISTRATION_FAILURE,
- EVENT_BARRING_INFO_CHANGED,
- EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED,
- EVENT_DATA_ENABLED_CHANGED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TelephonyEvent {}
+ public static final long LISTEN_PHYSICAL_CHANNEL_CONFIGURATION = 0x100000000L;
/*
* Subscription used to listen to the phone state changes
@@ -1072,13 +504,9 @@
/**
* @hide
*/
- //TODO: The maxTargetSdk should be S if the build time tool updates it.
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- @UnsupportedAppUsage(
- maxTargetSdk = Build.VERSION_CODES.R,
- publicAlternatives = "Use {@code TelephonyManager#registerPhoneStateListener(" +
- "Executor, PhoneStateListener)} instead")
- public IPhoneStateListener callback;
+ @UnsupportedAppUsage
+ public final IPhoneStateListener callback;
/**
* Create a PhoneStateListener for the Phone with the default subscription.
@@ -1135,737 +563,17 @@
* The Executor must not be null.
*
* @param executor a non-null Executor that will execute callbacks for the PhoneStateListener.
- * @deprecated Use
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)} instead.
*/
- @Deprecated
public PhoneStateListener(@NonNull Executor executor) {
this(null, executor);
}
- /**
- * @hide
- */
- public void setExecutor(@NonNull @CallbackExecutor Executor executor) {
- if (executor == null) {
+ private PhoneStateListener(Integer subId, Executor e) {
+ if (e == null) {
throw new IllegalArgumentException("PhoneStateListener Executor must be non-null");
}
- callback = new IPhoneStateListenerStub(this, executor);
- }
-
- private PhoneStateListener(Integer subId, Executor e) {
- setExecutor(e);
mSubId = subId;
- }
-
- /**
- * Interface for service state listener.
- */
- public interface ServiceStateChangedListener {
- /**
- * Callback invoked when device service state changes on the registered subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * The instance of {@link ServiceState} passed as an argument here will have various
- * levels of location information stripped from it depending on the location permissions
- * that your app holds.
- * Only apps holding the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission will
- * receive all the information in {@link ServiceState}.
- *
- * @see ServiceState#STATE_EMERGENCY_ONLY
- * @see ServiceState#STATE_IN_SERVICE
- * @see ServiceState#STATE_OUT_OF_SERVICE
- * @see ServiceState#STATE_POWER_OFF
- */
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void onServiceStateChanged(@NonNull ServiceState serviceState);
- }
-
- /**
- * Interface for message waiting indicator listener.
- */
- public interface MessageWaitingIndicatorChangedListener {
- /**
- * Callback invoked when the message-waiting indicator changes on the registered
- * subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- */
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public void onMessageWaitingIndicatorChanged(boolean mwi);
- }
-
- /**
- * Interface for call-forwarding indicator listener.
- */
- public interface CallForwardingIndicatorChangedListener {
- /**
- * Callback invoked when the call-forwarding indicator changes on the registered
- * subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- */
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public void onCallForwardingIndicatorChanged(boolean cfi);
- }
-
- /**
- * Interface for device cell location listener.
- */
- public interface CellLocationChangedListener {
- /**
- * Callback invoked when device cell location changes on the registered subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- */
- @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
- public void onCellLocationChanged(@NonNull CellLocation location);
- }
-
- /**
- * Interface for call state listener.
- */
- public interface CallStateChangedListener {
- /**
- * Callback invoked when device call state changes.
- * <p>
- * Reports the state of Telephony (mobile) calls on the device for the registered s
- * ubscription.
- * <p>
- * Note: the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- * <p>
- * Note: The state returned here may differ from that returned by
- * {@link TelephonyManager#getCallState()}. Receivers of this callback should be aware that
- * calling {@link TelephonyManager#getCallState()} from within this callback may return a
- * different state than the callback reports.
- *
- * @param state call state
- * @param phoneNumber call phone number. If application does not have
- * {@link android.Manifest.permission#READ_CALL_LOG} permission or carrier
- * privileges (see {@link TelephonyManager#hasCarrierPrivileges}), an empty string will be
- * passed as an argument.
- */
- @RequiresPermission(android.Manifest.permission.READ_CALL_LOG)
- public void onCallStateChanged(@CallState int state, @Nullable String phoneNumber);
- }
-
- /**
- * Interface for data connection state listener.
- */
- public interface DataConnectionStateChangedListener {
- /**
- * Callback invoked when connection state changes on the registered subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @see TelephonyManager#DATA_DISCONNECTED
- * @see TelephonyManager#DATA_CONNECTING
- * @see TelephonyManager#DATA_CONNECTED
- * @see TelephonyManager#DATA_SUSPENDED
- *
- * @param state is the current state of data connection.
- * @param networkType is the current network type of data connection.
- */
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void onDataConnectionStateChanged(@DataState int state,
- @NetworkType int networkType);
- }
-
- /**
- * Interface for data activity state listener.
- */
- public interface DataActivityListener {
- /**
- * Callback invoked when data activity state changes on the registered subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @see TelephonyManager#DATA_ACTIVITY_NONE
- * @see TelephonyManager#DATA_ACTIVITY_IN
- * @see TelephonyManager#DATA_ACTIVITY_OUT
- * @see TelephonyManager#DATA_ACTIVITY_INOUT
- * @see TelephonyManager#DATA_ACTIVITY_DORMANT
- */
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void onDataActivity(@DataActivityType int direction);
- }
-
- /**
- * Interface for network signal strengths listener.
- */
- public interface SignalStrengthsChangedListener {
- /**
- * Callback invoked when network signal strengths changes on the registered subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- */
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength);
- }
-
- /**
- * Interface for network signal strengths listener which always reported from modem.
- */
- public interface AlwaysReportedSignalStrengthsChangedListener {
- /**
- * Callback always invoked from modem when network signal strengths changes on the
- * registered subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- */
- @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
- public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength);
- }
-
- /**
- * Interface for cell info listener.
- */
- public interface CellInfoChangedListener {
- /**
- * Callback invoked when a observed cell info has changed or new cells have been added
- * or removed on the registered subscription.
- * Note, the registration subscription ID s from {@link TelephonyManager} object
- * which registersPhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @param cellInfo is the list of currently visible cells.
- */
- @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
- public void onCellInfoChanged(@NonNull List<CellInfo> cellInfo);
- }
-
- /**
- * Interface for precise device call state listener.
- *
- * @hide
- */
- @SystemApi
- public interface PreciseCallStateChangedListener {
- /**
- * Callback invoked when precise device call state changes on the registered subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @param callState {@link PreciseCallState}
- */
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
- public void onPreciseCallStateChanged(@NonNull PreciseCallState callState);
- }
-
- /**
- * Interface for call disconnect cause listener.
- */
- public interface CallDisconnectCauseChangedListener {
- /**
- * Callback invoked when call disconnect cause changes on the registered subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @param disconnectCause {@link DisconnectCause}.
- * @param preciseDisconnectCause {@link PreciseDisconnectCause}.
- */
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
- public void onCallDisconnectCauseChanged(@DisconnectCauses int disconnectCause,
- @PreciseDisconnectCauses int preciseDisconnectCause);
- }
-
- /**
- * Interface for IMS call disconnect cause listener.
- */
- public interface ImsCallDisconnectCauseChangedListener {
- /**
- * Callback invoked when IMS call disconnect cause changes on the registered subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed.
- *
- */
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
- public void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo);
- }
-
- /**
- * Interface for precise data connection state listener.
- */
- public interface PreciseDataConnectionStateChangedListener {
- /**
- * Callback providing update about the default/internet data connection on the registered
- * subscription.
- *
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
- * or the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
- * @param dataConnectionState {@link PreciseDataConnectionState}
- */
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
- public void onPreciseDataConnectionStateChanged(
- @NonNull PreciseDataConnectionState dataConnectionState);
- }
-
- /**
- * Interface for Single Radio Voice Call Continuity listener.
- *
- * @hide
- */
- @SystemApi
- public interface SrvccStateChangedListener {
- /**
- * Callback invoked when there has been a change in the Single Radio Voice Call Continuity
- * (SRVCC) state for the currently active call on the registered subscription.
- *
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- */
- @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public void onSrvccStateChanged(@SrvccState int srvccState);
- }
-
- /**
- * Interface for SIM voice activation state listener.
- *
- * @hide
- */
- @SystemApi
- public interface VoiceActivationStateChangedListener {
- /**
- * Callback invoked when the SIM voice activation state has changed on the registered
- * subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @param state is the current SIM voice activation state
- */
- @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public void onVoiceActivationStateChanged(@SimActivationState int state);
-
- }
-
- /**
- * Interface for SIM data activation state listener.
- */
- public interface DataActivationStateChangedListener {
- /**
- * Callback invoked when the SIM data activation state has changed on the registered
- * subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @param state is the current SIM data activation state
- */
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void onDataActivationStateChanged(@SimActivationState int state);
- }
-
- /**
- * Interface for user mobile data state listener.
- */
- public interface UserMobileDataStateChangedListener {
- /**
- * Callback invoked when the user mobile data state has changed on the registered
- * subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @param enabled indicates whether the current user mobile data state is enabled or
- * disabled.
- */
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void onUserMobileDataStateChanged(boolean enabled);
- }
-
- /**
- * Interface for display info listener.
- */
- public interface DisplayInfoChangedListener {
- /**
- * Callback invoked when the display info has changed on the registered subscription.
- * <p> The {@link TelephonyDisplayInfo} contains status information shown to the user
- * based on carrier policy.
- *
- * @param telephonyDisplayInfo The display information.
- */
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo);
- }
-
- /**
- * Interface for the current emergency number list listener.
- */
- public interface EmergencyNumberListChangedListener {
- /**
- * Callback invoked when the current emergency number list has changed on the registered
- * subscription.
- *
- * Note, the registered subscription is associated with {@link TelephonyManager} object
- * on which
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}
- * was called.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * given subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @param emergencyNumberList Map associating all active subscriptions on the device with
- * the list of emergency numbers originating from that
- * subscription.
- * If there are no active subscriptions, the map will contain a
- * single entry with
- * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} as
- * the key and a list of emergency numbers as the value. If no
- * emergency number information is available, the value will be
- * empty.
- */
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public void onEmergencyNumberListChanged(
- @NonNull Map<Integer, List<EmergencyNumber>> emergencyNumberList);
- }
-
- /**
- * Interface for outgoing emergency call listener.
- *
- * @hide
- */
- @SystemApi
- public interface OutgoingEmergencyCallListener {
- /**
- * Callback invoked when an outgoing call is placed to an emergency number.
- *
- * This method will be called when an emergency call is placed on any subscription
- * (including the no-SIM case), regardless of which subscription this listener was
- * registered on.
- *
- * The default implementation of this method calls
- * {@link #onOutgoingEmergencyCall(EmergencyNumber)} for backwards compatibility purposes.
- * Do not call {@code super(...)} from within your implementation unless you want
- * {@link #onOutgoingEmergencyCall(EmergencyNumber)} to be called as well.
- *
- * @param placedEmergencyNumber The {@link EmergencyNumber} the emergency call was
- * placed to.
- * @param subscriptionId The subscription ID used to place the emergency call. If the
- * emergency call was placed without a valid subscription
- * (e.g. when there are no SIM cards in the device), this will be
- * equal to {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
- */
- @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
- public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber,
- int subscriptionId);
- }
-
- /**
- * Interface for outgoing emergency sms listener.
- *
- * @hide
- */
- @SystemApi
- public interface OutgoingEmergencySmsListener {
- /**
- * Smsback invoked when an outgoing sms is sent to an emergency number.
- *
- * This method will be called when an emergency sms is sent on any subscription,
- * regardless of which subscription this listener was registered on.
- *
- * The default implementation of this method calls
- * {@link #onOutgoingEmergencySms(EmergencyNumber)} for backwards compatibility purposes. Do
- * not call {@code super(...)} from within your implementation unless you want
- * {@link #onOutgoingEmergencySms(EmergencyNumber)} to be called as well.
- *
- * @param sentEmergencyNumber The {@link EmergencyNumber} the emergency sms was sent to.
- * @param subscriptionId The subscription ID used to send the emergency sms.
- */
- @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
- public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber,
- int subscriptionId);
- }
-
- /**
- * Interface for phone capability listener.
- *
- */
- public interface PhoneCapabilityChangedListener {
- /**
- * Callback invoked when phone capability changes.
- * Note, this callback triggers regardless of registered subscription.
- *
- * @param capability the new phone capability
- */
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void onPhoneCapabilityChanged(@NonNull PhoneCapability capability);
- }
-
- /**
- * Interface for active data subscription ID listener.
- */
- public interface ActiveDataSubscriptionIdChangedListener {
- /**
- * Callback invoked when active data subscription ID changes.
- * Note, this callback triggers regardless of registered subscription.
- *
- * @param subId current subscription used to setup Cellular Internet data.
- * For example, it could be the current active opportunistic subscription
- * in use, or the subscription user selected as default data subscription in
- * DSDS mode.
- */
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public void onActiveDataSubscriptionIdChanged(int subId);
- }
-
- /**
- * Interface for modem radio power state listener.
- *
- * @hide
- */
- @SystemApi
- public interface RadioPowerStateChangedListener {
- /**
- * Callback invoked when modem radio power state changes on the registered subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @param state the modem radio power state
- */
- @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public void onRadioPowerStateChanged(@RadioPowerState int state);
- }
-
- /**
- * Interface for carrier network listener.
- */
- public interface CarrierNetworkChangeListener {
- /**
- * Callback invoked when telephony has received notice from a carrier
- * app that a network action that could result in connectivity loss
- * has been requested by an app using
- * {@link android.service.carrier.CarrierService#notifyCarrierNetworkChange(boolean)}
- *
- * This is optional and is only used to allow the system to provide alternative UI while
- * telephony is performing an action that may result in intentional, temporary network
- * lack of connectivity.
- *
- * Note, this callback is pinned to the registered subscription and will be invoked when
- * the notifying carrier app has carrier privilege rule on the registered
- * subscription. {@link android.telephony.TelephonyManager#hasCarrierPrivileges}
- *
- * @param active If the carrier network change is or shortly will be active,
- * {@code true} indicate that showing alternative UI, {@code false} otherwise.
- */
- public void onCarrierNetworkChange(boolean active);
- }
-
- /**
- * Interface for registration failures listener.
- */
- public interface RegistrationFailedListener {
- /**
- * Report that Registration or a Location/Routing/Tracking Area update has failed.
- *
- * <p>Indicate whenever a registration procedure, including a location, routing, or tracking
- * area update fails. This includes procedures that do not necessarily result in a change of
- * the modem's registration status. If the modem's registration status changes, that is
- * reflected in the onNetworkStateChanged() and subsequent
- * get{Voice/Data}RegistrationState().
- *
- * <p>Because registration failures are ephemeral, this callback is not sticky.
- * Registrants will not receive the most recent past value when registering.
- *
- * @param cellIdentity the CellIdentity, which must include the globally unique identifier
- * for the cell (for example, all components of the CGI or ECGI).
- * @param chosenPlmn a 5 or 6 digit alphanumeric PLMN (MCC|MNC) among those broadcast by the
- * cell that was chosen for the failed registration attempt.
- * @param domain DOMAIN_CS, DOMAIN_PS or both in case of a combined procedure.
- * @param causeCode the primary failure cause code of the procedure.
- * For GSM/UMTS (MM), values are in TS 24.008 Sec 10.5.95
- * For GSM/UMTS (GMM), values are in TS 24.008 Sec 10.5.147
- * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9
- * For NR (5GMM), cause codes are TS 24.501 Sec 9.11.3.2
- * Integer.MAX_VALUE if this value is unused.
- * @param additionalCauseCode the cause code of any secondary/combined procedure
- * if appropriate. For UMTS, if a combined attach succeeds for
- * PS only, then the GMM cause code shall be included as an
- * additionalCauseCode. For LTE (ESM), cause codes are in
- * TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
- */
- @RequiresPermission(allOf = {
- Manifest.permission.READ_PRECISE_PHONE_STATE,
- Manifest.permission.ACCESS_FINE_LOCATION
- })
- public void onRegistrationFailed(@NonNull CellIdentity cellIdentity,
- @NonNull String chosenPlmn, @Domain int domain,
- int causeCode, int additionalCauseCode);
- }
-
- /**
- * Interface for call attributes listener.
- *
- * @hide
- */
- @SystemApi
- public interface CallAttributesChangedListener {
- /**
- * Callback invoked when the call attributes changes on the registered subscription.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers PhoneStateListener by
- * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * @param callAttributes the call attributes
- */
- @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
- void onCallAttributesChanged(@NonNull CallAttributes callAttributes);
- }
-
- /**
- * Interface for barring information listener.
- */
- public interface BarringInfoChangedListener {
- /**
- * 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
- */
- @RequiresPermission(allOf = {
- Manifest.permission.READ_PRECISE_PHONE_STATE,
- Manifest.permission.ACCESS_FINE_LOCATION
- })
- public void onBarringInfoChanged(@NonNull BarringInfo barringInfo);
- }
-
- /**
- * Interface for current physical channel configuration listener.
- */
- public interface PhysicalChannelConfigChangedListener {
- /**
- * Callback invoked when the current physical channel configuration has changed
- *
- * @param configs List of the current {@link PhysicalChannelConfig}s
- */
- @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
- public void onPhysicalChannelConfigChanged(@NonNull List<PhysicalChannelConfig> configs);
- }
-
- /**
- * Interface for data enabled listener.
- *
- * @hide
- */
- @SystemApi
- public interface DataEnabledChangedListener {
- /**
- * Callback invoked when the data enabled changes.
- *
- * @param enabled {@code true} if data is enabled, otherwise disabled.
- * @param reason Reason for data enabled/disabled.
- * See {@link TelephonyManager.DataEnabledReason}.
- */
- @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
- public void onDataEnabledChanged(boolean enabled,
- @DataEnabledReason int reason);
+ callback = new IPhoneStateListenerStub(this, e);
}
/**
@@ -1999,7 +707,6 @@
* same as above, but with the network type. Both called.
*/
public void onDataConnectionStateChanged(int state, int networkType) {
- // default implementation empty
}
/**
@@ -2047,7 +754,6 @@
* @param cellInfo is the list of currently visible cells.
*/
public void onCellInfoChanged(List<CellInfo> cellInfo) {
- // default implementation empty
}
/**
@@ -2061,7 +767,7 @@
* @param callState {@link PreciseCallState}
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+ @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
@SystemApi
public void onPreciseCallStateChanged(@NonNull PreciseCallState callState) {
// default implementation empty
@@ -2080,9 +786,9 @@
* @param preciseDisconnectCause {@link PreciseDisconnectCause}.
*
*/
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
- public void onCallDisconnectCauseChanged(@DisconnectCauses int disconnectCause,
- @PreciseDisconnectCauses int preciseDisconnectCause) {
+ @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
+ public void onCallDisconnectCauseChanged(@Annotation.DisconnectCauses int disconnectCause,
+ int preciseDisconnectCause) {
// default implementation empty
}
@@ -2098,7 +804,7 @@
* @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed.
*
*/
- @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+ @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
public void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo) {
// default implementation empty
}
@@ -2120,7 +826,7 @@
*
* @param dataConnectionState {@link PreciseDataConnectionState}
*/
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
public void onPreciseDataConnectionStateChanged(
@NonNull PreciseDataConnectionState dataConnectionState) {
// default implementation empty
@@ -2158,7 +864,6 @@
*/
@SystemApi
public void onSrvccStateChanged(@SrvccState int srvccState) {
- // default implementation empty
}
@@ -2177,7 +882,6 @@
*/
@SystemApi
public void onVoiceActivationStateChanged(@SimActivationState int state) {
- // default implementation empty
}
/**
@@ -2194,7 +898,6 @@
* @hide
*/
public void onDataActivationStateChanged(@SimActivationState int state) {
- // default implementation empty
}
/**
@@ -2222,7 +925,7 @@
*
* @param telephonyDisplayInfo The display information.
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @RequiresPermission((android.Manifest.permission.READ_PHONE_STATE))
public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
// default implementation empty
}
@@ -2355,7 +1058,7 @@
* @param capability the new phone capability
* @hide
*/
- public void onPhoneCapabilityChanged(@NonNull PhoneCapability capability) {
+ public void onPhoneCapabilityChanged(PhoneCapability capability) {
// default implementation empty
}
@@ -2399,8 +1102,7 @@
* subId. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
- * Requires permission {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
- *
+ * @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE}
* @param state the modem radio power state
* @hide
*/
@@ -2476,6 +1178,18 @@
}
/**
+ * Callback invoked when the current physical channel configuration has changed
+ *
+ * @param configs List of the current {@link PhysicalChannelConfig}s
+ * @hide
+ */
+ @SystemApi
+ public void onPhysicalChannelConfigurationChanged(
+ @NonNull List<PhysicalChannelConfig> configs) {
+ // 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.
*
@@ -2498,6 +1212,7 @@
public void onServiceStateChanged(ServiceState serviceState) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onServiceStateChanged(serviceState)));
}
@@ -2505,6 +1220,7 @@
public void onSignalStrengthChanged(int asu) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onSignalStrengthChanged(asu)));
}
@@ -2512,6 +1228,7 @@
public void onMessageWaitingIndicatorChanged(boolean mwi) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onMessageWaitingIndicatorChanged(mwi)));
}
@@ -2519,6 +1236,7 @@
public void onCallForwardingIndicatorChanged(boolean cfi) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onCallForwardingIndicatorChanged(cfi)));
}
@@ -2530,6 +1248,7 @@
cellIdentity == null ? CellLocation.getEmpty() : cellIdentity.asCellLocation();
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onCellLocationChanged(location)));
}
@@ -2537,6 +1256,7 @@
public void onCallStateChanged(int state, String incomingNumber) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onCallStateChanged(state, incomingNumber)));
}
@@ -2544,6 +1264,7 @@
public void onDataConnectionStateChanged(int state, int networkType) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
if (state == TelephonyManager.DATA_DISCONNECTING
&& VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
Binder.withCleanCallingIdentity(() -> mExecutor.execute(
@@ -2564,6 +1285,7 @@
public void onDataActivity(int direction) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onDataActivity(direction)));
}
@@ -2571,6 +1293,7 @@
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onSignalStrengthsChanged(signalStrength)));
}
@@ -2578,6 +1301,7 @@
public void onCellInfoChanged(List<CellInfo> cellInfo) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onCellInfoChanged(cellInfo)));
}
@@ -2585,6 +1309,7 @@
public void onPreciseCallStateChanged(PreciseCallState callState) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onPreciseCallStateChanged(callState)));
}
@@ -2592,6 +1317,7 @@
public void onCallDisconnectCauseChanged(int disconnectCause, int preciseDisconnectCause) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onCallDisconnectCauseChanged(
disconnectCause, preciseDisconnectCause)));
@@ -2601,6 +1327,7 @@
PreciseDataConnectionState dataConnectionState) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(
() -> psl.onPreciseDataConnectionStateChanged(dataConnectionState)));
@@ -2618,6 +1345,7 @@
public void onSrvccStateChanged(int state) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onSrvccStateChanged(state)));
}
@@ -2625,6 +1353,7 @@
public void onVoiceActivationStateChanged(int activationState) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(
() -> psl.onVoiceActivationStateChanged(activationState)));
@@ -2633,6 +1362,7 @@
public void onDataActivationStateChanged(int activationState) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(
() -> psl.onDataActivationStateChanged(activationState)));
@@ -2641,6 +1371,7 @@
public void onUserMobileDataStateChanged(boolean enabled) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(
() -> psl.onUserMobileDataStateChanged(enabled)));
@@ -2649,6 +1380,7 @@
public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(
() -> psl.onDisplayInfoChanged(telephonyDisplayInfo)));
@@ -2657,6 +1389,7 @@
public void onOemHookRawEvent(byte[] rawData) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onOemHookRawEvent(rawData)));
}
@@ -2664,6 +1397,7 @@
public void onCarrierNetworkChange(boolean active) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onCarrierNetworkChange(active)));
}
@@ -2671,6 +1405,7 @@
public void onEmergencyNumberListChanged(Map emergencyNumberList) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(
() -> psl.onEmergencyNumberListChanged(emergencyNumberList)));
@@ -2680,6 +1415,7 @@
int subscriptionId) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(
() -> psl.onOutgoingEmergencyCall(placedEmergencyNumber,
@@ -2690,6 +1426,7 @@
int subscriptionId) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(
() -> psl.onOutgoingEmergencySms(sentEmergencyNumber, subscriptionId)));
@@ -2698,6 +1435,7 @@
public void onPhoneCapabilityChanged(PhoneCapability capability) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onPhoneCapabilityChanged(capability)));
}
@@ -2705,6 +1443,7 @@
public void onRadioPowerStateChanged(@RadioPowerState int state) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state)));
}
@@ -2712,6 +1451,7 @@
public void onCallAttributesChanged(CallAttributes callAttributes) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onCallAttributesChanged(callAttributes)));
}
@@ -2719,6 +1459,7 @@
public void onActiveDataSubIdChanged(int subId) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onActiveDataSubscriptionIdChanged(subId)));
}
@@ -2726,51 +1467,44 @@
public void onImsCallDisconnectCauseChanged(ImsReasonInfo disconnectCause) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(
() -> psl.onImsCallDisconnectCauseChanged(disconnectCause)));
+
}
public void onRegistrationFailed(@NonNull CellIdentity cellIdentity,
- @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode) {
+ @NonNull String chosenPlmn, int domain,
+ int causeCode, int additionalCauseCode) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> psl.onRegistrationFailed(
- cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode)));
+ 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)));
}
- public void onPhysicalChannelConfigChanged(List<PhysicalChannelConfig> configs) {
+ public void onPhysicalChannelConfigurationChanged(List<PhysicalChannelConfig> configs) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
- PhysicalChannelConfigChangedListener listener =
- (PhysicalChannelConfigChangedListener) mPhoneStateListenerWeakRef.get();
- if (listener == null) return;
+ if (psl == null) return;
+
Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> listener.onPhysicalChannelConfigChanged(
- configs)));
- }
-
- public void onDataEnabledChanged(boolean enabled, @DataEnabledReason int reason) {
- if ((mPhoneStateListenerWeakRef.get() instanceof DataEnabledChangedListener)) {
- DataEnabledChangedListener listener =
- (DataEnabledChangedListener) mPhoneStateListenerWeakRef.get();
- if (listener == null) return;
-
- Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> listener.onDataEnabledChanged(
- enabled, reason)));
- }
+ () -> mExecutor.execute(
+ () -> psl.onPhysicalChannelConfigurationChanged(configs)));
}
}
+
private void log(String s) {
Rlog.d(LOG_TAG, s);
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index c706e21..24ed29a 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -15,7 +15,6 @@
*/
package android.telephony;
-import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -25,9 +24,6 @@
import android.content.Context;
import android.os.Binder;
import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.Annotation.CallState;
@@ -41,7 +37,6 @@
import android.telephony.Annotation.SrvccState;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
-import android.util.ArraySet;
import android.util.Log;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
@@ -50,7 +45,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -212,7 +206,7 @@
}
/**
- * To check the SDK version for {@link #listenWithEventList}.
+ * To check the SDK version for {@link #listenForSubscriber}.
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
@@ -224,23 +218,23 @@
* @param pkg Package name
* @param featureId Feature ID
* @param listener Listener providing callback
- * @param events List events
+ * @param events Events
* @param notifyNow Whether to notify instantly
*/
- public void listenWithEventList(int subId, @NonNull String pkg, @NonNull String featureId,
- @NonNull PhoneStateListener listener, @NonNull int[] events, boolean notifyNow) {
+ public void listenForSubscriber(int subId, @NonNull String pkg, @NonNull String featureId,
+ @NonNull PhoneStateListener listener, long events, boolean notifyNow) {
try {
// subId from PhoneStateListener is deprecated Q on forward, use the subId from
// TelephonyManager instance. Keep using subId from PhoneStateListener for pre-Q.
if (Compatibility.isChangeEnabled(LISTEN_CODE_CHANGE)) {
// Since mSubId in PhoneStateListener is deprecated from Q on forward, this is
// the only place to set mSubId and its for "informational" only.
- listener.mSubId = (events.length == 0)
+ listener.mSubId = (events == PhoneStateListener.LISTEN_NONE)
? SubscriptionManager.INVALID_SUBSCRIPTION_ID : subId;
} else if (listener.mSubId != null) {
subId = listener.mSubId;
}
- sRegistry.listenWithEventList(
+ sRegistry.listenForSubscriber(
subId, pkg, featureId, listener.callback, events, notifyNow);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -777,349 +771,13 @@
* @param subId the subId
* @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
*/
- public void notifyPhysicalChannelConfigForSubscriber(
+ public void notifyPhysicalChannelConfigurationForSubscriber(
int subId, List<PhysicalChannelConfig> configs) {
try {
- sRegistry.notifyPhysicalChannelConfigForSubscriber(subId, configs);
+ sRegistry.notifyPhysicalChannelConfigurationForSubscriber(subId, configs);
} catch (RemoteException ex) {
// system server crash
}
}
- public @NonNull Set<Integer> getEventsFromListener(@NonNull PhoneStateListener listener) {
-
- Set<Integer> eventList = new ArraySet<>();
-
- if (listener instanceof PhoneStateListener.ServiceStateChangedListener) {
- eventList.add(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.MessageWaitingIndicatorChangedListener) {
- eventList.add(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.CallForwardingIndicatorChangedListener) {
- eventList.add(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.CellLocationChangedListener) {
- eventList.add(PhoneStateListener.EVENT_CELL_LOCATION_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.CallStateChangedListener) {
- eventList.add(PhoneStateListener.EVENT_CALL_STATE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.DataConnectionStateChangedListener) {
- eventList.add(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.DataActivityListener) {
- eventList.add(PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.SignalStrengthsChangedListener) {
- eventList.add(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.AlwaysReportedSignalStrengthsChangedListener) {
- eventList.add(PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.CellInfoChangedListener) {
- eventList.add(PhoneStateListener.EVENT_CELL_INFO_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.PreciseCallStateChangedListener) {
- eventList.add(PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.CallDisconnectCauseChangedListener) {
- eventList.add(PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.ImsCallDisconnectCauseChangedListener) {
- eventList.add(PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.PreciseDataConnectionStateChangedListener) {
- eventList.add(PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.SrvccStateChangedListener) {
- eventList.add(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.VoiceActivationStateChangedListener) {
- eventList.add(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.DataActivationStateChangedListener) {
- eventList.add(PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.UserMobileDataStateChangedListener) {
- eventList.add(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.DisplayInfoChangedListener) {
- eventList.add(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.EmergencyNumberListChangedListener) {
- eventList.add(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.OutgoingEmergencyCallListener) {
- eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL);
- }
-
- if (listener instanceof PhoneStateListener.OutgoingEmergencySmsListener) {
- eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS);
- }
-
- if (listener instanceof PhoneStateListener.PhoneCapabilityChangedListener) {
- eventList.add(PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.ActiveDataSubscriptionIdChangedListener) {
- eventList.add(PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.RadioPowerStateChangedListener) {
- eventList.add(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.CarrierNetworkChangeListener) {
- eventList.add(PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.RegistrationFailedListener) {
- eventList.add(PhoneStateListener.EVENT_REGISTRATION_FAILURE);
- }
-
- if (listener instanceof PhoneStateListener.CallAttributesChangedListener) {
- eventList.add(PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.BarringInfoChangedListener) {
- eventList.add(PhoneStateListener.EVENT_BARRING_INFO_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.PhysicalChannelConfigChangedListener) {
- eventList.add(PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
- }
-
- if (listener instanceof PhoneStateListener.DataEnabledChangedListener) {
- eventList.add(PhoneStateListener.EVENT_DATA_ENABLED_CHANGED);
- }
-
- return eventList;
- }
-
- private @NonNull Set<Integer> getEventsFromBitmask(int eventMask) {
-
- Set<Integer> eventList = new ArraySet<>();
-
- if ((eventMask & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
- eventList.add(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
- eventList.add(PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
- eventList.add(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
- eventList.add(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
- eventList.add(PhoneStateListener.EVENT_CELL_LOCATION_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
- eventList.add(PhoneStateListener.EVENT_CALL_STATE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
- eventList.add(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) {
- eventList.add(PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
- eventList.add(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) != 0) {
- eventList.add(PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_CELL_INFO) != 0) {
- eventList.add(PhoneStateListener.EVENT_CELL_INFO_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_PRECISE_CALL_STATE) != 0) {
- eventList.add(PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) {
- eventList.add(PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_DATA_CONNECTION_REAL_TIME_INFO) != 0) {
- eventList.add(PhoneStateListener.EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT) != 0) {
- eventList.add(PhoneStateListener.EVENT_OEM_HOOK_RAW);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED) != 0) {
- eventList.add(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) != 0) {
- eventList.add(PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) != 0) {
- eventList.add(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_DATA_ACTIVATION_STATE) != 0) {
- eventList.add(PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) {
- eventList.add(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
- eventList.add(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE) != 0) {
- eventList.add(PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0) {
- eventList.add(PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) {
- eventList.add(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST) != 0) {
- eventList.add(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES) != 0) {
- eventList.add(PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED) != 0) {
- eventList.add(PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) != 0) {
- eventList.add(PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL) != 0) {
- eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS) != 0) {
- eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_REGISTRATION_FAILURE) != 0) {
- eventList.add(PhoneStateListener.EVENT_REGISTRATION_FAILURE);
- }
-
- if ((eventMask & PhoneStateListener.LISTEN_BARRING_INFO) != 0) {
- eventList.add(PhoneStateListener.EVENT_BARRING_INFO_CHANGED);
- }
- return eventList;
-
- }
-
- /**
- * Registers a listener object to receive notification of changes
- * in specified telephony states.
- * <p>
- * To register a listener, pass a {@link PhoneStateListener} which implements
- * interfaces of events. For example,
- * FakeServiceStateChangedListener extends {@link PhoneStateListener} implements
- * {@link PhoneStateListener.ServiceStateChangedListener}.
- *
- * At registration, and when a specified telephony state changes, the telephony manager invokes
- * the appropriate callback method on the listener object and passes the current (updated)
- * values.
- * <p>
- *
- * If this TelephonyManager object has been created with
- * {@link TelephonyManager#createForSubscriptionId}, applies to the given subId.
- * Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}.
- * To listen events for multiple subIds, pass a separate listener object to
- * each TelephonyManager object created with {@link TelephonyManager#createForSubscriptionId}.
- *
- * Note: if you call this method while in the middle of a binder transaction, you <b>must</b>
- * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A
- * {@link SecurityException} will be thrown otherwise.
- *
- * This API should be used sparingly -- large numbers of listeners will cause system
- * instability. If a process has registered too many listeners without unregistering them, it
- * may encounter an {@link IllegalStateException} when trying to register more listeners.
- *
- * @param listener The {@link PhoneStateListener} object to register.
- */
- public void registerPhoneStateListener(@NonNull @CallbackExecutor Executor executor, int subId,
- String pkgName, String attributionTag, @NonNull PhoneStateListener listener,
- boolean notifyNow) {
- registerPhoneStateListener(executor, subId, pkgName, attributionTag, listener,
- getEventsFromListener(listener), notifyNow);
- }
-
- public void registerPhoneStateListenerWithEvents(int subId, String pkgName,
- String attributionTag, @NonNull PhoneStateListener listener, int events,
- boolean notifyNow) {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
- registerPhoneStateListener(new HandlerExecutor(new Handler(Looper.myLooper())),
- subId, pkgName, attributionTag, listener, getEventsFromBitmask(events), notifyNow);
- }
-
- private void registerPhoneStateListener(@NonNull @CallbackExecutor Executor executor, int subId,
- String pkgName, String attributionTag, @NonNull PhoneStateListener listener,
- @NonNull Set<Integer> events, boolean notifyNow) {
- if (listener == null) {
- throw new IllegalStateException("telephony service is null.");
- }
-
- listener.setExecutor(executor);
- listenWithEventList(subId, pkgName, attributionTag, listener,
- events.stream().mapToInt(i -> i).toArray(), notifyNow);
- }
-
- /**
- * Unregister an existing {@link PhoneStateListener}.
- *
- * @param listener The {@link PhoneStateListener} object to unregister.
- */
- public void unregisterPhoneStateListener(int subId, String pkgName, String attributionTag,
- @NonNull PhoneStateListener listener,
- boolean notifyNow) {
- listenWithEventList(subId, pkgName, attributionTag, listener, new int[0], notifyNow);
- }
}
diff --git a/core/java/android/uwb/AngleOfArrivalSupport.aidl b/core/java/android/uwb/AngleOfArrivalSupport.aidl
new file mode 100644
index 0000000..57666ff
--- /dev/null
+++ b/core/java/android/uwb/AngleOfArrivalSupport.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum AngleOfArrivalSupport {
+ /**
+ * The device does not support angle of arrival
+ */
+ NONE,
+
+ /**
+ * The device supports planar angle of arrival
+ */
+ TWO_DIMENSIONAL,
+
+ /**
+ * The device does supports three dimensional angle of arrival with hemispherical azimuth angles
+ */
+ THREE_DIMENSIONAL_HEMISPHERICAL,
+
+ /**
+ * The device does supports three dimensional angle of arrival with full azimuth angles
+ */
+ THREE_DIMENSIONAL_SPHERICAL,
+}
+
diff --git a/core/java/android/uwb/CloseReason.aidl b/core/java/android/uwb/CloseReason.aidl
new file mode 100644
index 0000000..bef129e
--- /dev/null
+++ b/core/java/android/uwb/CloseReason.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum CloseReason {
+ /**
+ * Unknown reason
+ */
+ UNKNOWN,
+
+ /**
+ * A local API call triggered the close, such as a call to
+ * IUwbAdapter.stopRanging.
+ */
+ LOCAL_API,
+
+ /**
+ * The maximum number of sessions has been reached. This error may be generated
+ * for an active session if a higher priority session begins.
+ */
+ MAX_SESSIONS_REACHED,
+
+ /**
+ * The system state has changed resulting in the session ending (e.g. the user
+ * disables UWB, or the user's locale changes and an active channel is no longer
+ * permitted to be used).
+ */
+ SYSTEM_POLICY,
+
+ /**
+ * The remote device has requested to terminate the session
+ */
+ REMOTE_REQUEST,
+
+ /**
+ * The session was closed for a protocol specific reason
+ */
+ PROTOCOL_SPECIFIC,
+}
+
diff --git a/core/java/android/uwb/IUwbAdapter.aidl b/core/java/android/uwb/IUwbAdapter.aidl
new file mode 100644
index 0000000..d29ed34
--- /dev/null
+++ b/core/java/android/uwb/IUwbAdapter.aidl
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import android.os.PersistableBundle;
+import android.uwb.AngleOfArrivalSupport;
+import android.uwb.IUwbAdapterStateCallbacks;
+import android.uwb.IUwbRangingCallbacks;
+import android.uwb.SessionHandle;
+
+/**
+ * @hide
+ */
+interface IUwbAdapter {
+ /*
+ * Register the callbacks used to notify the framework of events and data
+ *
+ * The provided callback's IUwbAdapterStateCallbacks#onAdapterStateChanged
+ * function must be called immediately following registration with the current
+ * state of the UWB adapter.
+ *
+ * @param callbacks callback to provide range and status updates to the framework
+ */
+ void registerAdapterStateCallbacks(in IUwbAdapterStateCallbacks adapterStateCallbacks);
+
+ /*
+ * Unregister the callbacks used to notify the framework of events and data
+ *
+ * Calling this function with an unregistered callback is a no-op
+ *
+ * @param callbacks callback to unregister
+ */
+ void unregisterAdapterStateCallbacks(in IUwbAdapterStateCallbacks callbacks);
+
+ /**
+ * Returns true if ranging is supported, false otherwise
+ */
+ boolean isRangingSupported();
+
+ /**
+ * Get the angle of arrival supported by this device
+ *
+ * @return the angle of arrival type supported
+ */
+ AngleOfArrivalSupport getAngleOfArrivalSupport();
+
+ /**
+ * Generates a list of the supported 802.15.4z channels
+ *
+ * The list must be prioritized in the order of preferred channel usage.
+ *
+ * The list must only contain channels that are permitted to be used in the
+ * device's current location.
+ *
+ * @return an array of support channels on the device for the current location.
+ */
+ int[] getSupportedChannels();
+
+ /**
+ * Generates a list of the supported 802.15.4z preamble codes
+ *
+ * The list must be prioritized in the order of preferred preamble usage.
+ *
+ * The list must only contain preambles that are permitted to be used in the
+ * device's current location.
+ *
+ * @return an array of supported preambles on the device for the current
+ * location.
+ */
+ int[] getSupportedPreambleCodes();
+
+ /**
+ * Get the accuracy of the ranging timestamps
+ *
+ * @return accuracy of the ranging timestamps in nanoseconds
+ */
+ long getTimestampResolutionNanos();
+
+ /**
+ * Get the supported number of simultaneous ranging sessions
+ *
+ * @return the supported number of simultaneous ranging sessions
+ */
+ int getMaxSimultaneousSessions();
+
+ /**
+ * Get the maximum number of remote devices per session
+ *
+ * @return the maximum number of remote devices supported in a single session
+ */
+ int getMaxRemoteDevicesPerSession();
+
+ /**
+ * Provides the capabilities and features of the device
+ *
+ * @return specification specific capabilities and features of the device
+ */
+ PersistableBundle getSpecificationInfo();
+
+ /**
+ * Request to start a new ranging session
+ *
+ * This function must return before calling IUwbAdapterCallbacks
+ * #onRangingStarted, #onRangingClosed, or #onRangingResult.
+ *
+ * A ranging session does not need to be started before returning.
+ *
+ * IUwbAdapterCallbacks#onRangingStarted must be called within
+ * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being called
+ * if the ranging session is scheduled to start successfully.
+ *
+ * IUwbAdapterCallbacks#onRangingStartFailed must be called within
+ * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being called
+ * if the ranging session fails to be scheduled to start successfully.
+ *
+ * @param rangingCallbacks the callbacks used to deliver ranging information
+ * @param parameters the configuration to use for ranging
+ * @return a SessionHandle used to identify this ranging request
+ */
+ SessionHandle startRanging(in IUwbRangingCallbacks rangingCallbacks,
+ in PersistableBundle parameters);
+
+ /**
+ * Stop and close ranging for the session associated with the given handle
+ *
+ * Calling with an invalid handle or a handle that has already been closed
+ * is a no-op.
+ *
+ * IUwbAdapterCallbacks#onRangingClosed must be called within
+ * RANGING_SESSION_CLOSE_THRESHOLD_MS of #stopRanging being called.
+ *
+ * @param sessionHandle the session handle to stop ranging for
+ */
+ void closeRanging(in SessionHandle sessionHandle);
+
+ /**
+ * The maximum allowed time to start a ranging session.
+ */
+ const int RANGING_SESSION_START_THRESHOLD_MS = 3000; // Value TBD
+
+ /**
+ * The maximum allowed time to notify the framework that a session has been
+ * closed.
+ */
+ const int RANGING_SESSION_CLOSE_THRESHOLD_MS = 3000; // Value TBD
+
+ /**
+ * Ranging scheduling time unit (RSTU) for High Rate Pulse (HRP) PHY
+ */
+ const int HIGH_RATE_PULSE_CHIRPS_PER_RSTU = 416;
+
+ /**
+ * Ranging scheduling time unit (RSTU) for Low Rate Pulse (LRP) PHY
+ */
+ const int LOW_RATE_PULSE_CHIRPS_PER_RSTU = 1;
+}
diff --git a/core/java/android/uwb/IUwbAdapterStateCallbacks.aidl b/core/java/android/uwb/IUwbAdapterStateCallbacks.aidl
new file mode 100644
index 0000000..d928eab
--- /dev/null
+++ b/core/java/android/uwb/IUwbAdapterStateCallbacks.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import android.uwb.StateChangeReason;
+
+/**
+ * @hide
+ */
+interface IUwbAdapterStateCallbacks {
+ /**
+ * Called whenever the adapter state changes
+ *
+ * @param isEnabled true if the adapter is enabled, false otherwise
+ * @param reason the reason that the state has changed
+ */
+ void onAdapterStateChanged(boolean isEnabled, StateChangeReason reason);
+}
\ No newline at end of file
diff --git a/core/java/android/uwb/IUwbRangingCallbacks.aidl b/core/java/android/uwb/IUwbRangingCallbacks.aidl
new file mode 100644
index 0000000..1fc3bfd
--- /dev/null
+++ b/core/java/android/uwb/IUwbRangingCallbacks.aidl
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import android.os.PersistableBundle;
+import android.uwb.CloseReason;
+import android.uwb.RangingReport;
+import android.uwb.SessionHandle;
+import android.uwb.StartFailureReason;
+
+/**
+ * @hide
+ */
+interface IUwbRangingCallbacks {
+ /**
+ * Called when ranging has started
+ *
+ * May output parameters generated by the lower layers that must be sent to the
+ * remote device(s). The PersistableBundle must be constructed using the UWB
+ * support library.
+ *
+ * @param sessionHandle the session the callback is being invoked for
+ * @param rangingOutputParameters parameters generated by the lower layer that
+ * should be sent to the remote device.
+ */
+ void onRangingStarted(in SessionHandle sessionHandle,
+ in PersistableBundle parameters);
+
+ /**
+ * Called when a ranging session fails to start
+ *
+ * @param sessionHandle the session the callback is being invoked for
+ * @param reason the reason the session failed to start
+ * @param parameters protocol specific parameters
+ */
+ void onRangingStartFailed(in SessionHandle sessionHandle, StartFailureReason reason,
+ in PersistableBundle parameters);
+ /**
+ * Called when a ranging session is closed
+ *
+ * @param sessionHandle the session the callback is being invoked for
+ * @param reason the reason the session was closed
+ * @param parameters protocol specific parameters
+ */
+ void onRangingClosed(in SessionHandle sessionHandle, CloseReason reason,
+ in PersistableBundle parameters);
+
+ /**
+ * Provides a new RangingResult to the framework
+ *
+ * The reported timestamp for a ranging measurement must be calculated as the
+ * time which the ranging round that generated this measurement concluded.
+ *
+ * @param sessionHandle an identifier to associate the ranging results with a
+ * session that is active
+ * @param result the ranging report
+ */
+ void onRangingResult(in SessionHandle sessionHandle, in RangingReport result);
+}
diff --git a/core/java/android/uwb/MeasurementStatus.aidl b/core/java/android/uwb/MeasurementStatus.aidl
new file mode 100644
index 0000000..5fa1554
--- /dev/null
+++ b/core/java/android/uwb/MeasurementStatus.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum MeasurementStatus {
+ /**
+ * Ranging was successful
+ */
+ SUCCESS,
+
+ /**
+ * The remote device is out of range
+ */
+ FAILURE_OUT_OF_RANGE,
+
+ /**
+ * An unknown failure has occurred.
+ */
+ FAILURE_UNKNOWN,
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java b/core/java/android/uwb/RangingReport.aidl
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
copy to core/java/android/uwb/RangingReport.aidl
index d7a3af0..c32747a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
+++ b/core/java/android/uwb/RangingReport.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -11,13 +11,9 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.classifier;
+package android.uwb;
-public class PointerCountEvaluator {
- public static float evaluate(int value) {
- return (value - 1) * (value - 1);
- }
-}
+parcelable RangingReport;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java b/core/java/android/uwb/SessionHandle.aidl
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
copy to core/java/android/uwb/SessionHandle.aidl
index d7a3af0..58a7dbb 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
+++ b/core/java/android/uwb/SessionHandle.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -11,13 +11,9 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.classifier;
+package android.uwb;
-public class PointerCountEvaluator {
- public static float evaluate(int value) {
- return (value - 1) * (value - 1);
- }
-}
+parcelable SessionHandle;
diff --git a/core/java/android/uwb/SessionHandle.java b/core/java/android/uwb/SessionHandle.java
new file mode 100644
index 0000000..928fcbdc
--- /dev/null
+++ b/core/java/android/uwb/SessionHandle.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class SessionHandle implements Parcelable {
+ private final int mId;
+
+ public SessionHandle(int id) {
+ mId = id;
+ }
+
+ protected SessionHandle(Parcel in) {
+ mId = in.readInt();
+ }
+
+ public static final Creator<SessionHandle> CREATOR = new Creator<SessionHandle>() {
+ @Override
+ public SessionHandle createFromParcel(Parcel in) {
+ return new SessionHandle(in);
+ }
+
+ @Override
+ public SessionHandle[] newArray(int size) {
+ return new SessionHandle[size];
+ }
+ };
+
+ public int getId() {
+ return mId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof SessionHandle) {
+ SessionHandle other = (SessionHandle) obj;
+ return mId == other.mId;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "SessionHandle [id=" + mId + "]";
+ }
+}
diff --git a/core/java/android/uwb/StartFailureReason.aidl b/core/java/android/uwb/StartFailureReason.aidl
new file mode 100644
index 0000000..4d9c962
--- /dev/null
+++ b/core/java/android/uwb/StartFailureReason.aidl
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum StartFailureReason {
+ /**
+ * Unknown start failure reason
+ */
+ UNKNOWN,
+
+ /**
+ * The provided parameters were invalid and ranging could not start
+ */
+ BAD_PARAMETERS,
+
+ /**
+ * The maximum number of sessions has been reached. This error may be generated
+ * for an active session if a higher priority session begins.
+ */
+ MAX_SESSIONS_REACHED,
+
+ /**
+ * The system state has changed resulting in the session ending (e.g. the user
+ * disables UWB, or the user's locale changes and an active channel is no longer
+ * permitted to be used).
+ */
+ SYSTEM_POLICY,
+
+ /**
+ * The session could not start because of a protocol specific reason.
+ */
+ PROTOCOL_SPECIFIC,
+}
+
diff --git a/core/java/android/uwb/StateChangeReason.aidl b/core/java/android/uwb/StateChangeReason.aidl
new file mode 100644
index 0000000..46a6e2e
--- /dev/null
+++ b/core/java/android/uwb/StateChangeReason.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum StateChangeReason {
+ /**
+ * The state changed for an unknown reason
+ */
+ UNKNOWN,
+
+ /**
+ * The adapter state changed because a session started.
+ */
+ SESSION_STARTED,
+
+
+ /**
+ * The adapter state changed because all sessions were closed.
+ */
+ ALL_SESSIONS_CLOSED,
+
+ /**
+ * The adapter state changed because of a device system change.
+ */
+ SYSTEM_POLICY,
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java b/core/java/android/uwb/UwbAddress.aidl
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
copy to core/java/android/uwb/UwbAddress.aidl
index d7a3af0..a202b1a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
+++ b/core/java/android/uwb/UwbAddress.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -11,13 +11,9 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.classifier;
+package android.uwb;
-public class PointerCountEvaluator {
- public static float evaluate(int value) {
- return (value - 1) * (value - 1);
- }
-}
+parcelable UwbAddress;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 924fc6d..0533533 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -668,22 +668,24 @@
void setShouldShowIme(int displayId, boolean shouldShow);
/**
- * Waits for transactions to get applied before injecting input.
- * This includes waiting for the input windows to get sent to InputManager.
+ * Waits for transactions to get applied before injecting input, optionally waiting for
+ * animations to complete. This includes waiting for the input windows to get sent to
+ * InputManager.
*
* This is needed for testing since the system add windows and injects input
* quick enough that the windows don't have time to get sent to InputManager.
*/
- boolean injectInputAfterTransactionsApplied(in InputEvent ev, int mode);
+ boolean injectInputAfterTransactionsApplied(in InputEvent ev, int mode,
+ boolean waitForAnimations);
/**
- * Waits until all animations have completed and input information has been sent from
- * WindowManager to native InputManager.
+ * Waits until input information has been sent from WindowManager to native InputManager,
+ * optionally waiting for animations to complete.
*
* This is needed for testing since we need to ensure input information has been propagated to
* native InputManager before proceeding with tests.
*/
- void syncInputTransactions();
+ void syncInputTransactions(boolean waitForAnimations);
/**
* Returns whether SurfaceFlinger layer tracing is enabled.
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 038dff6..7d1adc36 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -144,6 +144,20 @@
}
/**
+ * Called when a Pointer Capture event is received.
+ *
+ * @param pointerCaptureEnabled if true, the window associated with this input channel has just
+ * received Pointer Capture
+ * if false, the window associated with this input channel has just
+ * lost Pointer Capture
+ * @see View#requestPointerCapture()
+ * @see View#releasePointerCapture()
+ */
+ // Called from native code.
+ public void onPointerCaptureEvent(boolean pointerCaptureEnabled) {
+ }
+
+ /**
* Called when a batched input event is pending.
*
* The batched input event will continue to accumulate additional movement
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index f7fbb1c..673ed0d 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -43,12 +43,13 @@
*/
@RemoteViews.RemoteView
public class NotificationHeaderView extends FrameLayout {
- private final int mContentEndMargin;
private final int mHeadingEndMargin;
+ private final int mTouchableHeight;
private OnClickListener mExpandClickListener;
private HeaderTouchListener mTouchListener = new HeaderTouchListener();
private NotificationTopLineView mTopLineView;
private NotificationExpandButton mExpandButton;
+ private View mAltExpandTarget;
private CachingIconView mIcon;
private Drawable mBackground;
private boolean mEntireHeaderClickable;
@@ -82,8 +83,8 @@
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
Resources res = getResources();
- mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end);
mHeadingEndMargin = res.getDimensionPixelSize(R.dimen.notification_heading_margin_end);
+ mTouchableHeight = res.getDimensionPixelSize(R.dimen.notification_header_touchable_height);
mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
}
@@ -93,6 +94,7 @@
mIcon = findViewById(R.id.icon);
mTopLineView = findViewById(R.id.notification_top_line);
mExpandButton = findViewById(R.id.expand_button);
+ mAltExpandTarget = findViewById(R.id.alternate_expand_target);
setClipToPadding(false);
}
@@ -146,6 +148,7 @@
public void setOnClickListener(@Nullable OnClickListener l) {
mExpandClickListener = l;
mExpandButton.setOnClickListener(mExpandClickListener);
+ mAltExpandTarget.setOnClickListener(mExpandClickListener);
updateTouchListener();
}
@@ -187,6 +190,7 @@
private final ArrayList<Rect> mTouchRects = new ArrayList<>();
private Rect mExpandButtonRect;
+ private Rect mAltExpandTargetRect;
private int mTouchSlop;
private boolean mTrackGesture;
private float mDownX;
@@ -199,6 +203,7 @@
mTouchRects.clear();
addRectAroundView(mIcon);
mExpandButtonRect = addRectAroundView(mExpandButton);
+ mAltExpandTargetRect = addRectAroundView(mAltExpandTarget);
addWidthRect();
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@@ -206,7 +211,7 @@
private void addWidthRect() {
Rect r = new Rect();
r.top = 0;
- r.bottom = (int) (32 * getResources().getDisplayMetrics().density);
+ r.bottom = mTouchableHeight;
r.left = 0;
r.right = getWidth();
mTouchRects.add(r);
@@ -277,7 +282,8 @@
return true;
}
if (mExpandOnlyOnButton) {
- return mExpandButtonRect.contains((int) x, (int) y);
+ return mExpandButtonRect.contains((int) x, (int) y)
+ || mAltExpandTargetRect.contains((int) x, (int) y);
}
for (int i = 0; i < mTouchRects.size(); i++) {
Rect r = mTouchRects.get(i);
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
index a8eabe5..05636de 100644
--- a/core/java/android/view/NotificationTopLineView.java
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -97,10 +97,8 @@
final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
final boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
- int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth,
- MeasureSpec.AT_MOST);
- int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight,
- MeasureSpec.AT_MOST);
+ int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth, MeasureSpec.AT_MOST);
+ int heightSpec = MeasureSpec.makeMeasureSpec(givenHeight, MeasureSpec.AT_MOST);
int totalWidth = getPaddingStart();
int maxChildHeight = -1;
mMaxAscent = -1;
@@ -114,7 +112,7 @@
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec,
lp.leftMargin + lp.rightMargin, lp.width);
- int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
+ int childHeightSpec = getChildMeasureSpec(heightSpec,
lp.topMargin + lp.bottomMargin, lp.height);
child.measure(childWidthSpec, childHeightSpec);
totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
@@ -131,37 +129,37 @@
int endMargin = Math.max(mHeaderTextMarginEnd, getPaddingEnd());
if (totalWidth > givenWidth - endMargin) {
int overFlow = totalWidth - givenWidth + endMargin;
- if (mAppName != null) {
- // We are overflowing, lets shrink the app name first
- overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName,
- mChildMinWidth);
- }
- if (mTitle != null) {
- // still overflowing, we shrink the title text
- overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mTitle,
- mChildMinWidth);
- }
+ // First shrink the app name, down to a minimum size
+ overFlow = shrinkViewForOverflow(heightSpec, overFlow, mAppName, mChildMinWidth);
- // still overflowing, we shrink the header text
- overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0);
+ // Next, shrink the header text (this usually has subText)
+ // This shrinks the subtext first, but not all the way (yet!)
+ overFlow = shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, mChildMinWidth);
- // still overflowing, finally we shrink the secondary header text
- shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText,
- 0);
+ // Next, shrink the secondary header text (this rarely has conversationTitle)
+ overFlow = shrinkViewForOverflow(heightSpec, overFlow, mSecondaryHeaderText, 0);
+
+ // Next, shrink the title text (this has contentTitle; only in headerless views)
+ overFlow = shrinkViewForOverflow(heightSpec, overFlow, mTitle, mChildMinWidth);
+
+ // Finally, if there is still overflow, shrink the header down to 0 if still necessary.
+ shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, 0);
}
setMeasuredDimension(givenWidth, wrapHeight ? maxChildHeight : givenHeight);
}
private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView,
int minimumWidth) {
- final int oldWidth = targetView.getMeasuredWidth();
- if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
- // we're still too big
- int newSize = Math.max(minimumWidth, oldWidth - overFlow);
- int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
- targetView.measure(childWidthSpec, heightSpec);
- overFlow -= oldWidth - newSize;
+ if (targetView != null) {
+ final int oldWidth = targetView.getMeasuredWidth();
+ if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
+ // we're still too big
+ int newSize = Math.max(minimumWidth, oldWidth - overFlow);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ targetView.measure(childWidthSpec, heightSpec);
+ overFlow -= oldWidth - newSize;
+ }
}
return overFlow;
}
diff --git a/core/java/android/view/OnReceiveContentListener.java b/core/java/android/view/OnReceiveContentListener.java
index 4955289..db9c538 100644
--- a/core/java/android/view/OnReceiveContentListener.java
+++ b/core/java/android/view/OnReceiveContentListener.java
@@ -48,7 +48,7 @@
* public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};
*
* @Override
- * public Payload onReceiveContent(TextView view, Payload payload) {
+ * public Payload onReceiveContent(View view, Payload payload) {
* Map<Boolean, Payload> split = payload.partition(item -> item.getUri() != null);
* if (split.get(true) != null) {
* ClipData clip = payload.getClip();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a88ad9f..30ec2b0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9008,7 +9008,7 @@
}
/**
- * Sets the listener to be {@link #onReceiveContent used} to handle insertion of
+ * Sets the listener to be {@link #performReceiveContent used} to handle insertion of
* content into this view.
*
* <p>Depending on the type of view, this listener may be invoked for different scenarios. For
@@ -9039,7 +9039,6 @@
* not be null or empty if a non-null listener is passed in.
* @param listener The listener to use. This can be null to reset to the default behavior.
*/
- @SuppressWarnings("rawtypes")
public void setOnReceiveContentListener(@Nullable String[] mimeTypes,
@Nullable OnReceiveContentListener listener) {
if (listener != null) {
@@ -9055,27 +9054,46 @@
}
/**
- * Receives the given content. Invokes the listener configured via
- * {@link #setOnReceiveContentListener}; if no listener is set, the default implementation is a
- * no-op (returns the passed-in content without acting on it).
+ * Receives the given content. If no listener is set, invokes {@link #onReceiveContent}. If a
+ * listener is {@link #setOnReceiveContentListener set}, invokes the listener instead; if the
+ * listener returns a non-null result, invokes {@link #onReceiveContent} to handle it.
*
* @param payload The content to insert and related metadata.
*
* @return The portion of the passed-in content that was not accepted (may be all, some, or none
* of the passed-in content).
*/
- @SuppressWarnings({"rawtypes", "unchecked"})
- public @Nullable Payload onReceiveContent(@NonNull Payload payload) {
+ public @Nullable Payload performReceiveContent(@NonNull Payload payload) {
final OnReceiveContentListener listener = (mListenerInfo == null) ? null
: getListenerInfo().mOnReceiveContentListener;
if (listener != null) {
- return listener.onReceiveContent(this, payload);
+ final Payload remaining = listener.onReceiveContent(this, payload);
+ return (remaining == null) ? null : onReceiveContent(remaining);
}
+ return onReceiveContent(payload);
+ }
+
+ /**
+ * Implements the default behavior for receiving content for this type of view. The default
+ * view implementation is a no-op (returns the passed-in content without acting on it).
+ *
+ * <p>Widgets should override this method to define their default behavior for receiving
+ * content. Apps should {@link #setOnReceiveContentListener set a listener} to provide
+ * app-specific handling for receiving content.
+ *
+ * <p>See {@link #setOnReceiveContentListener} and {@link #performReceiveContent} for more info.
+ *
+ * @param payload The content to insert and related metadata.
+ *
+ * @return The portion of the passed-in content that was not handled (may be all, some, or none
+ * of the passed-in content).
+ */
+ public @Nullable Payload onReceiveContent(@NonNull Payload payload) {
return payload;
}
/**
- * Returns the MIME types accepted by {@link #onReceiveContent} for this view, as
+ * Returns the MIME types accepted by {@link #performReceiveContent} for this view, as
* configured via {@link #setOnReceiveContentListener}. By default returns null.
*
* <p>Different features (e.g. pasting from the clipboard, inserting stickers from the soft
@@ -9092,7 +9110,7 @@
* {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to
* lowercase.
*
- * @return The MIME types accepted by {@link #onReceiveContent} for this view (may
+ * @return The MIME types accepted by {@link #performReceiveContent} for this view (may
* include patterns such as "image/*").
*/
public @Nullable String[] getOnReceiveContentMimeTypes() {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 299c41b..8fdcac7 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -2372,7 +2372,7 @@
return;
}
Payload payload = new Payload.Builder(clip, SOURCE_AUTOFILL).build();
- Payload result = view.onReceiveContent(payload);
+ Payload result = view.performReceiveContent(payload);
if (result != null) {
Log.w(TAG, "autofillContent(): receiver could not insert content: id=" + id
+ ", view=" + view + ", clip=" + clip);
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index a92d1f5..1ab9edf 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -43,6 +43,8 @@
import android.view.OnReceiveContentListener;
import android.view.View;
+import com.android.internal.util.Preconditions;
+
class ComposingText implements NoCopySpan {
}
@@ -504,7 +506,7 @@
*/
@Nullable
public CharSequence getTextBeforeCursor(@IntRange(from = 0) int length, int flags) {
- if (length < 0) return null;
+ Preconditions.checkArgumentNonnegative(length);
final Editable content = getEditable();
if (content == null) return null;
@@ -563,7 +565,7 @@
*/
@Nullable
public CharSequence getTextAfterCursor(@IntRange(from = 0) int length, int flags) {
- if (length < 0) return null;
+ Preconditions.checkArgumentNonnegative(length);
final Editable content = getEditable();
if (content == null) return null;
@@ -600,7 +602,8 @@
@Nullable
public SurroundingText getSurroundingText(
@IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags) {
- if (beforeLength < 0 || afterLength < 0) return null;
+ Preconditions.checkArgumentNonnegative(beforeLength);
+ Preconditions.checkArgumentNonnegative(afterLength);
final Editable content = getEditable();
if (content == null) return null;
@@ -927,9 +930,9 @@
}
/**
- * Default implementation which invokes {@link View#onReceiveContent} on the target view if the
- * view {@link View#getOnReceiveContentMimeTypes allows} content insertion; otherwise returns
- * false without any side effects.
+ * Default implementation which invokes {@link View#performReceiveContent} on the target
+ * view if the view {@link View#getOnReceiveContentMimeTypes allows} content insertion;
+ * otherwise returns false without any side effects.
*/
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
ClipDescription description = inputContentInfo.getDescription();
@@ -954,6 +957,6 @@
.setLinkUri(inputContentInfo.getLinkUri())
.setExtras(opts)
.build();
- return mTargetView.onReceiveContent(payload) == null;
+ return mTargetView.performReceiveContent(payload) == null;
}
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index da14f2c..00ba326 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2872,7 +2872,7 @@
final OnReceiveContentListener.Payload payload =
new OnReceiveContentListener.Payload.Builder(clip, SOURCE_DRAG_AND_DROP)
.build();
- mTextView.onReceiveContent(payload);
+ mTextView.performReceiveContent(payload);
if (dragDropIntoItself) {
deleteSourceAfterLocalDrop(dragLocalState, offset, originalLength);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fb13807..98f8087 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2152,7 +2152,7 @@
if (isTextEditable()) {
ClipData clip = ClipData.newPlainText("", result);
Payload payload = new Payload.Builder(clip, SOURCE_PROCESS_TEXT).build();
- onReceiveContent(payload);
+ performReceiveContent(payload);
if (mEditor != null) {
mEditor.refreshTextActionMode();
}
@@ -11858,7 +11858,7 @@
return;
}
final Payload payload = new Payload.Builder(clip, SOURCE_AUTOFILL).build();
- onReceiveContent(payload);
+ performReceiveContent(payload);
}
@Override
@@ -12927,7 +12927,7 @@
final Payload payload = new Payload.Builder(clip, SOURCE_CLIPBOARD)
.setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT)
.build();
- onReceiveContent(payload);
+ performReceiveContent(payload);
sLastCutCopyOrTextChangedTime = 0;
}
@@ -13728,17 +13728,12 @@
}
/**
- * Receives the given content. Clients wishing to provide custom behavior should configure a
- * listener via {@link #setOnReceiveContentListener}.
+ * Default {@link TextView} implementation for receiving content. Apps wishing to provide
+ * custom behavior should configure a listener via {@link #setOnReceiveContentListener}.
*
- * <p>If a listener is set, invokes the listener. If the listener returns a non-null result,
- * executes the default platform handling for the portion of the content returned by the
- * listener.
- *
- * <p>If no listener is set, executes the default platform behavior. For non-editable TextViews
- * the default behavior is a no-op (returns the passed-in content without acting on it). For
- * editable TextViews the default behavior coerces all content to text and inserts into the
- * view.
+ * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in
+ * content without acting on it). For editable TextViews the default behavior coerces all
+ * content to text and inserts into the view.
*
* @param payload The content to insert and related metadata.
*
@@ -13747,11 +13742,10 @@
*/
@Override
public @Nullable Payload onReceiveContent(@NonNull Payload payload) {
- Payload remaining = super.onReceiveContent(payload);
- if (remaining != null && mEditor != null) {
- return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, remaining);
+ if (mEditor != null) {
+ return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload);
}
- return remaining;
+ return payload;
}
private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index e6a1661..6d98a59 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -52,7 +52,7 @@
@UnsupportedAppUsage
byte[] getStatistics();
- ParcelFileDescriptor getStatisticsStream();
+ ParcelFileDescriptor getStatisticsStream(boolean updateAll);
// Return true if we see the battery as currently charging.
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index c0d46f6..571ac1b 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -37,34 +37,33 @@
//TODO (163431584): need also consider other refresh rates.
private static final long JANK_THRESHOLD_NANOS = 1000000000 / 60;
private static final long UNKNOWN_TIMESTAMP = -1;
+ public static final int NANOS_IN_MILLISECOND = 1_000_000;
private final HardwareRendererObserver mObserver;
+ private final int mTraceThresholdMissedFrames;
+ private final int mTraceThresholdFrameTimeMillis;
private final ThreadedRendererWrapper mRendererWrapper;
private final FrameMetricsWrapper mMetricsWrapper;
private long mBeginTime = UNKNOWN_TIMESTAMP;
private long mEndTime = UNKNOWN_TIMESTAMP;
- private boolean mShouldTriggerTrace;
private boolean mSessionEnd;
+ private boolean mCancelled = false;
private int mTotalFramesCount = 0;
private int mMissedFramesCount = 0;
private long mMaxFrameTimeNanos = 0;
private Session mSession;
- /**
- * Constructor of FrameTracker.
- * @param session a trace session.
- * @param handler a handler for handling callbacks.
- * @param renderer a ThreadedRendererWrapper instance.
- * @param metrics a FrameMetricsWrapper instance.
- */
public FrameTracker(@NonNull Session session, @NonNull Handler handler,
- @NonNull ThreadedRendererWrapper renderer, @NonNull FrameMetricsWrapper metrics) {
+ @NonNull ThreadedRendererWrapper renderer, @NonNull FrameMetricsWrapper metrics,
+ int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis) {
mSession = session;
mRendererWrapper = renderer;
mMetricsWrapper = metrics;
mObserver = new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), handler);
+ mTraceThresholdMissedFrames = traceThresholdMissedFrames;
+ mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis;
}
/**
@@ -109,13 +108,15 @@
}
Trace.endAsyncSection(mSession.getName(), (int) mBeginTime);
mRendererWrapper.removeObserver(mObserver);
- mBeginTime = UNKNOWN_TIMESTAMP;
- mEndTime = UNKNOWN_TIMESTAMP;
- mShouldTriggerTrace = false;
+ mCancelled = true;
}
@Override
public synchronized void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
+ if (mCancelled) {
+ return;
+ }
+
// Since this callback might come a little bit late after the end() call.
// We should keep tracking the begin / end timestamp.
// Then compare with vsync timestamp to check if the frame is in the duration of the CUJ.
@@ -136,13 +137,14 @@
Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames",
mTotalFramesCount);
Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis",
- (int) (mMaxFrameTimeNanos / 1_000_000));
+ (int) (mMaxFrameTimeNanos / NANOS_IN_MILLISECOND));
// Trigger perfetto if necessary.
- if (mShouldTriggerTrace) {
- if (DEBUG) {
- Log.v(TAG, "Found janky frame, triggering perfetto.");
- }
+ boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
+ && mMissedFramesCount >= mTraceThresholdMissedFrames;
+ boolean overFrameTimeThreshold = mTraceThresholdFrameTimeMillis != -1
+ && mMaxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND;
+ if (overMissedFramesThreshold || overFrameTimeThreshold) {
triggerPerfetto();
}
if (mSession.logToStatsd()) {
@@ -168,7 +170,6 @@
if (isJankyFrame) {
mMissedFramesCount += 1;
- mShouldTriggerTrace = true;
}
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index fcaa963..771a72c 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -36,7 +36,10 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.os.Build;
+import android.os.HandlerExecutor;
import android.os.HandlerThread;
+import android.provider.DeviceConfig;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
@@ -47,6 +50,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
/**
@@ -55,9 +59,21 @@
*/
public class InteractionJankMonitor {
private static final String TAG = InteractionJankMonitor.class.getSimpleName();
- private static final boolean DEBUG = false;
private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L);
+ private static final String SETTINGS_ENABLED_KEY = "enabled";
+ private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
+ private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY =
+ "trace_threshold_missed_frames";
+ private static final String SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY =
+ "trace_threshold_frame_time_millis";
+ /** Default to being enabled on debug builds. */
+ private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE;
+ /** Default to collecting data for all CUJs. */
+ private static final int DEFAULT_SAMPLING_INTERVAL = 1;
+ /** Default to triggering trace if 3 frames are missed OR a frame takes at least 64ms */
+ private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3;
+ private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
// Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
@@ -106,12 +122,20 @@
private static volatile InteractionJankMonitor sInstance;
+ private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
+ this::updateProperties;
+
private ThreadedRendererWrapper mRenderer;
private FrameMetricsWrapper mMetrics;
private SparseArray<FrameTracker> mRunningTrackers;
private SparseArray<Runnable> mTimeoutActions;
private HandlerThread mWorker;
+
private boolean mInitialized;
+ private boolean mEnabled = DEFAULT_ENABLED;
+ private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
+ private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES;
+ private int mTraceThresholdFrameTimeMillis = DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS;
/** @hide */
@IntDef({
@@ -134,10 +158,12 @@
CUJ_NOTIFICATION_APP_START,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface CujType {}
+ public @interface CujType {
+ }
/**
* Get the singleton of InteractionJankMonitor.
+ *
* @return instance of InteractionJankMonitor
*/
public static InteractionJankMonitor getInstance() {
@@ -154,6 +180,7 @@
/**
* This constructor should be only public to tests.
+ *
* @param worker the worker thread for the callbacks
*/
@VisibleForTesting
@@ -165,11 +192,11 @@
/**
* Init InteractionJankMonitor for later instrumentation.
+ *
* @param view Any view in the view tree to get context and ThreadedRenderer.
* @return boolean true if the instance has been initialized successfully.
*/
public boolean init(@NonNull View view) {
- //TODO (163505250): This should be no-op if not in droid food rom.
if (!mInitialized) {
synchronized (this) {
if (!mInitialized) {
@@ -180,7 +207,20 @@
mRenderer = new ThreadedRendererWrapper(view.getThreadedRenderer());
mMetrics = new FrameMetricsWrapper();
mWorker.start();
+ mEnabled = DEFAULT_ENABLED;
+ mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
mInitialized = true;
+
+ // Post initialization to the background in case we're running on the main
+ // thread.
+ mWorker.getThreadHandler().post(
+ () -> mPropertiesChangedListener.onPropertiesChanged(
+ DeviceConfig.getProperties(
+ DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
+ new HandlerExecutor(mWorker.getThreadHandler()),
+ mPropertiesChangedListener);
}
}
}
@@ -189,6 +229,7 @@
/**
* Create a {@link FrameTracker} instance.
+ *
* @param session the session associates with this tracker
* @return instance of the FrameTracker
*/
@@ -196,47 +237,50 @@
public FrameTracker createFrameTracker(Session session) {
synchronized (this) {
if (!mInitialized) return null;
- return new FrameTracker(session, mWorker.getThreadHandler(), mRenderer, mMetrics);
+ return new FrameTracker(session, mWorker.getThreadHandler(), mRenderer, mMetrics,
+ mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis);
}
}
/**
* Begin a trace session, must invoke {@link #init(View)} before invoking this method.
+ *
* @param cujType the specific {@link InteractionJankMonitor.CujType}.
* @return boolean true if the tracker is started successfully, false otherwise.
*/
public boolean begin(@CujType int cujType) {
- //TODO (163505250): This should be no-op if not in droid food rom.
synchronized (this) {
- return begin(cujType, 0L /* timeout */);
+ return begin(cujType, DEFAULT_TIMEOUT_MS);
}
}
/**
* Begin a trace session, must invoke {@link #init(View)} before invoking this method.
+ *
* @param cujType the specific {@link InteractionJankMonitor.CujType}.
* @param timeout the elapsed time in ms until firing the timeout action.
* @return boolean true if the tracker is started successfully, false otherwise.
*/
public boolean begin(@CujType int cujType, long timeout) {
- //TODO (163505250): This should be no-op if not in droid food rom.
synchronized (this) {
if (!mInitialized) {
Log.d(TAG, "Not initialized!", new Throwable());
return false;
}
- Session session = new Session(cujType);
- FrameTracker tracker = getTracker(session);
+ boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+ if (!mEnabled || !shouldSample) {
+ return false;
+ }
+ FrameTracker tracker = getTracker(cujType);
// Skip subsequent calls if we already have an ongoing tracing.
if (tracker != null) return false;
// begin a new trace session.
- tracker = createFrameTracker(session);
+ tracker = createFrameTracker(new Session(cujType));
mRunningTrackers.put(cujType, tracker);
tracker.begin();
// Cancel the trace if we don't get an end() call in specified duration.
- timeout = timeout > 0L ? timeout : DEFAULT_TIMEOUT_MS;
Runnable timeoutAction = () -> cancel(cujType);
mTimeoutActions.put(cujType, timeoutAction);
mWorker.getThreadHandler().postDelayed(timeoutAction, timeout);
@@ -246,6 +290,7 @@
/**
* End a trace session, must invoke {@link #init(View)} before invoking this method.
+ *
* @param cujType the specific {@link InteractionJankMonitor.CujType}.
* @return boolean true if the tracker is ended successfully, false otherwise.
*/
@@ -263,18 +308,18 @@
mTimeoutActions.remove(cujType);
}
- Session session = new Session(cujType);
- FrameTracker tracker = getTracker(session);
+ FrameTracker tracker = getTracker(cujType);
// Skip this call since we haven't started a trace yet.
if (tracker == null) return false;
tracker.end();
- mRunningTrackers.remove(session.getCuj());
+ mRunningTrackers.remove(cujType);
return true;
}
}
/**
* Cancel the trace session, must invoke {@link #init(View)} before invoking this method.
+ *
* @return boolean true if the tracker is cancelled successfully, false otherwise.
*/
public boolean cancel(@CujType int cujType) {
@@ -291,50 +336,40 @@
mTimeoutActions.remove(cujType);
}
- Session session = new Session(cujType);
- FrameTracker tracker = getTracker(session);
+ FrameTracker tracker = getTracker(cujType);
// Skip this call since we haven't started a trace yet.
if (tracker == null) return false;
tracker.cancel();
- mRunningTrackers.remove(session.getCuj());
+ mRunningTrackers.remove(cujType);
return true;
}
}
- private void destroy() {
- synchronized (this) {
- int trackers = mRunningTrackers.size();
- for (int i = 0; i < trackers; i++) {
- mRunningTrackers.valueAt(i).cancel();
- }
- mRunningTrackers = null;
- mTimeoutActions.clear();
- mTimeoutActions = null;
- mWorker.quit();
- mWorker = null;
- }
- }
-
- /**
- * Abandon current instance.
- */
- @VisibleForTesting
- public static void abandon() {
- if (sInstance == null) return;
- synchronized (InteractionJankMonitor.class) {
- if (sInstance == null) return;
- sInstance.destroy();
- sInstance = null;
- }
- }
-
- private FrameTracker getTracker(Session session) {
+ private FrameTracker getTracker(@CujType int cuj) {
synchronized (this) {
if (!mInitialized) return null;
- return mRunningTrackers.get(session.getCuj());
+ return mRunningTrackers.get(cuj);
}
}
+ private void updateProperties(DeviceConfig.Properties properties) {
+ synchronized (this) {
+ mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
+ DEFAULT_SAMPLING_INTERVAL);
+ mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
+ mTraceThresholdMissedFrames = properties.getInt(SETTINGS_THRESHOLD_MISSED_FRAMES_KEY,
+ DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
+ mTraceThresholdFrameTimeMillis = properties.getInt(
+ SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY,
+ DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
+ }
+ }
+
+ @VisibleForTesting
+ public DeviceConfig.OnPropertiesChangedListener getPropertiesChangedListener() {
+ return mPropertiesChangedListener;
+ }
+
/**
* Trigger the perfetto daemon to collect and upload data.
*/
@@ -402,12 +437,14 @@
* A class to represent a session.
*/
public static class Session {
- private @CujType int mCujType;
+ @CujType
+ private int mCujType;
public Session(@CujType int cujType) {
mCujType = cujType;
}
+ @CujType
public int getCuj() {
return mCujType;
}
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 41be5c4..2237efc 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -57,6 +57,7 @@
public static String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
public static String SYSTEM_CHANGES = "SYSTEM_CHANGES";
public static String DO_NOT_DISTURB = "DO_NOT_DISTURB";
+ public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
public static void createAll(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -191,6 +192,13 @@
NotificationManager.IMPORTANCE_LOW);
channelsList.add(dndChanges);
+ final NotificationChannel newFeaturePrompt = new NotificationChannel(
+ ACCESSIBILITY_MAGNIFICATION,
+ context.getString(R.string.notification_channel_accessibility_magnification),
+ NotificationManager.IMPORTANCE_HIGH);
+ newFeaturePrompt.setBlockable(true);
+ channelsList.add(newFeaturePrompt);
+
nm.createNotificationChannels(channelsList);
}
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index c110b26..c85b5d7 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -18,9 +18,10 @@
package com.android.internal.os;
import android.compat.annotation.UnsupportedAppUsage;
-import android.os.BasicShellCommandHandler;
import android.os.Build;
+import com.android.modules.utils.BasicShellCommandHandler;
+
import java.io.PrintStream;
public abstract class BaseCommand {
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 2676745..9e59e50 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -203,7 +203,7 @@
}
}
return getStats(IBatteryStats.Stub.asInterface(
- ServiceManager.getService(BatteryStats.SERVICE_NAME)));
+ ServiceManager.getService(BatteryStats.SERVICE_NAME)), true);
}
@UnsupportedAppUsage
@@ -223,8 +223,13 @@
@UnsupportedAppUsage
public BatteryStats getStats() {
+ return getStats(true /* updateAll */);
+ }
+
+ /** Retrieves stats from BatteryService, optionally getting updated numbers */
+ public BatteryStats getStats(boolean updateAll) {
if (mStats == null) {
- load();
+ load(updateAll);
}
return mStats;
}
@@ -720,19 +725,23 @@
@UnsupportedAppUsage
private void load() {
+ load(true);
+ }
+
+ private void load(boolean updateAll) {
if (mBatteryInfo == null) {
return;
}
- mStats = getStats(mBatteryInfo);
+ mStats = getStats(mBatteryInfo, updateAll);
if (mCollectBatteryBroadcast) {
mBatteryBroadcast = mContext.registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
}
- private static BatteryStatsImpl getStats(IBatteryStats service) {
+ private static BatteryStatsImpl getStats(IBatteryStats service, boolean updateAll) {
try {
- ParcelFileDescriptor pfd = service.getStatisticsStream();
+ ParcelFileDescriptor pfd = service.getStatisticsStream(updateAll);
if (pfd != null) {
if (false) {
Log.d(TAG, "selinux context: "
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 854fb17..44dca9b 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -69,6 +69,6 @@
void onRegistrationFailed(in CellIdentity cellIdentity,
String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
void onBarringInfoChanged(in BarringInfo barringInfo);
- void onPhysicalChannelConfigChanged(in List<PhysicalChannelConfig> configs);
- void onDataEnabledChanged(boolean enabled, int reason);
+ void onPhysicalChannelConfigurationChanged(in List<PhysicalChannelConfig> configs);
+
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index ae1657b..a28a663 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -41,10 +41,16 @@
IOnSubscriptionsChangedListener callback);
void removeOnSubscriptionsChangedListener(String pkg,
IOnSubscriptionsChangedListener callback);
-
- void listenWithEventList(in int subId, String pkg, String featureId,
- IPhoneStateListener callback, in int[] events, boolean notifyNow);
-
+ /**
+ * @deprecated Use {@link #listenWithFeature(String, String, IPhoneStateListener, int,
+ * boolean) instead
+ */
+ @UnsupportedAppUsage
+ void listen(String pkg, IPhoneStateListener callback, int events, boolean notifyNow);
+ void listenWithFeature(String pkg, String featureId, IPhoneStateListener callback, long events,
+ boolean notifyNow);
+ void listenForSubscriber(in int subId, String pkg, String featureId,
+ IPhoneStateListener callback, long events, boolean notifyNow);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
void notifyCallStateForAllSubs(int state, String incomingNumber);
void notifyCallState(in int phoneId, in int subId, int state, String incomingNumber);
@@ -93,6 +99,6 @@
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);
- void notifyPhysicalChannelConfigForSubscriber(in int subId,
+ void notifyPhysicalChannelConfigurationForSubscriber(in int subId,
in List<PhysicalChannelConfig> configs);
}
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index a87e8aa..8012540 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -15,15 +15,11 @@
package com.android.internal.util;
import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
import android.os.Build;
import android.os.SystemClock;
import android.os.Trace;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.util.EventLog;
-import android.util.KeyValueListParser;
import android.util.Log;
import android.util.SparseLongArray;
@@ -135,8 +131,16 @@
mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
// Post initialization to the background in case we're running on the main thread.
- BackgroundThread.getHandler().post(this::registerSettingsObserver);
- BackgroundThread.getHandler().post(this::readSettings);
+ BackgroundThread.getHandler().post(() -> this.updateProperties(
+ DeviceConfig.getProperties(DeviceConfig.NAMESPACE_LATENCY_TRACKER)));
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
+ BackgroundThread.getExecutor(), this::updateProperties);
+ }
+
+ private void updateProperties(DeviceConfig.Properties properties) {
+ mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
+ DEFAULT_SAMPLING_INTERVAL);
+ mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
}
/**
@@ -171,28 +175,6 @@
}
}
- private void registerSettingsObserver() {
- Uri settingsUri = Settings.Global.getUriFor(Settings.Global.LATENCY_TRACKER);
- mContext.getContentResolver().registerContentObserver(
- settingsUri, false, new SettingsObserver(this), UserHandle.myUserId());
- }
-
- private void readSettings() {
- KeyValueListParser parser = new KeyValueListParser(',');
- String settingsValue = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.LATENCY_TRACKER);
-
- try {
- parser.setString(settingsValue);
- mSamplingInterval = parser.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
- DEFAULT_SAMPLING_INTERVAL);
- mEnabled = parser.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Incorrect settings format", e);
- mEnabled = false;
- }
- }
-
public static boolean isEnabled(Context ctx) {
return getInstance(ctx).isEnabled();
}
@@ -236,8 +218,8 @@
/**
* Logs an action that has started and ended. This needs to be called from the main thread.
*
- * @param action The action to end. One of the ACTION_* values.
- * @param duration The duration of the action in ms.
+ * @param action The action to end. One of the ACTION_* values.
+ * @param duration The duration of the action in ms.
*/
public void logAction(int action, int duration) {
boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
@@ -260,18 +242,4 @@
FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED, STATSD_ACTION[action], duration);
}
}
-
- private static class SettingsObserver extends ContentObserver {
- private final LatencyTracker mThisTracker;
-
- SettingsObserver(LatencyTracker thisTracker) {
- super(BackgroundThread.getHandler());
- mThisTracker = thisTracker;
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
- mThisTracker.readSettings();
- }
- }
}
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 1ea918a..1c78750 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -50,6 +50,7 @@
jmethodID dispatchInputEvent;
jmethodID onFocusEvent;
+ jmethodID onPointerCaptureEvent;
jmethodID onBatchedInputEventPending;
} gInputEventReceiverClassInfo;
@@ -345,6 +346,19 @@
finishInputEvent(seq, true /* handled */);
continue;
}
+ case AINPUT_EVENT_TYPE_CAPTURE: {
+ const CaptureEvent* captureEvent = static_cast<CaptureEvent*>(inputEvent);
+ if (kDebugDispatchCycle) {
+ ALOGD("channel '%s' ~ Received capture event: pointerCaptureEnabled=%s",
+ getInputChannelName().c_str(),
+ toString(captureEvent->getPointerCaptureEnabled()));
+ }
+ env->CallVoidMethod(receiverObj.get(),
+ gInputEventReceiverClassInfo.onPointerCaptureEvent,
+ jboolean(captureEvent->getPointerCaptureEnabled()));
+ finishInputEvent(seq, true /* handled */);
+ continue;
+ }
default:
assert(false); // InputConsumer should prevent this from ever happening
@@ -489,6 +503,9 @@
"dispatchInputEvent", "(ILandroid/view/InputEvent;)V");
gInputEventReceiverClassInfo.onFocusEvent =
GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onFocusEvent", "(ZZ)V");
+ gInputEventReceiverClassInfo.onPointerCaptureEvent =
+ GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onPointerCaptureEvent",
+ "(Z)V");
gInputEventReceiverClassInfo.onBatchedInputEventPending =
GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onBatchedInputEventPending",
"(I)V");
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 6ffafc1..3156f71 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -78,7 +78,6 @@
#include <android-base/unique_fd.h>
#include <bionic/malloc.h>
#include <bionic/mte.h>
-#include <bionic/mte_kernel.h>
#include <cutils/fs.h>
#include <cutils/memory.h>
#include <cutils/multiuser.h>
@@ -1650,28 +1649,6 @@
}
}
-#ifdef ANDROID_EXPERIMENTAL_MTE
-static void SetTagCheckingLevel(int level) {
-#ifdef __aarch64__
- if (!(getauxval(AT_HWCAP2) & HWCAP2_MTE)) {
- return;
- }
-
- int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
- if (tagged_addr_ctrl < 0) {
- ALOGE("prctl(PR_GET_TAGGED_ADDR_CTRL) failed: %s", strerror(errno));
- return;
- }
-
- tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | level;
- if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) < 0) {
- ALOGE("prctl(PR_SET_TAGGED_ADDR_CTRL, %d) failed: %s", tagged_addr_ctrl,
- strerror(errno));
- }
-#endif
-}
-#endif
-
// Utility routine to specialize a zygote child process.
static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
@@ -1807,22 +1784,14 @@
heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI;
break;
case RuntimeFlags::MEMORY_TAG_LEVEL_ASYNC:
-#ifdef ANDROID_EXPERIMENTAL_MTE
- SetTagCheckingLevel(PR_MTE_TCF_ASYNC);
-#endif
heap_tagging_level = M_HEAP_TAGGING_LEVEL_ASYNC;
break;
case RuntimeFlags::MEMORY_TAG_LEVEL_SYNC:
-#ifdef ANDROID_EXPERIMENTAL_MTE
- SetTagCheckingLevel(PR_MTE_TCF_SYNC);
-#endif
heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC;
break;
default:
-#ifdef ANDROID_EXPERIMENTAL_MTE
- SetTagCheckingLevel(PR_MTE_TCF_NONE);
-#endif
heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE;
+ break;
}
android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level));
// Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
diff --git a/core/proto/android/stats/camera/Android.bp b/core/proto/android/stats/camera/Android.bp
new file mode 100644
index 0000000..cc75e57
--- /dev/null
+++ b/core/proto/android/stats/camera/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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: "cameraprotosnano",
+ proto: {
+ type: "nano",
+ },
+ srcs: [
+ "*.proto",
+ ],
+ java_version: "1.8",
+ target: {
+ android: {
+ jarjar_rules: "jarjar-rules.txt",
+ },
+ host: {
+ static_libs: ["libprotobuf-java-nano"],
+ }
+ },
+ sdk_version: "core_platform",
+}
diff --git a/core/proto/android/stats/camera/camera.proto b/core/proto/android/stats/camera/camera.proto
new file mode 100644
index 0000000..4062855
--- /dev/null
+++ b/core/proto/android/stats/camera/camera.proto
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.stats.camera;
+option java_multiple_files = true;
+
+message CameraStreamProto {
+ // The stream width (in pixels)
+ optional int32 width = 1;
+ // The stream height (in pixels)
+ optional int32 height = 2;
+ // The format of the stream
+ optional int32 format = 3;
+ // The dataspace of the stream
+ optional int32 data_space = 4;
+ // The usage flag of the stream
+ optional int64 usage = 5;
+
+ // The number of requests for this stream
+ optional int64 request_count = 6;
+ // The number of buffer error for this stream
+ optional int64 error_count = 7;
+ // The capture latency of first request for this stream
+ optional int32 first_capture_latency_millis = 8;
+
+ // The maximum number of hal buffers
+ optional int32 max_hal_buffers = 9;
+ // The maximum number of app buffers
+ optional int32 max_app_buffers = 10;
+}
diff --git a/core/proto/android/stats/camera/jarjar-rules.txt b/core/proto/android/stats/camera/jarjar-rules.txt
new file mode 100644
index 0000000..40043a86
--- /dev/null
+++ b/core/proto/android/stats/camera/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.nano.** com.android.framework.protobuf.nano.@1
diff --git a/core/proto/android/stats/tv/tif_enums.proto b/core/proto/android/stats/tv/tif_enums.proto
new file mode 100644
index 0000000..a9028e5
--- /dev/null
+++ b/core/proto/android/stats/tv/tif_enums.proto
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.stats.tv;
+option java_multiple_files = true;
+
+// Enums for TV Input Framework
+option java_outer_classname = "TifStatsEnums";
+
+// Tune State of a TV Input Service Framework
+enum TifTuneState {
+ TIF_TUNE_STATE_UNKNOWN = 0;
+ CREATED = 1;
+ SURFACE_ATTACHED = 2;
+ SURFACE_DETACHED = 3;
+ RELEASED = 4;
+ TUNE_STARTED = 5;
+ VIDEO_AVAILABLE = 6;
+
+ // Keep in sync with TvInputManager
+ // Use the TvInputManager value + 100
+ VIDEO_UNAVAILABLE_REASON_UNKNOWN = 100;
+ VIDEO_UNAVAILABLE_REASON_TUNING = 101;
+ VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 102;
+ VIDEO_UNAVAILABLE_REASON_BUFFERING = 103;
+ VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 104;
+ VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = 105;
+ VIDEO_UNAVAILABLE_REASON_INSUFFICIENT_RESOURCE = 106;
+ VIDEO_UNAVAILABLE_REASON_CAS_INSUFFICIENT_OUTPUT_PROTECTION=107;
+ VIDEO_UNAVAILABLE_REASON_CAS_PVR_RECORDING_NOT_ALLOWED=108;
+ VIDEO_UNAVAILABLE_REASON_CAS_NO_LICENSE=109;
+ VIDEO_UNAVAILABLE_REASON_CAS_LICENSE_EXPIRED=110;
+ VIDEO_UNAVAILABLE_REASON_CAS_NEED_ACTIVATION=111;
+ VIDEO_UNAVAILABLE_REASON_CAS_NEED_PAIRING=112;
+ VIDEO_UNAVAILABLE_REASON_CAS_NO_CARD=113;
+ VIDEO_UNAVAILABLE_REASON_CAS_CARD_MUTE=114;
+ VIDEO_UNAVAILABLE_REASON_CAS_CARD_INVALID=115;
+ VIDEO_UNAVAILABLE_REASON_CAS_BLACKOUT=116;
+ VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING=117;
+ VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN=118;
+}
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index 552a1bd..9662b8e 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -28,7 +28,6 @@
android:layout_height="wrap_content"
android:gravity="end"
android:orientation="horizontal"
- android:paddingEnd="@dimen/bubble_gone_padding_end"
android:background="@color/notification_action_list_background_color"
>
@@ -45,22 +44,34 @@
<!-- actions will be added here -->
</com.android.internal.widget.NotificationActionListLayout>
- <ImageView
- android:id="@+id/snooze_button"
- android:layout_width="@dimen/notification_actions_icon_size"
- android:layout_height="@dimen/notification_actions_icon_size"
- android:layout_gravity="center_vertical|end"
- android:visibility="gone"
- android:scaleType="centerInside"
- />
+ <!--
+ This nested linear layout exists to ensure that if the neither of the contained
+ actions is visible we have some minimum padding at the end of the actions is present,
+ then there will be 12dp of padding at the end of the actions list.
+ -->
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:minWidth="@dimen/snooze_and_bubble_gone_padding_end"
+ >
+ <ImageView
+ android:id="@+id/snooze_button"
+ android:layout_width="@dimen/notification_actions_icon_size"
+ android:layout_height="@dimen/notification_actions_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:visibility="gone"
+ android:scaleType="centerInside"
+ />
- <ImageView
- android:id="@+id/bubble_button"
- android:layout_width="@dimen/notification_actions_icon_size"
- android:layout_height="@dimen/notification_actions_icon_size"
- android:layout_gravity="center_vertical|end"
- android:visibility="gone"
- android:scaleType="centerInside"
- />
+ <ImageView
+ android:id="@+id/bubble_button"
+ android:layout_width="@dimen/notification_actions_icon_size"
+ android:layout_height="@dimen/notification_actions_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:visibility="gone"
+ android:scaleType="centerInside"
+ />
+ </LinearLayout>
</LinearLayout>
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index a26473a..b0ee12a 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -50,20 +50,18 @@
android:theme="@style/Theme.DeviceDefault.Notification"
>
- <TextView
- android:id="@+id/app_name_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:singleLine="true"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:visibility="?attr/notificationHeaderAppNameVisibility"
- />
-
<include layout="@layout/notification_top_line_views" />
</NotificationTopLineView>
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ />
+
<com.android.internal.widget.NotificationExpandButton
android:id="@+id/expand_button"
android:layout_width="@dimen/notification_header_expand_icon_size"
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index ded16b7..69d4a12 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -68,6 +68,12 @@
android:theme="@style/Theme.DeviceDefault.Notification"
>
+ <!--
+ NOTE: The notification_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
@@ -138,6 +144,14 @@
/>
<FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ />
+
+ <FrameLayout
android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index 520ae28..f9364d5 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -327,10 +327,10 @@
android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="@dimen/conversation_expand_button_size"
- android:paddingStart="16dp"
+ android:paddingStart="@dimen/conversation_expand_button_side_margin"
android:orientation="horizontal"
android:layout_gravity="end|top"
- android:paddingEnd="@dimen/notification_content_margin_end"
+ android:paddingEnd="@dimen/conversation_expand_button_side_margin"
android:clipToPadding="false"
android:clipChildren="false"
>
diff --git a/core/res/res/layout/notification_top_line_views.xml b/core/res/res/layout/notification_top_line_views.xml
index 51974ac..c71e886 100644
--- a/core/res/res/layout/notification_top_line_views.xml
+++ b/core/res/res/layout/notification_top_line_views.xml
@@ -14,13 +14,23 @@
~ limitations under the License
-->
<!--
- This layout file should be included inside a NotificationTopLineView, usually after either a
- <TextView android:id="@+id/app_name_text"/> or <TextView android:id="@+id/title"/>
+ This layout file should be included inside a NotificationTopLineView, sometimes after a
+ <TextView android:id="@+id/title"/>
-->
<merge
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
+ android:id="@+id/app_name_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:singleLine="true"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:visibility="?attr/notificationHeaderAppNameVisibility"
+ />
+
+ <TextView
android:id="@+id/header_text_secondary_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index fc9f670..c32e8dc 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2834,6 +2834,38 @@
<attr name="resource" format="reference" />
</declare-styleable>
+ <!-- The <code>property</code> tag is used to attach additional data that can
+ be supplied to the parent component. A component element can contain any
+ number of <code>property</code> subelements. Valid names are any of the
+ <code>PROPERTY_</code> constants defined in the
+ {@link android.content.pm.PackageManager PackageManager} class. Values
+ are obtained using the appropriate method on the
+ {@link android.content.pm.PackageManager.Property PackageManager.Property} class.
+ <p>Ordinary values are specified through the value attribute. Resource IDs are
+ specified through the resource attribute.
+ <p>It is invalid to specify both a value and resource attributes. -->
+ <declare-styleable name="AndroidManifestProperty"
+ parent="AndroidManifestApplication
+ AndroidManifestActivity
+ AndroidManifestReceiver
+ AndroidManifestProvider
+ AndroidManifestService">
+ <attr name="name" />
+ <!-- Concrete value to assign to this property.
+ The data can later be retrieved from the property object
+ through
+ {@link android.content.pm.PackageManager.Property#getString Property.getString},
+ {@link android.content.pm.PackageManager.Property#getInteger Property.getInteger},
+ {@link android.content.pm.PackageManager.Property#getBoolean Property.getBoolean},
+ or {@link android.content.pm.PackageManager.Property#getFloat Property.getFloat}
+ depending on the type used here. -->
+ <attr name="value" />
+ <!-- The resource identifier to assign to this property.
+ The resource identifier can later be retrieved from the property object through
+ {@link android.content.pm.PackageManager.Property#getResourceId Property.getResourceId}. -->
+ <attr name="resource" />
+ </declare-styleable>
+
<!-- The <code>intent-filter</code> tag is used to construct an
{@link android.content.IntentFilter} object that will be used
to determine which component can handle a particular
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 79eae67..19591f6 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -259,6 +259,9 @@
<!-- The height of the background for a notification header on a group -->
<dimen name="notification_header_background_height">49.5dp</dimen>
+ <!-- The height of the full-width touch rectangle for the notification header -->
+ <dimen name="notification_header_touchable_height">36dp</dimen>
+
<!-- The top padding for the notification header -->
<dimen name="notification_header_padding_top">16dp</dimen>
@@ -340,11 +343,8 @@
<!-- The margin of the content to an image-->
<dimen name="notification_content_image_margin_end">8dp</dimen>
- <!-- The padding at the end of actions when the bubble button is visible-->
- <dimen name="bubble_visible_padding_end">3dp</dimen>
-
- <!-- The padding at the end of actions when the bubble button is gone-->
- <dimen name="bubble_gone_padding_end">12dp</dimen>
+ <!-- The padding at the end of actions when the snooze and bubble buttons are gone-->
+ <dimen name="snooze_and_bubble_gone_padding_end">12dp</dimen>
<!-- The spacing between messages in Notification.MessagingStyle -->
<dimen name="notification_messaging_spacing">6dp</dimen>
@@ -744,6 +744,9 @@
<dimen name="conversation_expand_button_size">80dp</dimen>
<!-- Top margin of the expand button for conversations when expanded -->
<dimen name="conversation_expand_button_top_margin_expanded">18dp</dimen>
+ <!-- Side margin of the expand button for conversations.
+ width of expand asset (22) + 2 * this (13) == notification_header_expand_icon_size (48) -->
+ <dimen name="conversation_expand_button_side_margin">13dp</dimen>
<!-- Side margins of the conversation badge in relation to the conversation icon -->
<dimen name="conversation_badge_side_margin">36dp</dimen>
<!-- size of the notification badge when applied to the conversation icon -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8eb0853..be6b6b1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -717,6 +717,10 @@
[CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6665375982962336520] -->
<string name="notification_channel_foreground_service">Apps consuming battery</string>
+ <!-- Text shown when viewing channel settings for notifications related to accessibility
+ magnification. [CHAR_LIMIT=NONE]-->
+ <string name="notification_channel_accessibility_magnification">Magnification</string>
+
<!-- Label for foreground service notification when one app is running.
[CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] -->
<string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is
@@ -5767,4 +5771,16 @@
<string name="config_pdp_reject_service_not_subscribed"></string>
<!-- pdp data reject dialog string for cause 55 (MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED) [CHAR LIMIT=100] -->
<string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed"></string>
+
+ <!-- Window magnification prompt related string. -->
+
+ <!-- Notification title to prompt the user that new magnification feature is available. [CHAR LIMIT=50] -->
+ <string name="window_magnification_prompt_title">New: Window Magnifier</string>
+ <!-- Notification content to prompt the user that new magnification feature is available. [CHAR LIMIT=50] -->
+ <string name="window_magnification_prompt_content">You can now magnify some or all of your screen</string>
+ <!-- Notification action to bring the user to magnification settings page. [CHAR LIMIT=50] -->
+ <string name="turn_on_magnification_settings_action">Turn on in Settings</string>
+ <!-- Notification action to dismiss. [CHAR LIMIT=50] -->
+ <string name="dismiss_action">Dismiss</string>
+
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 23733af..40aae9e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2861,6 +2861,7 @@
<java-symbol type="id" name="header_text" />
<java-symbol type="id" name="header_text_secondary" />
<java-symbol type="id" name="expand_button" />
+ <java-symbol type="id" name="alternate_expand_target" />
<java-symbol type="id" name="notification_header" />
<java-symbol type="id" name="notification_top_line" />
<java-symbol type="id" name="time_divider" />
@@ -2878,6 +2879,7 @@
<java-symbol type="dimen" name="notification_content_margin_top" />
<java-symbol type="dimen" name="notification_content_margin" />
<java-symbol type="dimen" name="notification_header_background_height" />
+ <java-symbol type="dimen" name="notification_header_touchable_height" />
<java-symbol type="dimen" name="notification_header_expand_icon_size" />
<java-symbol type="dimen" name="notification_expand_button_padding_top" />
<java-symbol type="dimen" name="notification_header_icon_size" />
@@ -3458,6 +3460,7 @@
<java-symbol type="string" name="notification_channel_heavy_weight_app" />
<java-symbol type="string" name="notification_channel_system_changes" />
<java-symbol type="string" name="notification_channel_do_not_disturb" />
+ <java-symbol type="string" name="notification_channel_accessibility_magnification" />
<java-symbol type="string" name="config_defaultAutofillService" />
<java-symbol type="string" name="config_defaultTextClassifierPackage" />
<java-symbol type="string" name="config_defaultWellbeingPackage" />
@@ -3559,8 +3562,6 @@
<java-symbol type="id" name="clip_children_tag" />
<java-symbol type="id" name="bubble_button" />
<java-symbol type="id" name="snooze_button" />
- <java-symbol type="dimen" name="bubble_visible_padding_end" />
- <java-symbol type="dimen" name="bubble_gone_padding_end" />
<java-symbol type="dimen" name="text_size_body_2_material" />
<java-symbol type="dimen" name="messaging_avatar_size" />
<java-symbol type="dimen" name="messaging_group_sending_progress_size" />
@@ -4096,4 +4097,10 @@
<java-symbol type="dimen" name="config_taskLetterboxAspectRatio" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
+
+ <!-- Window magnification prompt -->
+ <java-symbol type="string" name="window_magnification_prompt_title" />
+ <java-symbol type="string" name="window_magnification_prompt_content" />
+ <java-symbol type="string" name="turn_on_magnification_settings_action" />
+ <java-symbol type="string" name="dismiss_action" />
</resources>
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java
index 2f2bef8..119b70a 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java
@@ -24,16 +24,18 @@
@Test
public void testBuildEmailAndGetValue() {
- AppSearchEmail email = new AppSearchEmail.Builder("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)
- .setPropertyString("propertyKey", "propertyValue1", "propertyValue2")
- .setSubject("subject")
- .setBody("EmailBody")
- .build();
+ AppSearchEmail email =
+ new AppSearchEmail.Builder("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)
+ .setPropertyString("propertyKey", "propertyValue1", "propertyValue2")
+ .setSubject("subject")
+ .setBody("EmailBody")
+ .build();
assertThat(email.getUri()).isEqualTo("uri");
assertThat(email.getFrom()).isEqualTo("FakeFromAddress");
@@ -42,8 +44,9 @@
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.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/external/app/AppSearchSchemaTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchSchemaTest.java
deleted file mode 100644
index c171270..0000000
--- a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchSchemaTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.appsearch;
-
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.expectThrows;
-
-import android.app.appsearch.AppSearchSchema.PropertyConfig;
-import android.app.appsearch.exceptions.IllegalSchemaException;
-
-import org.junit.Test;
-
-public class AppSearchSchemaTest {
- @Test
- public void testInvalidEnums() {
- PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
- expectThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
- expectThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
- }
-
- @Test
- public void testMissingFields() {
- PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
- IllegalSchemaException e = expectThrows(IllegalSchemaException.class, builder::build);
- assertThat(e).hasMessageThat().contains("Missing field: dataType");
-
- builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
- e = expectThrows(IllegalSchemaException.class, builder::build);
- assertThat(e).hasMessageThat().contains("Missing field: schemaType");
-
- builder.setSchemaType("TestType");
- e = expectThrows(IllegalSchemaException.class, builder::build);
- assertThat(e).hasMessageThat().contains("Missing field: cardinality");
-
- builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
- builder.build();
- }
-
- @Test
- public void testDuplicateProperties() {
- AppSearchSchema.Builder builder = new AppSearchSchema.Builder("Email")
- .addProperty(new PropertyConfig.Builder("subject")
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()
- );
- IllegalSchemaException e = expectThrows(IllegalSchemaException.class,
- () -> builder.addProperty(new PropertyConfig.Builder("subject")
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()));
- assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
- }
-}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
deleted file mode 100644
index 9c29943..0000000
--- a/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.appsearch;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.expectThrows;
-
-import org.junit.Test;
-
-public class GenericDocumentTest {
- private static final byte[] sByteArray1 = new byte[]{(byte) 1, (byte) 2, (byte) 3};
- private static final byte[] sByteArray2 = new byte[]{(byte) 4, (byte) 5, (byte) 6, (byte) 7};
- private static final GenericDocument sDocumentProperties1 = new GenericDocument
- .Builder("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
- .setCreationTimestampMillis(12345L)
- .build();
- private static final GenericDocument sDocumentProperties2 = new GenericDocument
- .Builder("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
- .setCreationTimestampMillis(6789L)
- .build();
-
- @Test
- public void testDocumentEquals_Identical() {
- GenericDocument document1 = new GenericDocument.Builder("uri1", "schemaType1")
- .setCreationTimestampMillis(5L)
- .setTtlMillis(1L)
- .setPropertyLong("longKey1", 1L, 2L, 3L)
- .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
- .setPropertyBoolean("booleanKey1", true, false, true)
- .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
- .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
- .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
- .build();
- GenericDocument document2 = new GenericDocument.Builder("uri1", "schemaType1")
- .setCreationTimestampMillis(5L)
- .setTtlMillis(1L)
- .setPropertyLong("longKey1", 1L, 2L, 3L)
- .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
- .setPropertyBoolean("booleanKey1", true, false, true)
- .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
- .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
- .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
- .build();
- assertThat(document1).isEqualTo(document2);
- assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
- }
-
- @Test
- public void testDocumentEquals_DifferentOrder() {
- GenericDocument document1 = new GenericDocument.Builder("uri1", "schemaType1")
- .setCreationTimestampMillis(5L)
- .setPropertyLong("longKey1", 1L, 2L, 3L)
- .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
- .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
- .setPropertyBoolean("booleanKey1", true, false, true)
- .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
- .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
- .build();
-
- // Create second document with same parameter but different order.
- GenericDocument document2 = new GenericDocument.Builder("uri1", "schemaType1")
- .setCreationTimestampMillis(5L)
- .setPropertyBoolean("booleanKey1", true, false, true)
- .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
- .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
- .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
- .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
- .setPropertyLong("longKey1", 1L, 2L, 3L)
- .build();
- assertThat(document1).isEqualTo(document2);
- assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
- }
-
- @Test
- public void testDocumentEquals_Failure() {
- GenericDocument document1 = new GenericDocument.Builder("uri1", "schemaType1")
- .setCreationTimestampMillis(5L)
- .setPropertyLong("longKey1", 1L, 2L, 3L)
- .build();
-
- // Create second document with same order but different value.
- GenericDocument document2 = new GenericDocument.Builder("uri1", "schemaType1")
- .setCreationTimestampMillis(5L)
- .setPropertyLong("longKey1", 1L, 2L, 4L) // Different
- .build();
- assertThat(document1).isNotEqualTo(document2);
- assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
- }
-
- @Test
- public void testDocumentEquals_Failure_RepeatedFieldOrder() {
- GenericDocument document1 = new GenericDocument.Builder("uri1", "schemaType1")
- .setCreationTimestampMillis(5L)
- .setPropertyBoolean("booleanKey1", true, false, true)
- .build();
-
- // Create second document with same order but different value.
- GenericDocument document2 = new GenericDocument.Builder("uri1", "schemaType1")
- .setCreationTimestampMillis(5L)
- .setPropertyBoolean("booleanKey1", true, true, false) // Different
- .build();
- assertThat(document1).isNotEqualTo(document2);
- assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
- }
-
- @Test
- public void testDocumentGetSingleValue() {
- GenericDocument document = new GenericDocument.Builder("uri1", "schemaType1")
- .setCreationTimestampMillis(5L)
- .setScore(1)
- .setTtlMillis(1L)
- .setPropertyLong("longKey1", 1L)
- .setPropertyDouble("doubleKey1", 1.0)
- .setPropertyBoolean("booleanKey1", true)
- .setPropertyString("stringKey1", "test-value1")
- .setPropertyBytes("byteKey1", sByteArray1)
- .setPropertyDocument("documentKey1", sDocumentProperties1)
- .build();
- assertThat(document.getUri()).isEqualTo("uri1");
- assertThat(document.getTtlMillis()).isEqualTo(1L);
- assertThat(document.getSchemaType()).isEqualTo("schemaType1");
- assertThat(document.getCreationTimestampMillis()).isEqualTo(5);
- assertThat(document.getScore()).isEqualTo(1);
- 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");
- assertThat(document.getPropertyBytes("byteKey1"))
- .asList().containsExactly((byte) 1, (byte) 2, (byte) 3);
- assertThat(document.getPropertyDocument("documentKey1")).isEqualTo(sDocumentProperties1);
- }
-
- @Test
- public void testDocumentGetArrayValues() {
- GenericDocument document = new GenericDocument.Builder("uri1", "schemaType1")
- .setCreationTimestampMillis(5L)
- .setPropertyLong("longKey1", 1L, 2L, 3L)
- .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
- .setPropertyBoolean("booleanKey1", true, false, true)
- .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
- .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
- .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
- .build();
-
- assertThat(document.getUri()).isEqualTo("uri1");
- assertThat(document.getSchemaType()).isEqualTo("schemaType1");
- 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");
- assertThat(document.getPropertyBytesArray("byteKey1")).asList()
- .containsExactly(sByteArray1, sByteArray2);
- assertThat(document.getPropertyDocumentArray("documentKey1")).asList()
- .containsExactly(sDocumentProperties1, sDocumentProperties2);
- }
-
- @Test
- public void testDocument_ToString() throws Exception {
- GenericDocument document = new GenericDocument.Builder("uri1", "schemaType1")
- .setCreationTimestampMillis(5L)
- .setPropertyLong("longKey1", 1L, 2L, 3L)
- .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
- .setPropertyBoolean("booleanKey1", true, false, true)
- .setPropertyString("stringKey1", "String1", "String2", "String3")
- .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
- .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
- .build();
- String exceptedString = "{ key: 'creationTimestampMillis' value: 5 } "
- + "{ key: 'namespace' value: } "
- + "{ key: 'properties' value: "
- + "{ key: 'booleanKey1' value: [ 'true' 'false' 'true' ] } "
- + "{ key: 'byteKey1' value: "
- + "{ key: 'byteArray' value: [ '1' '2' '3' ] } "
- + "{ key: 'byteArray' value: [ '4' '5' '6' '7' ] } } "
- + "{ key: 'documentKey1' value: [ '"
- + "{ key: 'creationTimestampMillis' value: 12345 } "
- + "{ key: 'namespace' value: } "
- + "{ key: 'properties' value: } "
- + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType1 } "
- + "{ key: 'score' value: 0 } "
- + "{ key: 'ttlMillis' value: 0 } "
- + "{ key: 'uri' value: sDocumentProperties1 } ' '"
- + "{ key: 'creationTimestampMillis' value: 6789 } "
- + "{ key: 'namespace' value: } "
- + "{ key: 'properties' value: } "
- + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType2 } "
- + "{ key: 'score' value: 0 } "
- + "{ key: 'ttlMillis' value: 0 } "
- + "{ key: 'uri' value: sDocumentProperties2 } ' ] } "
- + "{ key: 'doubleKey1' value: [ '1.0' '2.0' '3.0' ] } "
- + "{ key: 'longKey1' value: [ '1' '2' '3' ] } "
- + "{ key: 'stringKey1' value: [ 'String1' 'String2' 'String3' ] } } "
- + "{ key: 'schemaType' value: schemaType1 } "
- + "{ key: 'score' value: 0 } "
- + "{ key: 'ttlMillis' value: 0 } "
- + "{ key: 'uri' value: uri1 } ";
- assertThat(document.toString()).isEqualTo(exceptedString);
- }
-
- @Test
- public void testDocumentGetValues_DifferentTypes() {
- GenericDocument document = new GenericDocument.Builder("uri1", "schemaType1")
- .setScore(1)
- .setPropertyLong("longKey1", 1L)
- .setPropertyBoolean("booleanKey1", true, false, true)
- .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
- .build();
-
- // Get a value for a key that doesn't exist
- assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(0.0);
- 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")).isEqualTo(0.0);
- assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
- }
-
- @Test
- public void testDocumentInvalid() {
- GenericDocument.Builder builder = new GenericDocument.Builder("uri1", "schemaType1");
- expectThrows(
- IllegalArgumentException.class,
- () -> builder.setPropertyBoolean("test", new boolean[]{}));
- }
-}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
index d4635fd..9fd480d 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
@@ -18,69 +18,33 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.testng.Assert.expectThrows;
-
import android.os.Bundle;
import org.junit.Test;
public class SearchSpecTest {
@Test
- public void buildSearchSpecWithoutTermMatchType() {
- expectThrows(RuntimeException.class, () -> new SearchSpec.Builder()
- .addSchema("testSchemaType")
- .build());
- }
-
- @Test
- public void testBuildSearchSpec() {
- SearchSpec searchSpec = new SearchSpec.Builder()
- .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
- .addNamespace("namespace1", "namespace2")
- .addSchema("schemaTypes1", "schemaTypes2")
- .setSnippetCount(5)
- .setSnippetCountPerProperty(10)
- .setMaxSnippetSize(15)
- .setNumPerPage(42)
- .setOrder(SearchSpec.ORDER_ASCENDING)
- .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
- .build();
-
- assertThat(searchSpec.getTermMatch()).isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
- assertThat(searchSpec.getNamespaces())
- .containsExactly("namespace1", "namespace2").inOrder();
- assertThat(searchSpec.getSchemas())
- .containsExactly("schemaTypes1", "schemaTypes2").inOrder();
- assertThat(searchSpec.getSnippetCount()).isEqualTo(5);
- assertThat(searchSpec.getSnippetCountPerProperty()).isEqualTo(10);
- assertThat(searchSpec.getMaxSnippetSize()).isEqualTo(15);
- assertThat(searchSpec.getNumPerPage()).isEqualTo(42);
- assertThat(searchSpec.getOrder()).isEqualTo(SearchSpec.ORDER_ASCENDING);
- assertThat(searchSpec.getRankingStrategy())
- .isEqualTo(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE);
- }
-
- @Test
public void testGetBundle() {
- SearchSpec searchSpec = new SearchSpec.Builder()
- .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
- .addNamespace("namespace1", "namespace2")
- .addSchema("schemaTypes1", "schemaTypes2")
- .setSnippetCount(5)
- .setSnippetCountPerProperty(10)
- .setMaxSnippetSize(15)
- .setNumPerPage(42)
- .setOrder(SearchSpec.ORDER_ASCENDING)
- .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
- .build();
+ SearchSpec searchSpec =
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .addNamespace("namespace1", "namespace2")
+ .addSchemaType("schemaTypes1", "schemaTypes2")
+ .setSnippetCount(5)
+ .setSnippetCountPerProperty(10)
+ .setMaxSnippetSize(15)
+ .setResultCountPerPage(42)
+ .setOrder(SearchSpec.ORDER_ASCENDING)
+ .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
+ .build();
Bundle bundle = searchSpec.getBundle();
assertThat(bundle.getInt(SearchSpec.TERM_MATCH_TYPE_FIELD))
.isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
- assertThat(bundle.getStringArrayList(SearchSpec.NAMESPACE_FIELD)).containsExactly(
- "namespace1", "namespace2");
- assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_TYPE_FIELD)).containsExactly(
- "schemaTypes1", "schemaTypes2");
+ assertThat(bundle.getStringArrayList(SearchSpec.NAMESPACE_FIELD))
+ .containsExactly("namespace1", "namespace2");
+ assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_TYPE_FIELD))
+ .containsExactly("schemaTypes1", "schemaTypes2");
assertThat(bundle.getInt(SearchSpec.SNIPPET_COUNT_FIELD)).isEqualTo(5);
assertThat(bundle.getInt(SearchSpec.SNIPPET_COUNT_PER_PROPERTY_FIELD)).isEqualTo(10);
assertThat(bundle.getInt(SearchSpec.MAX_SNIPPET_FIELD)).isEqualTo(15);
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchResultCtsTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchResultCtsTest.java
new file mode 100644
index 0000000..9c34b17
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchResultCtsTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearchResult;
+
+import org.junit.Test;
+
+public class AppSearchResultCtsTest {
+
+ @Test
+ public void testResultEquals_identical() {
+ AppSearchResult<String> result1 = AppSearchResult.newSuccessfulResult("String");
+ AppSearchResult<String> result2 = AppSearchResult.newSuccessfulResult("String");
+
+ assertThat(result1).isEqualTo(result2);
+ assertThat(result1.hashCode()).isEqualTo(result2.hashCode());
+
+ AppSearchResult<String> result3 =
+ AppSearchResult.newFailedResult(
+ AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+ AppSearchResult<String> result4 =
+ AppSearchResult.newFailedResult(
+ AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+
+ assertThat(result3).isEqualTo(result4);
+ assertThat(result3.hashCode()).isEqualTo(result4.hashCode());
+ }
+
+ @Test
+ public void testResultEquals_failure() {
+ AppSearchResult<String> result1 = AppSearchResult.newSuccessfulResult("String");
+ AppSearchResult<String> result2 = AppSearchResult.newSuccessfulResult("Wrong");
+ AppSearchResult<String> resultNull = AppSearchResult.newSuccessfulResult(/*value=*/ null);
+
+ assertThat(result1).isNotEqualTo(result2);
+ assertThat(result1.hashCode()).isNotEqualTo(result2.hashCode());
+ assertThat(result1).isNotEqualTo(resultNull);
+ assertThat(result1.hashCode()).isNotEqualTo(resultNull.hashCode());
+
+ AppSearchResult<String> result3 =
+ AppSearchResult.newFailedResult(
+ AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+ AppSearchResult<String> result4 =
+ AppSearchResult.newFailedResult(AppSearchResult.RESULT_IO_ERROR, "errorMessage");
+
+ assertThat(result3).isNotEqualTo(result4);
+ assertThat(result3.hashCode()).isNotEqualTo(result4.hashCode());
+
+ AppSearchResult<String> result5 =
+ AppSearchResult.newFailedResult(AppSearchResult.RESULT_INTERNAL_ERROR, "Wrong");
+
+ assertThat(result3).isNotEqualTo(result5);
+ assertThat(result3.hashCode()).isNotEqualTo(result5.hashCode());
+
+ AppSearchResult<String> result6 =
+ AppSearchResult.newFailedResult(
+ AppSearchResult.RESULT_INTERNAL_ERROR, /*errorMessage=*/ null);
+
+ assertThat(result3).isNotEqualTo(result6);
+ assertThat(result3.hashCode()).isNotEqualTo(result6.hashCode());
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchSchemaCtsTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchSchemaCtsTest.java
new file mode 100644
index 0000000..2eaebd6
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchSchemaCtsTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.exceptions.IllegalSchemaException;
+
+import org.junit.Test;
+
+public class AppSearchSchemaCtsTest {
+ @Test
+ public void testInvalidEnums() {
+ PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
+ expectThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
+ expectThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+ }
+
+ @Test
+ public void testMissingFields() {
+ PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
+ IllegalSchemaException e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: dataType");
+
+ builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: schemaType");
+
+ builder.setSchemaType("TestType");
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: cardinality");
+
+ builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
+ builder.build();
+ }
+
+ @Test
+ public void testDuplicateProperties() {
+ AppSearchSchema.Builder builder =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build());
+ IllegalSchemaException e =
+ expectThrows(
+ IllegalSchemaException.class,
+ () ->
+ builder.addProperty(
+ new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(
+ PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build()));
+ assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/cts/GenericDocumentCtsTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/cts/GenericDocumentCtsTest.java
new file mode 100644
index 0000000..657d556
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/cts/GenericDocumentCtsTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.GenericDocument;
+
+import org.junit.Test;
+
+public class GenericDocumentCtsTest {
+ private static final byte[] sByteArray1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+ private static final byte[] sByteArray2 = new byte[] {(byte) 4, (byte) 5, (byte) 6, (byte) 7};
+ private static final GenericDocument sDocumentProperties1 =
+ new GenericDocument.Builder<>("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+ .setCreationTimestampMillis(12345L)
+ .build();
+ private static final GenericDocument sDocumentProperties2 =
+ new GenericDocument.Builder<>("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+ .setCreationTimestampMillis(6789L)
+ .build();
+
+ @Test
+ public void testDocumentEquals_identical() {
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setTtlMillis(1L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .build();
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setTtlMillis(1L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .build();
+ assertThat(document1).isEqualTo(document2);
+ assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_differentOrder() {
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ // Create second document with same parameter but different order.
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .build();
+ assertThat(document1).isEqualTo(document2);
+ assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_failure() {
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .build();
+
+ // Create second document with same order but different value.
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyLong("longKey1", 1L, 2L, 4L) // Different
+ .build();
+ assertThat(document1).isNotEqualTo(document2);
+ assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_repeatedFieldOrder_failure() {
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .build();
+
+ // Create second document with same order but different value.
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyBoolean("booleanKey1", true, true, false) // Different
+ .build();
+ assertThat(document1).isNotEqualTo(document2);
+ assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentGetSingleValue() {
+ GenericDocument document =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setScore(1)
+ .setTtlMillis(1L)
+ .setPropertyLong("longKey1", 1L)
+ .setPropertyDouble("doubleKey1", 1.0)
+ .setPropertyBoolean("booleanKey1", true)
+ .setPropertyString("stringKey1", "test-value1")
+ .setPropertyBytes("byteKey1", sByteArray1)
+ .setPropertyDocument("documentKey1", sDocumentProperties1)
+ .build();
+ assertThat(document.getUri()).isEqualTo("uri1");
+ assertThat(document.getTtlMillis()).isEqualTo(1L);
+ assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+ assertThat(document.getCreationTimestampMillis()).isEqualTo(5);
+ assertThat(document.getScore()).isEqualTo(1);
+ 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");
+ assertThat(document.getPropertyBytes("byteKey1"))
+ .asList()
+ .containsExactly((byte) 1, (byte) 2, (byte) 3);
+ assertThat(document.getPropertyDocument("documentKey1")).isEqualTo(sDocumentProperties1);
+ }
+
+ @Test
+ public void testDocumentGetArrayValues() {
+ GenericDocument document =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .build();
+
+ assertThat(document.getUri()).isEqualTo("uri1");
+ assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+ 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");
+ assertThat(document.getPropertyBytesArray("byteKey1"))
+ .asList()
+ .containsExactly(sByteArray1, sByteArray2);
+ assertThat(document.getPropertyDocumentArray("documentKey1"))
+ .asList()
+ .containsExactly(sDocumentProperties1, sDocumentProperties2);
+ }
+
+ @Test
+ public void testDocument_toString() {
+ GenericDocument document =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setCreationTimestampMillis(5L)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString("stringKey1", "String1", "String2", "String3")
+ .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+ .setPropertyDocument(
+ "documentKey1", sDocumentProperties1, sDocumentProperties2)
+ .build();
+ String exceptedString =
+ "{ key: 'creationTimestampMillis' value: 5 } "
+ + "{ key: 'namespace' value: } "
+ + "{ key: 'properties' value: "
+ + "{ key: 'booleanKey1' value: [ 'true' 'false' 'true' ] } "
+ + "{ key: 'byteKey1' value: "
+ + "{ key: 'byteArray' value: [ '1' '2' '3' ] } "
+ + "{ key: 'byteArray' value: [ '4' '5' '6' '7' ] } } "
+ + "{ key: 'documentKey1' value: [ '"
+ + "{ key: 'creationTimestampMillis' value: 12345 } "
+ + "{ key: 'namespace' value: } "
+ + "{ key: 'properties' value: } "
+ + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType1 } "
+ + "{ key: 'score' value: 0 } "
+ + "{ key: 'ttlMillis' value: 0 } "
+ + "{ key: 'uri' value: sDocumentProperties1 } ' '"
+ + "{ key: 'creationTimestampMillis' value: 6789 } "
+ + "{ key: 'namespace' value: } "
+ + "{ key: 'properties' value: } "
+ + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType2 } "
+ + "{ key: 'score' value: 0 } "
+ + "{ key: 'ttlMillis' value: 0 } "
+ + "{ key: 'uri' value: sDocumentProperties2 } ' ] } "
+ + "{ key: 'doubleKey1' value: [ '1.0' '2.0' '3.0' ] } "
+ + "{ key: 'longKey1' value: [ '1' '2' '3' ] } "
+ + "{ key: 'stringKey1' value: [ 'String1' 'String2' 'String3' ] } } "
+ + "{ key: 'schemaType' value: schemaType1 } "
+ + "{ key: 'score' value: 0 } "
+ + "{ key: 'ttlMillis' value: 0 } "
+ + "{ key: 'uri' value: uri1 } ";
+ assertThat(document.toString()).isEqualTo(exceptedString);
+ }
+
+ @Test
+ public void testDocumentGetValues_differentTypes() {
+ GenericDocument document =
+ new GenericDocument.Builder<>("uri1", "schemaType1")
+ .setScore(1)
+ .setPropertyLong("longKey1", 1L)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ // Get a value for a key that doesn't exist
+ assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(0.0);
+ 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")).isEqualTo(0.0);
+ assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
+ }
+
+ @Test
+ public void testDocumentInvalid() {
+ GenericDocument.Builder<?> builder = new GenericDocument.Builder<>("uri1", "schemaType1");
+ expectThrows(
+ IllegalArgumentException.class,
+ () -> builder.setPropertyBoolean("test", new boolean[] {}));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/cts/SearchSpecCtsTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/cts/SearchSpecCtsTest.java
new file mode 100644
index 0000000..50bca27
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/cts/SearchSpecCtsTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.SearchSpec;
+
+import org.junit.Test;
+
+public class SearchSpecCtsTest {
+ @Test
+ public void buildSearchSpecWithoutTermMatchType() {
+ RuntimeException e =
+ expectThrows(
+ RuntimeException.class,
+ () -> new SearchSpec.Builder().addSchemaType("testSchemaType").build());
+ assertThat(e).hasMessageThat().contains("Missing termMatchType field");
+ }
+
+ @Test
+ public void testBuildSearchSpec() {
+ SearchSpec searchSpec =
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .addNamespace("namespace1", "namespace2")
+ .addSchemaType("schemaTypes1", "schemaTypes2")
+ .setSnippetCount(5)
+ .setSnippetCountPerProperty(10)
+ .setMaxSnippetSize(15)
+ .setResultCountPerPage(42)
+ .setOrder(SearchSpec.ORDER_ASCENDING)
+ .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
+ .build();
+
+ assertThat(searchSpec.getTermMatch()).isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
+ assertThat(searchSpec.getNamespaces())
+ .containsExactly("namespace1", "namespace2")
+ .inOrder();
+ assertThat(searchSpec.getSchemaTypes())
+ .containsExactly("schemaTypes1", "schemaTypes2")
+ .inOrder();
+ assertThat(searchSpec.getSnippetCount()).isEqualTo(5);
+ assertThat(searchSpec.getSnippetCountPerProperty()).isEqualTo(10);
+ assertThat(searchSpec.getMaxSnippetSize()).isEqualTo(15);
+ assertThat(searchSpec.getResultCountPerPage()).isEqualTo(42);
+ assertThat(searchSpec.getOrder()).isEqualTo(SearchSpec.ORDER_ASCENDING);
+ assertThat(searchSpec.getRankingStrategy())
+ .isEqualTo(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE);
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/cts/customer/CustomerDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/cts/customer/CustomerDocumentTest.java
new file mode 100644
index 0000000..29b5754
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/cts/customer/CustomerDocumentTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts.customer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.appsearch.GenericDocument;
+
+import org.junit.Test;
+
+/**
+ * Tests that {@link GenericDocument} and {@link GenericDocument.Builder} are extendable by
+ * developers.
+ *
+ * <p>This class is intentionally in a different package than {@link GenericDocument} to make sure
+ * there are no package-private methods required for external developers to add custom types.
+ */
+public class CustomerDocumentTest {
+
+ private static final byte[] BYTE_ARRAY1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+ private static final byte[] BYTE_ARRAY2 = new byte[] {(byte) 4, (byte) 5, (byte) 6};
+ private static final GenericDocument DOCUMENT_PROPERTIES1 =
+ new GenericDocument.Builder<>("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+ .build();
+ private static final GenericDocument DOCUMENT_PROPERTIES2 =
+ new GenericDocument.Builder<>("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+ .build();
+
+ @Test
+ public void testBuildCustomerDocument() {
+ CustomerDocument customerDocument =
+ new CustomerDocument.Builder("uri1")
+ .setScore(1)
+ .setCreationTimestampMillis(0)
+ .setPropertyLong("longKey1", 1L, 2L, 3L)
+ .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+ .setPropertyBoolean("booleanKey1", true, false, true)
+ .setPropertyString(
+ "stringKey1", "test-value1", "test-value2", "test-value3")
+ .setPropertyBytes("byteKey1", BYTE_ARRAY1, BYTE_ARRAY2)
+ .setPropertyDocument(
+ "documentKey1", DOCUMENT_PROPERTIES1, DOCUMENT_PROPERTIES2)
+ .build();
+
+ assertThat(customerDocument.getUri()).isEqualTo("uri1");
+ assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument");
+ assertThat(customerDocument.getScore()).isEqualTo(1);
+ assertThat(customerDocument.getCreationTimestampMillis()).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");
+ assertThat(customerDocument.getPropertyBytesArray("byteKey1"))
+ .asList()
+ .containsExactly(BYTE_ARRAY1, BYTE_ARRAY2);
+ assertThat(customerDocument.getPropertyDocumentArray("documentKey1"))
+ .asList()
+ .containsExactly(DOCUMENT_PROPERTIES1, DOCUMENT_PROPERTIES2);
+ }
+
+ /**
+ * An example document type for test purposes, defined outside of {@link GenericDocument} (the
+ * way an external developer would define it).
+ */
+ private static class CustomerDocument extends GenericDocument {
+ private CustomerDocument(GenericDocument document) {
+ super(document);
+ }
+
+ public static class Builder extends GenericDocument.Builder<CustomerDocument.Builder> {
+ private Builder(@NonNull String uri) {
+ super(uri, "customerDocument");
+ }
+
+ @Override
+ @NonNull
+ public CustomerDocument build() {
+ return new CustomerDocument(super.build());
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/customer/CustomerDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/customer/CustomerDocumentTest.java
deleted file mode 100644
index d56d0c3..0000000
--- a/core/tests/coretests/src/android/app/appsearch/external/app/customer/CustomerDocumentTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.appsearch.customer;
-
-import android.annotation.NonNull;
-import android.app.appsearch.GenericDocument;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.Test;
-
-/**
- * Tests that {@link GenericDocument} and {@link GenericDocument.Builder} are extendable by
- * developers.
- *
- * <p>This class is intentionally in a different package than {@link GenericDocument} to make sure
- * there are no package-private methods required for external developers to add custom types.
- */
-public class CustomerDocumentTest {
-
- private static byte[] sByteArray1 = new byte[]{(byte) 1, (byte) 2, (byte) 3};
- private static byte[] sByteArray2 = new byte[]{(byte) 4, (byte) 5, (byte) 6};
- private static GenericDocument sDocumentProperties1 = new GenericDocument
- .Builder("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
- .build();
- private static GenericDocument sDocumentProperties2 = new GenericDocument
- .Builder("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
- .build();
-
- @Test
- public void testBuildCustomerDocument() {
- CustomerDocument customerDocument = new CustomerDocument.Builder("uri1")
- .setScore(1)
- .setCreationTimestampMillis(0)
- .setPropertyLong("longKey1", 1L, 2L, 3L)
- .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
- .setPropertyBoolean("booleanKey1", true, false, true)
- .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
- .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
- .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
- .build();
-
- assertThat(customerDocument.getUri()).isEqualTo("uri1");
- assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument");
- assertThat(customerDocument.getScore()).isEqualTo(1);
- assertThat(customerDocument.getCreationTimestampMillis()).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");
- assertThat(customerDocument.getPropertyBytesArray("byteKey1")).asList()
- .containsExactly(sByteArray1, sByteArray2);
- assertThat(customerDocument.getPropertyDocumentArray("documentKey1")).asList()
- .containsExactly(sDocumentProperties1, sDocumentProperties2);
- }
-
- /**
- * An example document type for test purposes, defined outside of
- * {@link GenericDocument} (the way an external developer would define
- * it).
- */
- private static class CustomerDocument extends GenericDocument {
- private CustomerDocument(GenericDocument document) {
- super(document);
- }
-
- public static class Builder extends GenericDocument.Builder<CustomerDocument.Builder> {
- private Builder(@NonNull String uri) {
- super(uri, "customerDocument");
- }
-
- @Override
- @NonNull
- public CustomerDocument build() {
- return new CustomerDocument(super.build());
- }
- }
- }
-}
diff --git a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
new file mode 100644
index 0000000..27584a5
--- /dev/null
+++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.people;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Notification;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PeopleSpaceTileTest {
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getContext();
+ }
+
+ @Test
+ public void testId() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).build();
+ assertThat(tile.getId()).isEqualTo("123");
+
+ tile = new PeopleSpaceTile.Builder(new ShortcutInfo.Builder(mContext, "123").build()).setId(
+ "5").build();
+ assertThat(tile.getId()).isEqualTo("5");
+
+ tile = new PeopleSpaceTile.Builder("12", null, null, null).build();
+ assertThat(tile.getId()).isEqualTo("12");
+ }
+
+ @Test
+ public void testUserName() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).build();
+ assertThat(tile.getUserName()).isNull();
+
+ tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).setUserName("Name 1").build();
+ assertThat(tile.getUserName()).isEqualTo("Name 1");
+
+ tile = new PeopleSpaceTile.Builder(null, "Name 2", null, null).build();
+ assertThat(tile.getUserName()).isEqualTo("Name 2");
+ }
+
+ @Test
+ public void testUserIcon() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).setUserIcon(
+ Icon.createWithResource(mContext, 1)).build();
+ assertThat(tile.getUserIcon().toString()).isEqualTo(
+ Icon.createWithResource(mContext, 1).toString());
+
+ tile = new PeopleSpaceTile.Builder("12", null, Icon.createWithResource(mContext, 2),
+ null).build();
+ assertThat(tile.getUserIcon().toString()).isEqualTo(
+ Icon.createWithResource(mContext, 2).toString());
+ }
+
+ @Test
+ public void testContactUri() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).setContactUri(
+ Uri.parse("test")).build();
+
+ assertThat(tile.getContactUri()).isEqualTo(Uri.parse("test"));
+ }
+
+ @Test
+ public void testUid() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).setUid(42).build();
+
+ assertThat(tile.getUid()).isEqualTo(42);
+ }
+
+ @Test
+ public void testPackageName() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).build();
+ // Automatically added by creating a ShortcutInfo.
+ assertThat(tile.getPackageName()).isEqualTo("com.android.frameworks.coretests");
+
+ tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).setPackageName(
+ "package.name").build();
+ assertThat(tile.getPackageName()).isEqualTo("package.name");
+
+ tile = new PeopleSpaceTile.Builder("12", null, null,
+ new Intent().setPackage("intent.package")).build();
+ assertThat(tile.getPackageName()).isEqualTo("intent.package");
+ }
+
+ @Test
+ public void testLastInteractionTimestamp() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).build();
+ assertThat(tile.getLastInteractionTimestamp()).isEqualTo(0L);
+
+ tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).setLastInteractionTimestamp(
+ 7L).build();
+ assertThat(tile.getLastInteractionTimestamp()).isEqualTo(7L);
+ }
+
+ @Test
+ public void testImportantConversation() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).build();
+ assertFalse(tile.isImportantConversation());
+
+ tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).setIsImportantConversation(
+ true).build();
+ assertTrue(tile.isImportantConversation());
+ }
+
+ @Test
+ public void testHiddenConversation() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).build();
+ assertFalse(tile.isHiddenConversation());
+
+ tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).setIsHiddenConversation(
+ true).build();
+ assertTrue(tile.isHiddenConversation());
+ }
+
+ @Test
+ public void testNotification() {
+ Notification notification = new Notification.Builder(mContext, "test").build();
+ StatusBarNotification sbn = new StatusBarNotification("pkg" /* pkg */, "pkg" /* opPkg */,
+ 1 /* id */, "" /* tag */, 0 /* uid */, 0 /* initialPid */, 0 /* score */,
+ notification, UserHandle.CURRENT, 0 /* postTime */);
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).setNotification(sbn).build();
+
+ assertThat(tile.getNotification()).isEqualTo(sbn);
+ }
+
+ @Test
+ public void testIntent() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).build();
+ assertThat(tile.getIntent()).isNull();
+
+ tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build()).setIntent(new Intent()).build();
+ assertThat(tile.getIntent().toString()).isEqualTo(new Intent().toString());
+
+ tile = new PeopleSpaceTile.Builder("12", null, null, new Intent()).build();
+ assertThat(tile.getIntent().toString()).isEqualTo(new Intent().toString());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
new file mode 100644
index 0000000..7effa56
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
@@ -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 android.content.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.pm.PackageManager.Property;
+import android.os.Bundle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerPropertyTests {
+
+ @Test
+ public void testBooleanProperty() throws Exception {
+ final Property p = new Property("booleanProperty", true);
+ assertTrue(p.isBoolean());
+ assertFalse(p.isFloat());
+ assertFalse(p.isInteger());
+ assertFalse(p.isResourceId());
+ assertFalse(p.isString());
+ assertTrue(p.getBoolean());
+ assertEquals(0.0f, p.getFloat(), 0.0f);
+ assertEquals(0, p.getInteger());
+ assertEquals(0, p.getResourceId());
+ assertEquals(null, p.getString());
+ }
+
+ @Test
+ public void testBooleanPropertyToBundle() throws Exception {
+ final Bundle b = new Property("booleanProperty", true).toBundle(null);
+ assertTrue(b.getBoolean("booleanProperty"));
+ }
+
+ @Test
+ public void testFloatProperty() throws Exception {
+ final Property p = new Property("floatProperty", 3.14f);
+ assertFalse(p.isBoolean());
+ assertTrue(p.isFloat());
+ assertFalse(p.isInteger());
+ assertFalse(p.isResourceId());
+ assertFalse(p.isString());
+ assertFalse(p.getBoolean());
+ assertEquals(3.14f, p.getFloat(), 0.0f);
+ assertEquals(0, p.getInteger());
+ assertEquals(0, p.getResourceId());
+ assertEquals(null, p.getString());
+ }
+
+ @Test
+ public void testFloatPropertyToBundle() throws Exception {
+ final Bundle b = new Property("floatProperty", 3.14f).toBundle(null);
+ assertEquals(3.14f, b.getFloat("floatProperty"), 0.0f);
+ }
+
+ @Test
+ public void testIntegerProperty() throws Exception {
+ final Property p = new Property("integerProperty", 42, false);
+ assertFalse(p.isBoolean());
+ assertFalse(p.isFloat());
+ assertTrue(p.isInteger());
+ assertFalse(p.isResourceId());
+ assertFalse(p.isString());
+ assertFalse(p.getBoolean());
+ assertEquals(0.0f, p.getFloat(), 0.0f);
+ assertEquals(42, p.getInteger());
+ assertEquals(0, p.getResourceId());
+ assertEquals(null, p.getString());
+ }
+
+ @Test
+ public void testIntegerPropertyToBundle() throws Exception {
+ final Bundle b = new Property("integerProperty", 42, false).toBundle(null);
+ assertEquals(42, b.getInt("integerProperty"));
+ }
+
+ @Test
+ public void testResourceProperty() throws Exception {
+ final Property p = new Property("resourceProperty", 0x7f010001, true);
+ assertFalse(p.isBoolean());
+ assertFalse(p.isFloat());
+ assertFalse(p.isInteger());
+ assertTrue(p.isResourceId());
+ assertFalse(p.isString());
+ assertFalse(p.getBoolean());
+ assertEquals(0.0f, p.getFloat(), 0.0f);
+ assertEquals(0, p.getInteger());
+ assertEquals(0x7f010001, p.getResourceId());
+ assertEquals(null, p.getString());
+ }
+
+ @Test
+ public void testResourcePropertyToBundle() throws Exception {
+ final Bundle b = new Property("resourceProperty", 0x7f010001, true).toBundle(null);
+ assertEquals(0x7f010001, b.getInt("resourceProperty"));
+ }
+
+ @Test
+ public void testStringProperty() throws Exception {
+ final Property p = new Property("stringProperty", "koala");
+ assertFalse(p.isBoolean());
+ assertFalse(p.isFloat());
+ assertFalse(p.isInteger());
+ assertFalse(p.isResourceId());
+ assertTrue(p.isString());
+ assertFalse(p.getBoolean());
+ assertEquals(0.0f, p.getFloat(), 0.0f);
+ assertEquals(0, p.getInteger());
+ assertEquals(0, p.getResourceId());
+ assertEquals("koala", p.getString());
+ }
+
+ @Test
+ public void testStringPropertyToBundle() throws Exception {
+ final Bundle b = new Property("stringProperty", "koala").toBundle(null);
+ assertEquals("koala", b.getString("stringProperty"));
+ }
+
+ @Test
+ public void testProperty_invalidName() throws Exception {
+ try {
+ final Property p = new Property(null, 1);
+ fail("expected assertion error");
+ } catch (AssertionError expected) {
+ }
+ }
+
+ @Test
+ public void testProperty_invalidType() throws Exception {
+ try {
+ final Property p = new Property(null, 0);
+ fail("expected assertion error");
+ } catch (AssertionError expected) {
+ }
+
+ try {
+ final Property p = new Property(null, 6);
+ fail("expected assertion error");
+ } catch (AssertionError expected) {
+ }
+
+ try {
+ final Property p = new Property(null, -1);
+ fail("expected assertion error");
+ } catch (AssertionError expected) {
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index e17800f..2402420 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -77,7 +77,7 @@
Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
mTracker = Mockito.spy(
- new FrameTracker(session, handler, mRenderer, mWrapper));
+ new FrameTracker(session, handler, mRenderer, mWrapper, 1, -1));
doNothing().when(mTracker).triggerPerfetto();
}
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index a9cfd28..474cb1d 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -24,6 +24,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -32,6 +33,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
+import android.provider.DeviceConfig;
import android.view.View;
import android.view.ViewAttachTestActivity;
@@ -50,6 +52,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
@@ -71,8 +74,6 @@
mView = mActivity.getWindow().getDecorView();
assertThat(mView.isAttachedToWindow()).isTrue();
- InteractionJankMonitor.abandon();
-
Handler handler = spy(new Handler(mActivity.getMainLooper()));
doReturn(true).when(handler).sendMessageAtTime(any(), anyLong());
mWorker = spy(new HandlerThread("Interaction-jank-monitor-test"));
@@ -93,7 +94,7 @@
Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(),
new ThreadedRendererWrapper(mView.getThreadedRenderer()),
- new FrameMetricsWrapper()));
+ new FrameMetricsWrapper(), 1, -1));
doReturn(tracker).when(monitor).createFrameTracker(any());
// Simulate a trace session and see if begin / end are invoked.
@@ -104,6 +105,21 @@
}
@Test
+ public void testDisabledThroughDeviceConfig() {
+ InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker);
+ monitor.init(mView);
+
+ HashMap<String, String> propertiesValues = new HashMap<>();
+ propertiesValues.put("enabled", "false");
+ DeviceConfig.Properties properties = new DeviceConfig.Properties(
+ DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR, propertiesValues);
+ monitor.getPropertiesChangedListener().onPropertiesChanged(properties);
+
+ assertThat(monitor.begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+ assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+ }
+
+ @Test
public void testCheckInitState() {
InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker);
@@ -134,12 +150,13 @@
Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(),
new ThreadedRendererWrapper(mView.getThreadedRenderer()),
- new FrameMetricsWrapper()));
+ new FrameMetricsWrapper(), 1, -1));
doReturn(tracker).when(monitor).createFrameTracker(any());
assertThat(monitor.begin(session.getCuj())).isTrue();
verify(tracker).begin();
- verify(mWorker.getThreadHandler()).sendMessageAtTime(captor.capture(), anyLong());
+ verify(mWorker.getThreadHandler(), atLeastOnce()).sendMessageAtTime(captor.capture(),
+ anyLong());
Runnable runnable = captor.getValue().getCallback();
assertThat(runnable).isNotNull();
mWorker.getThreadHandler().removeCallbacks(runnable);
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 4094f83..306388f 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -191,6 +191,10 @@
}
@Override
+ public void toggleAndFollowTvPower() {
+ }
+
+ @Override
public void queryDisplayStatus(final IHdmiControlCallback callback) {
}
diff --git a/core/tests/uwbtests/src/android/uwb/SessionHandleTest.java b/core/tests/uwbtests/src/android/uwb/SessionHandleTest.java
new file mode 100644
index 0000000..8b42ff7
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/SessionHandleTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link SessionHandle}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SessionHandleTest {
+
+ @Test
+ public void testBasic() {
+ int handleId = 12;
+ SessionHandle handle = new SessionHandle(handleId);
+ assertEquals(handle.getId(), handleId);
+ }
+
+ @Test
+ public void testParcel() {
+ Parcel parcel = Parcel.obtain();
+ SessionHandle handle = new SessionHandle(10);
+ handle.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ SessionHandle fromParcel = SessionHandle.CREATOR.createFromParcel(parcel);
+ assertEquals(handle, fromParcel);
+ }
+}
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 745de84..3e3aefc 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -156,4 +156,11 @@
sub_dir: "permissions",
src: "com.android.car.provision.xml",
filename_from_src: true,
-}
\ No newline at end of file
+}
+
+prebuilt_etc {
+ name: "allowed_privapp_com.android.carshell",
+ sub_dir: "permissions",
+ src: "com.android.car.shell.xml",
+ filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.shell.xml b/data/etc/car/com.android.car.shell.xml
new file mode 100644
index 0000000..32666c8
--- /dev/null
+++ b/data/etc/car/com.android.car.shell.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<permissions>
+ <privapp-permissions package="com.android.car.shell">
+ <permission name="android.permission.INSTALL_PACKAGES" />
+ <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index fe1182e..e473c55 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -56,5 +56,7 @@
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
<permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
+ <permission name="android.permission.READ_DREAM_STATE"/>
+ <permission name="android.permission.READ_DREAM_SUPPRESSION"/>
</privapp-permissions>
</permissions>
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index eba7306..712349a 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -810,18 +810,16 @@
*/
public @NonNull Typeface build() {
final int userFallbackSize = mFamilies.size();
- final FontFamily[] fallback = SystemFonts.getSystemFallback(mFallbackName);
- final long[] ptrArray = new long[fallback.length + userFallbackSize];
+ final Typeface fallbackTypeface = getSystemDefaultTypeface(mFallbackName);
+ final long[] ptrArray = new long[userFallbackSize];
for (int i = 0; i < userFallbackSize; ++i) {
ptrArray[i] = mFamilies.get(i).getNativePtr();
}
- for (int i = 0; i < fallback.length; ++i) {
- ptrArray[i + userFallbackSize] = fallback[i].getNativePtr();
- }
final int weight = mStyle == null ? 400 : mStyle.getWeight();
final int italic =
(mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ? 0 : 1;
- return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
+ return new Typeface(nativeCreateFromArray(
+ ptrArray, fallbackTypeface.native_instance, weight, italic));
}
}
@@ -1058,7 +1056,8 @@
ptrArray[i] = families[i].mNativePtr;
}
return new Typeface(nativeCreateFromArray(
- ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ ptrArray, 0, RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE));
}
/**
@@ -1071,7 +1070,7 @@
for (int i = 0; i < families.length; ++i) {
ptrArray[i] = families[i].getNativePtr();
}
- return new Typeface(nativeCreateFromArray(ptrArray,
+ return new Typeface(nativeCreateFromArray(ptrArray, 0,
RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
}
@@ -1106,15 +1105,13 @@
@Deprecated
private static Typeface createFromFamiliesWithDefault(android.graphics.FontFamily[] families,
String fallbackName, int weight, int italic) {
- android.graphics.fonts.FontFamily[] fallback = SystemFonts.getSystemFallback(fallbackName);
- long[] ptrArray = new long[families.length + fallback.length];
+ final Typeface fallbackTypeface = getSystemDefaultTypeface(fallbackName);
+ long[] ptrArray = new long[families.length];
for (int i = 0; i < families.length; i++) {
ptrArray[i] = families[i].mNativePtr;
}
- for (int i = 0; i < fallback.length; i++) {
- ptrArray[i + families.length] = fallback[i].getNativePtr();
- }
- return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
+ return new Typeface(nativeCreateFromArray(
+ ptrArray, fallbackTypeface.native_instance, weight, italic));
}
// don't allow clients to call this directly
@@ -1379,7 +1376,8 @@
@UnsupportedAppUsage
private static native long nativeCreateWeightAlias(long native_instance, int weight);
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
+ private static native long nativeCreateFromArray(
+ long[] familyArray, long fallbackTypeface, int weight, int italic);
private static native int[] nativeGetSupportedAxes(long native_instance);
@CriticalNative
diff --git a/graphics/java/android/graphics/drawable/TEST_MAPPING b/graphics/java/android/graphics/drawable/TEST_MAPPING
new file mode 100644
index 0000000..1018702
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/TEST_MAPPING
@@ -0,0 +1,24 @@
+{
+ "presubmit": [
+ {
+
+ "name": "CtsGraphicsTestCases",
+ "file_patterns": ["(/|^)Icon\\.java"],
+ "options" : [
+ {
+ "include-filter": "android.graphics.drawable.cts.IconTest"
+ }
+ ]
+ },
+ {
+
+ "name": "FrameworksCoreTests",
+ "file_patterns": ["(/|^)Icon\\.java"],
+ "options" : [
+ {
+ "include-filter": "android.graphics.drawable.IconTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 95a8417..95c4706 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -67,19 +67,6 @@
}
/**
- * Returns fallback list for the given family name.
- *
- * If no fallback found for the given family name, returns fallback for the default family.
- *
- * @param familyName family name, e.g. "serif"
- * @hide
- */
- public static @NonNull FontFamily[] getSystemFallback(@Nullable String familyName) {
- final FontFamily[] families = sSystemFallbackMap.get(familyName);
- return families == null ? sSystemFallbackMap.get(DEFAULT_FAMILY) : families;
- }
-
- /**
* Returns raw system fallback map.
*
* This method is intended to be used only by Typeface static initializer.
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 3ac9d68..5867ef6 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -84,7 +84,7 @@
// java.security.KeyStore
put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi");
- put("alg.alias.KeyStore.AndroidKeyStoreLegacy", "AndroidKeyStore");
+ put("Alg.Alias.KeyStore.AndroidKeyStoreLegacy", "AndroidKeyStore");
// java.security.KeyPairGenerator
put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
diff --git a/libs/WindowManager/Shell/res/values-be/strings_tv.xml b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
index 973ae8e..d33bf99 100644
--- a/libs/WindowManager/Shell/res/values-be/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
@@ -20,5 +20,5 @@
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Відарыс у відарысе"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Праграма без назвы)"</string>
<string name="pip_close" msgid="9135220303720555525">"Закрыць PIP"</string>
- <string name="pip_fullscreen" msgid="7278047353591302554">"Ва ўвесь экран"</string>
+ <string name="pip_fullscreen" msgid="7278047353591302554">"Поўнаэкранны рэжым"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings_tv.xml b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
index 0432f1c..02cce9d 100644
--- a/libs/WindowManager/Shell/res/values-de/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
@@ -17,8 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for notification_channel_tv_pip (2576686079160402435) -->
- <skip />
+ <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Bild im Bild"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Kein Sendungsname gefunden)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP schließen"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Vollbild"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
index 7aaf79f..c74e0bb 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
@@ -17,8 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for notification_channel_tv_pip (2576686079160402435) -->
- <skip />
+ <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ചിത്രത്തിനുള്ളിൽ ചിത്രം"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(പേരില്ലാത്ത പ്രോഗ്രാം)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP അടയ്ക്കുക"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"പൂര്ണ്ണ സ്ക്രീന്"</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
index 64e5db5..3179533 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
@@ -17,8 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for notification_channel_tv_pip (2576686079160402435) -->
- <skip />
+ <string name="notification_channel_tv_pip" msgid="2576686079160402435">"تصویر میں تصویر"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(بلا عنوان پروگرام)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP بند کریں"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"فُل اسکرین"</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index c913e0c..f0eae97 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -30,6 +30,12 @@
<!-- Animation duration when using long press on recents to dock -->
<integer name="long_press_dock_anim_duration">250</integer>
+ <!-- Animation duration for translating of one handed when trigger / dismiss. -->
+ <integer name="config_one_handed_translate_animation_duration">300</integer>
+
+ <!-- One handed mode default offset % of display size -->
+ <fraction name="config_one_handed_offset">40%</fraction>
+
<!-- Allow one handed to enable round corner -->
<bool name="config_one_handed_enable_round_corner">true</bool>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index beac59b..aa7355b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -89,6 +89,7 @@
// TODO(b/173386799) keep in sync with Launcher3 and also don't do a broadcast
public static final String TASKBAR_CHANGED_BROADCAST = "taskbarChanged";
+ public static final String EXTRA_TASKBAR_CREATED = "taskbarCreated";
public static final String EXTRA_BUBBLE_OVERFLOW_OPENED = "bubbleOverflowOpened";
public static final String EXTRA_TASKBAR_VISIBLE = "taskbarVisible";
public static final String EXTRA_TASKBAR_POSITION = "taskbarPosition";
@@ -350,12 +351,15 @@
+ " itemPosition: " + itemPosition[0] + "," + itemPosition[1]
+ " iconSize: " + iconSize);
PointF point = new PointF(itemPosition[0], itemPosition[1]);
- mBubblePositioner.setPinnedLocation(point);
+ mBubblePositioner.setPinnedLocation(isVisible ? point : null);
mBubblePositioner.updateForTaskbar(iconSize, taskbarPosition, isVisible, taskbarSize);
if (mStackView != null) {
- if (isVisible) {
- mStackView.updateStackPosition();
+ if (isVisible && b.getBoolean(EXTRA_TASKBAR_CREATED, false /* default */)) {
+ // If taskbar was created, add and remove the window so that bubbles display on top
+ removeFromWindowManagerMaybe();
+ addToWindowManagerMaybe();
}
+ mStackView.updateStackPosition();
mBubbleIconFactory = new BubbleIconFactory(mContext);
mStackView.onDisplaySizeChanged();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index f84936e..e8c6cb7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -37,6 +37,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.TaskStackListenerCallback;
@@ -205,8 +206,11 @@
mGestureHandler = gestureHandler;
mOverlayManager = overlayManager;
- mOffSetFraction = SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50)
- / 100.0f;
+ final float offsetPercentageConfig = context.getResources().getFraction(
+ R.fraction.config_one_handed_offset, 1, 1);
+ final int sysPropPercentageConfig = SystemProperties.getInt(
+ ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f));
+ mOffSetFraction = sysPropPercentageConfig / 100.0f;
mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
context.getContentResolver());
mIsSwipeToNotificationEnabled =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index bd6c1e0..0311030 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -40,6 +40,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.os.SomeArgs;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
import java.io.PrintWriter;
@@ -156,8 +157,11 @@
mDisplayController = displayController;
mDefaultDisplayBounds.set(getDisplayBounds());
mLastVisualDisplayBounds.set(getDisplayBounds());
+ final int animationDurationConfig = context.getResources().getInteger(
+ R.integer.config_one_handed_translate_animation_duration);
mEnterExitAnimationDurationMs =
- SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION, 300);
+ SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION,
+ animationDurationConfig);
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
mTutorialHandler = tutorialHandler;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index b6b518d..d65ad62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -86,8 +86,11 @@
context.getSystemService(Context.ACCESSIBILITY_SERVICE);
mTargetViewContainer = new FrameLayout(context);
mTargetViewContainer.setClipChildren(false);
- mTutorialAreaHeight = Math.round(mDisplaySize.y
- * (SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50) / 100.0f));
+ final float offsetPercentageConfig = context.getResources().getFraction(
+ R.fraction.config_one_handed_offset, 1, 1);
+ final int sysPropPercentageConfig = SystemProperties.getInt(
+ ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f));
+ mTutorialAreaHeight = Math.round(mDisplaySize.y * (sysPropPercentageConfig / 100.0f));
mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial, null);
mTargetViewContainer.addView(mTutorialView);
mCanShowTutorial = (Settings.Secure.getInt(mContentResolver,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index df6683e..1bb5eda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.Size;
@@ -42,6 +43,9 @@
private final @NonNull PipBoundsState mPipBoundsState;
private final PipSnapAlgorithm mSnapAlgorithm;
+ private float mDefaultSizePercent;
+ private float mMinAspectRatioForMinSize;
+ private float mMaxAspectRatioForMinSize;
private float mDefaultAspectRatio;
private float mMinAspectRatio;
private float mMaxAspectRatio;
@@ -51,7 +55,7 @@
public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState) {
mPipBoundsState = pipBoundsState;
- mSnapAlgorithm = new PipSnapAlgorithm(context);
+ mSnapAlgorithm = new PipSnapAlgorithm();
reloadResources(context);
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
// resources as it would clobber mAspectRatio when entering PiP from fullscreen which
@@ -83,6 +87,11 @@
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
mMaxAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
+ mDefaultSizePercent = res.getFloat(
+ com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent);
+ mMaxAspectRatioForMinSize = res.getFloat(
+ com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
+ mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
}
/**
@@ -174,7 +183,7 @@
final int minEdgeSize = useCurrentMinEdgeSize ? mPipBoundsState.getMinEdgeSize()
: defaultMinEdgeSize;
// Use the existing size but adjusted to the aspect ratio and min edge size.
- size = mSnapAlgorithm.getSizeForAspectRatio(
+ size = getSizeForAspectRatio(
new Size(stackBounds.width(), stackBounds.height()), aspectRatio, minEdgeSize);
} else {
if (overrideMinSize != null) {
@@ -184,7 +193,7 @@
} else {
// Calculate the default size using the display size and default min edge size.
final DisplayInfo displayInfo = mPipBoundsState.getDisplayInfo();
- size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, mDefaultMinSize,
+ size = getSizeForAspectRatio(aspectRatio, mDefaultMinSize,
displayInfo.logicalWidth, displayInfo.logicalHeight);
}
}
@@ -229,7 +238,7 @@
defaultSize = adjustSizeToAspectRatio(overrideMinSize, mDefaultAspectRatio);
} else {
// Calculate the default size using the display size and default min edge size.
- defaultSize = mSnapAlgorithm.getSizeForAspectRatio(mDefaultAspectRatio,
+ defaultSize = getSizeForAspectRatio(mDefaultAspectRatio,
mDefaultMinSize, displayInfo.logicalWidth, displayInfo.logicalHeight);
}
Gravity.apply(mDefaultStackGravity, defaultSize.getWidth(), defaultSize.getHeight(),
@@ -270,13 +279,28 @@
getInsetBounds(movementBounds);
// Apply the movement bounds adjustments based on the current state.
- mSnapAlgorithm.getMovementBounds(stackBounds, movementBounds, movementBounds,
+ getMovementBounds(stackBounds, movementBounds, movementBounds,
(adjustForIme && mPipBoundsState.isImeShowing())
? mPipBoundsState.getImeHeight() : 0);
+
return movementBounds;
}
/**
+ * Adjusts movementBoundsOut so that it is the movement bounds for the given stackBounds.
+ */
+ public void getMovementBounds(Rect stackBounds, Rect insetBounds, Rect movementBoundsOut,
+ int bottomOffset) {
+ // Adjust the right/bottom to ensure the stack bounds never goes offscreen
+ movementBoundsOut.set(insetBounds);
+ movementBoundsOut.right = Math.max(insetBounds.left, insetBounds.right
+ - stackBounds.width());
+ movementBoundsOut.bottom = Math.max(insetBounds.top, insetBounds.bottom
+ - stackBounds.height());
+ movementBoundsOut.bottom -= bottomOffset;
+ }
+
+ /**
* @return the default snap fraction to apply instead of the default gravity when calculating
* the default stack bounds when first entering PiP.
*/
@@ -304,6 +328,62 @@
}
/**
+ * @return the size of the PiP at the given aspectRatio, ensuring that the minimum edge
+ * is at least minEdgeSize.
+ */
+ public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth,
+ int displayHeight) {
+ final int smallestDisplaySize = Math.min(displayWidth, displayHeight);
+ final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent);
+
+ final int width;
+ final int height;
+ if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) {
+ // Beyond these points, we can just use the min size as the shorter edge
+ if (aspectRatio <= 1) {
+ // Portrait, width is the minimum size
+ width = minSize;
+ height = Math.round(width / aspectRatio);
+ } else {
+ // Landscape, height is the minimum size
+ height = minSize;
+ width = Math.round(height * aspectRatio);
+ }
+ } else {
+ // Within these points, we ensure that the bounds fit within the radius of the limits
+ // at the points
+ final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
+ final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
+ height = (int) Math.round(Math.sqrt((radius * radius)
+ / (aspectRatio * aspectRatio + 1)));
+ width = Math.round(height * aspectRatio);
+ }
+ return new Size(width, height);
+ }
+
+ /**
+ * @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the
+ * minimum edge is at least minEdgeSize.
+ */
+ public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) {
+ final int smallestSize = Math.min(size.getWidth(), size.getHeight());
+ final int minSize = (int) Math.max(minEdgeSize, smallestSize);
+
+ final int width;
+ final int height;
+ if (aspectRatio <= 1) {
+ // Portrait, width is the minimum size.
+ width = minSize;
+ height = Math.round(width / aspectRatio);
+ } else {
+ // Landscape, height is the minimum size
+ height = minSize;
+ width = Math.round(height * aspectRatio);
+ }
+ return new Size(width, height);
+ }
+
+ /**
* Dumps internal states.
*/
public void dump(PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index ce1139b..53aa614 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -59,42 +59,39 @@
private final @NonNull Rect mExpandedBounds = new Rect();
private final @NonNull Rect mNormalMovementBounds = new Rect();
private final @NonNull Rect mExpandedMovementBounds = new Rect();
- private final Context mContext;
+ private final @NonNull Context mContext;
private float mAspectRatio;
private int mStashedState = STASH_TYPE_NONE;
private int mStashOffset;
- private PipReentryState mPipReentryState;
- private ComponentName mLastPipComponentName;
- private final DisplayInfo mDisplayInfo = new DisplayInfo();
- private final DisplayLayout mDisplayLayout = new DisplayLayout();
+ private @Nullable PipReentryState mPipReentryState;
+ private @Nullable ComponentName mLastPipComponentName;
+ private final @NonNull DisplayInfo mDisplayInfo = new DisplayInfo();
+ private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout();
/** The current minimum edge size of PIP. */
private int mMinEdgeSize;
/** The preferred minimum (and default) size specified by apps. */
- private Size mOverrideMinSize;
- private final @NonNull AnimatingBoundsState mAnimatingBoundsState = new AnimatingBoundsState();
+ private @Nullable Size mOverrideMinSize;
+ private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
private boolean mIsImeShowing;
private int mImeHeight;
private boolean mIsShelfShowing;
private int mShelfHeight;
- private Runnable mOnMinimalSizeChangeCallback;
- private BiConsumer<Boolean, Integer> mOnShelfVisibilityChangeCallback;
+ private @Nullable Runnable mOnMinimalSizeChangeCallback;
+ private @Nullable BiConsumer<Boolean, Integer> mOnShelfVisibilityChangeCallback;
- public PipBoundsState(Context context) {
+ public PipBoundsState(@NonNull Context context) {
mContext = context;
reloadResources();
}
- /**
- * Reloads the resources.
- */
+ /** Reloads the resources. */
public void onConfigurationChanged() {
reloadResources();
}
private void reloadResources() {
- mStashOffset = mContext.getResources()
- .getDimensionPixelSize(R.dimen.pip_stash_offset);
+ mStashOffset = mContext.getResources().getDimensionPixelSize(R.dimen.pip_stash_offset);
}
/** Set the current PIP bounds. */
@@ -102,12 +99,14 @@
mBounds.set(bounds);
}
+ /** Get the current PIP bounds. */
@NonNull
public Rect getBounds() {
return new Rect(mBounds);
}
/** Returns the current movement bounds. */
+ @NonNull
public Rect getMovementBounds() {
return mMovementBounds;
}
@@ -135,28 +134,28 @@
}
/** Set the normal movement bounds. */
- public void setNormalMovementBounds(Rect bounds) {
+ public void setNormalMovementBounds(@NonNull Rect bounds) {
mNormalMovementBounds.set(bounds);
}
/** Returns the normal movement bounds. */
+ @NonNull
public Rect getNormalMovementBounds() {
return mNormalMovementBounds;
}
/** Set the expanded movement bounds. */
- public void setExpandedMovementBounds(Rect bounds) {
+ public void setExpandedMovementBounds(@NonNull Rect bounds) {
mExpandedMovementBounds.set(bounds);
}
/** Returns the expanded movement bounds. */
+ @NonNull
public Rect getExpandedMovementBounds() {
return mExpandedMovementBounds;
}
- /**
- * Dictate where PiP currently should be stashed, if at all.
- */
+ /** Dictate where PiP currently should be stashed, if at all. */
public void setStashed(@StashType int stashedState) {
mStashedState = stashedState;
}
@@ -169,50 +168,39 @@
return mStashedState;
}
- /**
- * Whether PiP is stashed or not.
- */
+ /** Whether PiP is stashed or not. */
public boolean isStashed() {
return mStashedState != STASH_TYPE_NONE;
}
- /**
- * Returns the offset from the edge of the screen for PiP stash.
- */
+ /** Returns the offset from the edge of the screen for PiP stash. */
public int getStashOffset() {
return mStashOffset;
}
+ /** Set the PIP aspect ratio. */
public void setAspectRatio(float aspectRatio) {
mAspectRatio = aspectRatio;
}
+ /** Get the PIP aspect ratio. */
public float getAspectRatio() {
return mAspectRatio;
}
- /**
- * Save the reentry state to restore to when re-entering PIP mode.
- *
- * TODO(b/169373982): consider refactoring this so that this class alone can use mBounds and
- * calculate the snap fraction to save for re-entry.
- */
+ /** Save the reentry state to restore to when re-entering PIP mode. */
public void saveReentryState(@NonNull Rect bounds, float fraction) {
mPipReentryState = new PipReentryState(new Size(bounds.width(), bounds.height()), fraction);
}
- /**
- * Returns the saved reentry state.
- */
+ /** Returns the saved reentry state. */
@Nullable
public PipReentryState getReentryState() {
return mPipReentryState;
}
- /**
- * Set the last {@link ComponentName} to enter PIP mode.
- */
- public void setLastPipComponentName(ComponentName lastPipComponentName) {
+ /** Set the last {@link ComponentName} to enter PIP mode. */
+ public void setLastPipComponentName(@Nullable ComponentName lastPipComponentName) {
final boolean changed = !Objects.equals(mLastPipComponentName, lastPipComponentName);
mLastPipComponentName = lastPipComponentName;
if (changed) {
@@ -220,41 +208,40 @@
}
}
+ /** Get the last PIP component name, if any. */
+ @Nullable
public ComponentName getLastPipComponentName() {
return mLastPipComponentName;
}
+ /** Get the current display info. */
@NonNull
public DisplayInfo getDisplayInfo() {
return mDisplayInfo;
}
- /**
- * Update the display info.
- */
+ /** Update the display info. */
public void setDisplayInfo(@NonNull DisplayInfo displayInfo) {
mDisplayInfo.copyFrom(displayInfo);
}
+ /** Set the rotation of the display. */
public void setDisplayRotation(int rotation) {
mDisplayInfo.rotation = rotation;
}
- /**
- * Returns the display's bound.
- */
+ /** Returns the display's bounds. */
@NonNull
public Rect getDisplayBounds() {
return new Rect(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
}
- /**
- * Update the display layout.
- */
+ /** Update the display layout. */
public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
mDisplayLayout.set(displayLayout);
}
+ /** Get the display layout. */
@NonNull
public DisplayLayout getDisplayLayout() {
return mDisplayLayout;
@@ -275,10 +262,8 @@
return mMinEdgeSize;
}
- /**
- * Sets the preferred size of PIP as specified by the activity in PIP mode.
- */
- public void setOverrideMinSize(Size overrideMinSize) {
+ /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
+ public void setOverrideMinSize(@Nullable Size overrideMinSize) {
final boolean changed = !Objects.equals(overrideMinSize, mOverrideMinSize);
mOverrideMinSize = overrideMinSize;
if (changed && mOnMinimalSizeChangeCallback != null) {
@@ -287,6 +272,7 @@
}
/** Returns the preferred minimal size specified by the activity in PIP. */
+ @Nullable
public Size getOverrideMinSize() {
return mOverrideMinSize;
}
@@ -297,8 +283,10 @@
return Math.min(mOverrideMinSize.getWidth(), mOverrideMinSize.getHeight());
}
- public AnimatingBoundsState getAnimatingBoundsState() {
- return mAnimatingBoundsState;
+ /** Get the state of the bounds in motion. */
+ @NonNull
+ public MotionBoundsState getMotionBoundsState() {
+ return mMotionBoundsState;
}
/** Set whether the IME is currently showing and its height. */
@@ -344,41 +332,41 @@
/**
* Registers a callback when the minimal size of PIP that is set by the app changes.
*/
- public void setOnMinimalSizeChangeCallback(Runnable onMinimalSizeChangeCallback) {
+ public void setOnMinimalSizeChangeCallback(@Nullable Runnable onMinimalSizeChangeCallback) {
mOnMinimalSizeChangeCallback = onMinimalSizeChangeCallback;
}
/** Set a callback to be notified when the shelf visibility changes. */
public void setOnShelfVisibilityChangeCallback(
- BiConsumer<Boolean, Integer> onShelfVisibilityChangeCallback) {
+ @Nullable BiConsumer<Boolean, Integer> onShelfVisibilityChangeCallback) {
mOnShelfVisibilityChangeCallback = onShelfVisibilityChangeCallback;
}
- /** Source of truth for the current animation bounds of PIP. */
- public static class AnimatingBoundsState {
- /** The bounds used when PIP is being dragged or animated. */
- private final Rect mTemporaryBounds = new Rect();
+ /** Source of truth for the current bounds of PIP that may be in motion. */
+ public static class MotionBoundsState {
+ /** The bounds used when PIP is in motion (e.g. during a drag or animation) */
+ private final @NonNull Rect mBoundsInMotion = new Rect();
/** The destination bounds to which PIP is animating. */
- private final Rect mAnimatingToBounds = new Rect();
+ private final @NonNull Rect mAnimatingToBounds = new Rect();
/** Whether PIP is being dragged or animated (e.g. resizing, in fling, etc). */
- public boolean isAnimating() {
- return !mTemporaryBounds.isEmpty();
+ public boolean isInMotion() {
+ return !mBoundsInMotion.isEmpty();
}
/** Set the temporary bounds used to represent the drag or animation bounds of PIP. */
- public void setTemporaryBounds(Rect bounds) {
- mTemporaryBounds.set(bounds);
+ public void setBoundsInMotion(@NonNull Rect bounds) {
+ mBoundsInMotion.set(bounds);
}
/** Set the bounds to which PIP is animating. */
- public void setAnimatingToBounds(Rect bounds) {
+ public void setAnimatingToBounds(@NonNull Rect bounds) {
mAnimatingToBounds.set(bounds);
}
- /** Called when all ongoing dragging and animation operations have ended. */
+ /** Called when all ongoing motion operations have ended. */
public void onAllAnimationsEnded() {
- mTemporaryBounds.setEmpty();
+ mBoundsInMotion.setEmpty();
}
/** Called when an ongoing physics animation has ended. */
@@ -386,20 +374,22 @@
mAnimatingToBounds.setEmpty();
}
- /** Returns the temporary animation bounds. */
- public Rect getTemporaryBounds() {
- return mTemporaryBounds;
+ /** Returns the motion bounds. */
+ @NonNull
+ public Rect getBoundsInMotion() {
+ return mBoundsInMotion;
}
/** Returns the destination bounds to which PIP is currently animating. */
+ @NonNull
public Rect getAnimatingToBounds() {
return mAnimatingToBounds;
}
void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
- pw.println(prefix + AnimatingBoundsState.class.getSimpleName());
- pw.println(innerPrefix + "mTemporaryBounds=" + mTemporaryBounds);
+ pw.println(prefix + MotionBoundsState.class.getSimpleName());
+ pw.println(innerPrefix + "mBoundsInMotion=" + mBoundsInMotion);
pw.println(innerPrefix + "mAnimatingToBounds=" + mAnimatingToBounds);
}
}
@@ -432,9 +422,7 @@
}
}
- /**
- * Dumps internal state.
- */
+ /** Dumps internal state. */
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
@@ -461,6 +449,6 @@
} else {
mPipReentryState.dump(pw, innerPrefix);
}
- mAnimatingBoundsState.dump(pw, innerPrefix);
+ mMotionBoundsState.dump(pw, innerPrefix);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
index 7106075..0528e4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
@@ -20,11 +20,9 @@
import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.PointF;
import android.graphics.Rect;
-import android.util.Size;
+
+import com.android.internal.annotations.VisibleForTesting;
/**
* Calculates the snap targets and the snap position for the PIP given a position and a velocity.
@@ -32,19 +30,6 @@
*/
public class PipSnapAlgorithm {
- private final float mDefaultSizePercent;
- private final float mMinAspectRatioForMinSize;
- private final float mMaxAspectRatioForMinSize;
-
- public PipSnapAlgorithm(Context context) {
- Resources res = context.getResources();
- mDefaultSizePercent = res.getFloat(
- com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent);
- mMaxAspectRatioForMinSize = res.getFloat(
- com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
- mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
- }
-
/**
* Returns a fraction that describes where the PiP bounds is.
* See {@link #getSnapFraction(Rect, Rect, int)}.
@@ -136,81 +121,11 @@
}
/**
- * Adjusts {@param movementBoundsOut} so that it is the movement bounds for the given
- * {@param stackBounds}.
- */
- public void getMovementBounds(Rect stackBounds, Rect insetBounds, Rect movementBoundsOut,
- int bottomOffset) {
- // Adjust the right/bottom to ensure the stack bounds never goes offscreen
- movementBoundsOut.set(insetBounds);
- movementBoundsOut.right = Math.max(insetBounds.left, insetBounds.right -
- stackBounds.width());
- movementBoundsOut.bottom = Math.max(insetBounds.top, insetBounds.bottom -
- stackBounds.height());
- movementBoundsOut.bottom -= bottomOffset;
- }
-
- /**
- * @return the size of the PiP at the given {@param aspectRatio}, ensuring that the minimum edge
- * is at least {@param minEdgeSize}.
- */
- public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth,
- int displayHeight) {
- final int smallestDisplaySize = Math.min(displayWidth, displayHeight);
- final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent);
-
- final int width;
- final int height;
- if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) {
- // Beyond these points, we can just use the min size as the shorter edge
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
- } else {
- // Within these points, we ensure that the bounds fit within the radius of the limits
- // at the points
- final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
- final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
- height = (int) Math.round(Math.sqrt((radius * radius) /
- (aspectRatio * aspectRatio + 1)));
- width = Math.round(height * aspectRatio);
- }
- return new Size(width, height);
- }
-
- /**
- * @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the
- * minimum edge is at least minEdgeSize.
- */
- public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) {
- final int smallestSize = Math.min(size.getWidth(), size.getHeight());
- final int minSize = (int) Math.max(minEdgeSize, smallestSize);
-
- final int width;
- final int height;
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size.
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
- return new Size(width, height);
- }
-
- /**
* Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes
* the new bounds out to {@param boundsOut}.
*/
- public void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
+ @VisibleForTesting
+ void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
@PipBoundsState.StashType int stashType) {
int leftEdge = stackBounds.left;
if (stashType == STASH_TYPE_LEFT) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 8fa9448..903f7d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -90,7 +90,7 @@
});
/**
- * PhysicsAnimator instance for animating {@link PipBoundsState#getAnimatingBoundsState()}
+ * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
* using physics animations.
*/
private final PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator;
@@ -98,7 +98,7 @@
private MagnetizedObject<Rect> mMagnetizedPip;
/**
- * Update listener that resizes the PIP to {@link PipBoundsState#getAnimatingBoundsState()}.
+ * Update listener that resizes the PIP to {@link PipBoundsState#getMotionBoundsState()}.
*/
private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener;
@@ -172,14 +172,14 @@
mFloatingContentCoordinator = floatingContentCoordinator;
mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
- mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds());
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
mSfAnimationHandlerThreadLocal.get());
mResizePipUpdateListener = (target, values) -> {
- if (mPipBoundsState.getAnimatingBoundsState().isAnimating()) {
+ if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
- mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds(), null);
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion(), null);
}
};
}
@@ -187,8 +187,8 @@
@NonNull
@Override
public Rect getFloatingBoundsOnScreen() {
- return !mPipBoundsState.getAnimatingBoundsState().getAnimatingToBounds().isEmpty()
- ? mPipBoundsState.getAnimatingBoundsState().getAnimatingToBounds() : getBounds();
+ return !mPipBoundsState.getMotionBoundsState().getAnimatingToBounds().isEmpty()
+ ? mPipBoundsState.getMotionBoundsState().getAnimatingToBounds() : getBounds();
}
@NonNull
@@ -207,7 +207,7 @@
*/
void synchronizePinnedStackBounds() {
cancelPhysicsAnimation();
- mPipBoundsState.getAnimatingBoundsState().onAllAnimationsEnded();
+ mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
if (mPipTaskOrganizer.isInPip()) {
mFloatingContentCoordinator.onContentMoved(this);
@@ -242,7 +242,7 @@
resizePipUnchecked(toBounds);
mPipBoundsState.setBounds(toBounds);
} else {
- mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(toBounds);
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds);
mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
(Rect newBounds) -> {
mMainHandler.post(() -> {
@@ -278,8 +278,8 @@
// If we're already in the dismiss target area, then there won't be a move to set the
// temporary bounds, so just initialize it to the current bounds.
- if (!mPipBoundsState.getAnimatingBoundsState().isAnimating()) {
- mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(getBounds());
+ if (!mPipBoundsState.getMotionBoundsState().isInMotion()) {
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
}
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig)
@@ -396,7 +396,7 @@
final float xEndValue = velocityX < 0 ? leftEdge : rightEdge;
- final int startValueY = mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds().top;
+ final int startValueY = mPipBoundsState.getMotionBoundsState().getBoundsInMotion().top;
final float estimatedFlingYEndValue =
PhysicsAnimator.estimateFlingEndValue(startValueY, velocityY, mFlingConfigY);
@@ -411,7 +411,7 @@
void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) {
if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
// Animate from the current bounds if we're not already animating.
- mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(getBounds());
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
}
mTemporaryBoundsPhysicsAnimator
@@ -492,7 +492,7 @@
*/
private void cancelPhysicsAnimation() {
mTemporaryBoundsPhysicsAnimator.cancel();
- mPipBoundsState.getAnimatingBoundsState().onPhysicsAnimationEnded();
+ mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
mSpringingToTouch = false;
}
@@ -565,17 +565,17 @@
if (!mDismissalPending
&& !mSpringingToTouch
&& !mMagnetizedPip.getObjectStuckToTarget()) {
- // All animations (including dragging) have actually finished.
+ // All motion operations have actually finished.
mPipBoundsState.setBounds(
- mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds());
- mPipBoundsState.getAnimatingBoundsState().onAllAnimationsEnded();
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+ mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
if (!mDismissalPending) {
// do not schedule resize if PiP is dismissing, which may cause app re-open to
// mBounds instead of it's normal bounds.
mPipTaskOrganizer.scheduleFinishResizePip(getBounds());
}
}
- mPipBoundsState.getAnimatingBoundsState().onPhysicsAnimationEnded();
+ mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
mSpringingToTouch = false;
mDismissalPending = false;
}
@@ -586,7 +586,7 @@
* {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}.
*/
private void setAnimatingToBounds(Rect bounds) {
- mPipBoundsState.getAnimatingBoundsState().setAnimatingToBounds(bounds);
+ mPipBoundsState.getMotionBoundsState().setAnimatingToBounds(bounds);
mFloatingContentCoordinator.onContentMoved(this);
}
@@ -625,7 +625,7 @@
MagnetizedObject<Rect> getMagnetizedPip() {
if (mMagnetizedPip == null) {
mMagnetizedPip = new MagnetizedObject<Rect>(
- mContext, mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds(),
+ mContext, mPipBoundsState.getMotionBoundsState().getBoundsInMotion(),
FloatProperties.RECT_X, FloatProperties.RECT_Y) {
@Override
public float getWidth(@NonNull Rect animatedPipBounds) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 1c5d5b8..a78c4ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -307,8 +307,7 @@
public void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
final Rect toMovementBounds = new Rect();
- mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(outBounds, insetBounds,
- toMovementBounds, 0);
+ mPipBoundsAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0);
final int prevBottom = mPipBoundsState.getMovementBounds().bottom
- mMovementBoundsExtraOffsets;
if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) {
@@ -339,13 +338,13 @@
// Re-calculate the expanded bounds
Rect normalMovementBounds = new Rect();
- mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(normalBounds, insetBounds,
+ mPipBoundsAlgorithm.getMovementBounds(normalBounds, insetBounds,
normalMovementBounds, bottomOffset);
if (mPipBoundsState.getMovementBounds().isEmpty()) {
// mMovementBounds is not initialized yet and a clean movement bounds without
// bottom offset shall be used later in this function.
- mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(curBounds, insetBounds,
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
mPipBoundsState.getMovementBounds(), 0 /* bottomOffset */);
}
@@ -353,12 +352,12 @@
float aspectRatio = (float) normalBounds.width() / normalBounds.height();
Point displaySize = new Point();
mContext.getDisplay().getRealSize(displaySize);
- Size expandedSize = mPipBoundsAlgorithm.getSnapAlgorithm().getSizeForAspectRatio(
+ Size expandedSize = mPipBoundsAlgorithm.getSizeForAspectRatio(
aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
mPipBoundsState.setExpandedBounds(
new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
Rect expandedMovementBounds = new Rect();
- mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(
+ mPipBoundsAlgorithm.getMovementBounds(
mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
bottomOffset);
@@ -381,7 +380,7 @@
} else {
final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu();
final Rect toMovementBounds = new Rect();
- mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(curBounds, insetBounds,
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
toMovementBounds, mIsImeShowing ? mImeHeight : 0);
final int prevBottom = mPipBoundsState.getMovementBounds().bottom
- mMovementBoundsExtraOffsets;
@@ -659,7 +658,7 @@
private void animateToUnexpandedState(Rect restoreBounds) {
Rect restoredMovementBounds = new Rect();
- mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(restoreBounds,
+ mPipBoundsAlgorithm.getMovementBounds(restoreBounds,
mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */);
@@ -707,7 +706,7 @@
return;
}
- Rect bounds = getPossiblyAnimatingBounds();
+ Rect bounds = getPossiblyMotionBounds();
mDelta.set(0f, 0f);
mStartPosition.set(bounds.left, bounds.top);
mMovementWithinDismiss = touchState.getDownTouchPosition().y
@@ -746,7 +745,7 @@
mDelta.x += left - lastX;
mDelta.y += top - lastY;
- mTmpBounds.set(getPossiblyAnimatingBounds());
+ mTmpBounds.set(getPossiblyMotionBounds());
mTmpBounds.offsetTo((int) left, (int) top);
mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
@@ -865,7 +864,7 @@
* resized.
*/
private void updateMovementBounds() {
- mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(mPipBoundsState.getBounds(),
+ mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
mMotionHelper.onMovementBoundsChanged();
@@ -877,7 +876,7 @@
private Rect getMovementBounds(Rect curBounds) {
Rect movementBounds = new Rect();
- mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(curBounds, mInsetBounds,
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds,
movementBounds, mIsImeShowing ? mImeHeight : 0);
return movementBounds;
}
@@ -896,12 +895,12 @@
}
/**
- * Returns the PIP bounds if we're not animating, or the current, temporary animating bounds
- * otherwise.
+ * Returns the PIP bounds if we're not in the middle of a motion operation, or the current,
+ * temporary motion bounds otherwise.
*/
- Rect getPossiblyAnimatingBounds() {
- return mPipBoundsState.getAnimatingBoundsState().isAnimating()
- ? mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds()
+ Rect getPossiblyMotionBounds() {
+ return mPipBoundsState.getMotionBoundsState().isInMotion()
+ ? mPipBoundsState.getMotionBoundsState().getBoundsInMotion()
: mPipBoundsState.getBounds();
}
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
new file mode 100644
index 0000000..2c6c7b3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -0,0 +1,2 @@
+# includes OWNERS from parent directories
+natanieljr@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index e1c9384..0fb43e2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -92,6 +92,26 @@
}
@JvmOverloads
+fun LayersAssertion.appPairsDividerIsVisible(
+ bugId: Int = 0,
+ enabled: Boolean = bugId == 0
+) {
+ end("appPairsDividerIsVisible", bugId, enabled) {
+ this.showsLayer(FlickerTestBase.APP_PAIRS_DIVIDER)
+ }
+}
+
+@JvmOverloads
+fun LayersAssertion.appPairsDividerIsInvisible(
+ bugId: Int = 0,
+ enabled: Boolean = bugId == 0
+) {
+ end("appPairsDividerIsInVisible", bugId, enabled) {
+ this.hasNotLayer(FlickerTestBase.APP_PAIRS_DIVIDER)
+ }
+}
+
+@JvmOverloads
fun LayersAssertion.dockedStackDividerIsVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
index 2e6037d..54b8fdc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
@@ -130,6 +130,7 @@
const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
+ const val APP_PAIRS_DIVIDER = "AppPairDivider"
const val IMAGE_WALLPAPER = "ImageWallpaper"
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
new file mode 100644
index 0000000..2fc6944
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.apppairs
+
+import android.os.SystemClock
+import android.util.Log
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.compatibility.common.util.SystemUtil
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.TEST_REPETITIONS
+import com.android.wm.shell.flicker.appPairsDividerIsInvisible
+import com.android.wm.shell.flicker.appPairsDividerIsVisible
+import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
+import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
+import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+import java.io.IOException
+
+/**
+ * Test AppPairs launch.
+ * To run this test: `atest WMShellFlickerTests:AppPairsTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AppPairsTest(
+ rotationName: String,
+ rotation: Int
+) : AppPairsTestBase(rotationName, rotation) {
+ private val appPairsSetup: FlickerBuilder
+ get() = FlickerBuilder(instrumentation).apply {
+ val testLaunchActivity = "launch_appPairs_primary_secondary_activities"
+ withTestName {
+ testLaunchActivity
+ }
+ setup {
+ eachRun {
+ uiDevice.wakeUpAndGoToHomeScreen()
+ primaryApp.open()
+ uiDevice.pressHome()
+ secondaryApp.open()
+ uiDevice.pressHome()
+ updateTaskId()
+ }
+ }
+ teardown {
+ eachRun {
+ executeShellCommand(composePairsCommand(
+ primaryTaskId, secondaryTaskId, false /* pair */))
+ primaryApp.exit()
+ secondaryApp.exit()
+ }
+ }
+ assertions {
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ }
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ }
+ }
+
+ @Test
+ fun testAppPairs_pairPrimaryAndSecondaryApps() {
+ val testTag = "testAppPaired_pairPrimaryAndSecondary"
+ runWithFlicker(appPairsSetup) {
+ withTestName { testTag }
+ repeat {
+ TEST_REPETITIONS
+ }
+ transitions {
+ // TODO pair apps through normal UX flow
+ executeShellCommand(composePairsCommand(
+ primaryTaskId, secondaryTaskId, true /* pair */))
+ SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+ }
+ assertions {
+ layersTrace {
+ appPairsDividerIsVisible()
+ end("appsEndingBounds", enabled = false) {
+ val entry = this.trace.entries.firstOrNull()
+ ?: throw IllegalStateException("Trace is empty")
+ val dividerRegion = entry.getVisibleBounds(APP_PAIRS_DIVIDER)
+ this.hasVisibleRegion(primaryApp.defaultWindowName,
+ appPairsHelper.getPrimaryBounds(dividerRegion))
+ .and()
+ .hasVisibleRegion(secondaryApp.defaultWindowName,
+ appPairsHelper.getSecondaryBounds(dividerRegion))
+ }
+ }
+ windowManagerTrace {
+ end {
+ showsAppWindow(primaryApp.defaultWindowName)
+ .and()
+ .showsAppWindow(secondaryApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testAppPairs_unpairPrimaryAndSecondary() {
+ val testTag = "testAppPairs_unpairPrimaryAndSecondary"
+ runWithFlicker(appPairsSetup) {
+ withTestName { testTag }
+ repeat {
+ TEST_REPETITIONS
+ }
+ setup {
+ executeShellCommand(composePairsCommand(
+ primaryTaskId, secondaryTaskId, true /* pair */))
+ SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+ }
+ transitions {
+ // TODO pair apps through normal UX flow
+ executeShellCommand(composePairsCommand(
+ primaryTaskId, secondaryTaskId, false /* pair */))
+ SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+ }
+ assertions {
+ layersTrace {
+ appPairsDividerIsInvisible()
+ start("appsStartingBounds", enabled = false) {
+ val entry = this.trace.entries.firstOrNull()
+ ?: throw IllegalStateException("Trace is empty")
+ val dividerRegion = entry.getVisibleBounds(APP_PAIRS_DIVIDER)
+ this.hasVisibleRegion(primaryApp.defaultWindowName,
+ appPairsHelper.getPrimaryBounds(dividerRegion))
+ .and()
+ .hasVisibleRegion(secondaryApp.defaultWindowName,
+ appPairsHelper.getSecondaryBounds(dividerRegion))
+ }
+ end("appsEndingBounds", enabled = false) {
+ this.hasNotLayer(primaryApp.defaultWindowName)
+ .and()
+ .hasNotLayer(secondaryApp.defaultWindowName)
+ }
+ }
+ windowManagerTrace {
+ end {
+ hidesAppWindow(primaryApp.defaultWindowName)
+ .and()
+ .hidesAppWindow(secondaryApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ }
+
+ private fun composePairsCommand(
+ primaryApp: String,
+ secondaryApp: String,
+ pair: Boolean
+ ): String = buildString {
+ // dumpsys activity service SystemUIService WMShell {pair|unpair} ${TASK_ID_1} ${TASK_ID_2}
+ append("dumpsys activity service SystemUIService WMShell ")
+ if (pair) {
+ append("pair ")
+ } else {
+ append("unpair ")
+ }
+ append(primaryApp + " " + secondaryApp)
+ }
+
+ private fun executeShellCommand(cmd: String) {
+ try {
+ SystemUtil.runShellCommand(instrumentation, cmd)
+ } catch (e: IOException) {
+ Log.d("AppPairsTest", "executeShellCommand error!" + e)
+ }
+ }
+
+ private fun updateTaskId() {
+ val primaryAppComponent = primaryApp.openAppIntent.component
+ val secondaryAppComponent = secondaryApp.openAppIntent.component
+ if (primaryAppComponent != null) {
+ primaryTaskId = appPairsHelper.getTaskIdForActivity(
+ primaryAppComponent.packageName, primaryAppComponent.className).toString()
+ }
+ if (secondaryAppComponent != null) {
+ secondaryTaskId = appPairsHelper.getTaskIdForActivity(
+ secondaryAppComponent.packageName, secondaryAppComponent.className).toString()
+ }
+ }
+
+ companion object {
+ var primaryTaskId = ""
+ var secondaryTaskId = ""
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0)
+ return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt
new file mode 100644
index 0000000..1a4de0a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.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.wm.shell.flicker.apppairs
+
+import com.android.wm.shell.flicker.NonRotationTestBase
+import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME
+import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
+import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME
+import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_LABEL
+import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+
+abstract class AppPairsTestBase(
+ rotationName: String,
+ rotation: Int
+) : NonRotationTestBase(rotationName, rotation) {
+ protected val appPairsHelper = AppPairsHelper(instrumentation,
+ TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
+ TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME)
+ protected val primaryApp = SplitScreenHelper(instrumentation,
+ TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
+ TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME)
+ protected val secondaryApp = SplitScreenHelper(instrumentation,
+ TEST_APP_SPLITSCREEN_SECONDARY_LABEL,
+ TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME)
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
new file mode 100644
index 0000000..3b6fcdb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.graphics.Region
+import android.system.helpers.ActivityHelper
+import com.android.server.wm.flicker.helpers.WindowUtils
+
+class AppPairsHelper(
+ instrumentation: Instrumentation,
+ activityLabel: String,
+ componentName: ComponentName
+) : BaseAppHelper(
+ instrumentation,
+ activityLabel,
+ componentName
+) {
+ val activityHelper = ActivityHelper.getInstance()
+
+ fun getPrimaryBounds(dividerBounds: Region): android.graphics.Region {
+ val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right,
+ dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset)
+ return primaryAppBounds
+ }
+
+ fun getSecondaryBounds(dividerBounds: Region): android.graphics.Region {
+ val displayBounds = WindowUtils.displayBounds
+ val secondaryAppBounds = Region(0,
+ dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset,
+ displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight)
+ return secondaryAppBounds
+ }
+
+ fun getTaskIdForActivity(pkgName: String, activityName: String): Int {
+ return activityHelper.getTaskIdForActivity(pkgName, activityName)
+ }
+
+ companion object {
+ const val TEST_REPETITIONS = 1
+ const val TIMEOUT_MS = 3_000L
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
index 6f008ce..22496a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.helpers
-import android.app.ActivityManager
import android.app.Instrumentation
import android.content.ComponentName
import android.content.Context
@@ -46,9 +45,6 @@
protected val context: Context
get() = mInstrumentation.context
- private val activityManager: ActivityManager?
- get() = context.getSystemService(ActivityManager::class.java)
-
private val appSelector = By.pkg(packageName).depth(0)
protected val isTelevision: Boolean
@@ -77,10 +73,6 @@
return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
}
- fun forceStop() {
- activityManager?.forceStopPackage(packageName)
- }
-
override fun getOpenAppIntent(): Intent {
val intent = Intent()
intent.component = launcherActivityComponent
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index d343f2a..abb8fc5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -210,4 +210,4 @@
return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index d1906ba..251bc06 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -53,7 +53,7 @@
open fun tearDown() {
if (!isTelevision) return
- testApp.forceStop()
+ testApp.exit()
// Wait for 1 second, and check if the SystemUI has been alive and well since the start.
SystemClock.sleep(AFTER_TEXT_PROCESS_CHECK_DELAY)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
index 6e10f93..a0056df 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
@@ -60,8 +60,8 @@
}
teardown {
eachRun {
- splitScreenApp.forceStop()
- secondaryApp.forceStop()
+ splitScreenApp.exit()
+ secondaryApp.exit()
}
}
assertions {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
index 17fc862..32e112d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
@@ -65,8 +65,8 @@
}
teardown {
eachRun {
- splitScreenApp.forceStop()!!
- secondaryApp.forceStop()!!
+ splitScreenApp.exit()
+ secondaryApp.exit()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 7a6e0c1..a65d832 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -50,6 +50,7 @@
private static final float DEFAULT_ASPECT_RATIO = 1f;
private static final float MIN_ASPECT_RATIO = 0.5f;
private static final float MAX_ASPECT_RATIO = 2f;
+ private static final int DEFAULT_MIN_EDGE_SIZE = 100;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private DisplayInfo mDefaultDisplayInfo;
@@ -73,7 +74,8 @@
com.android.internal.R.integer.config_defaultPictureInPictureGravity,
Gravity.END | Gravity.BOTTOM);
res.addOverride(
- com.android.internal.R.dimen.default_minimal_size_pip_resizable_task, 100);
+ com.android.internal.R.dimen.default_minimal_size_pip_resizable_task,
+ DEFAULT_MIN_EDGE_SIZE);
res.addOverride(
com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets,
"16x16");
@@ -112,6 +114,127 @@
}
@Test
+ public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
+ final Size defaultSize = mPipBoundsAlgorithm.getSizeForAspectRatio(DEFAULT_ASPECT_RATIO,
+ DEFAULT_MIN_EDGE_SIZE, mDefaultDisplayInfo.logicalWidth,
+ mDefaultDisplayInfo.logicalHeight);
+
+ mPipBoundsState.setOverrideMinSize(null);
+ final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+ assertEquals(defaultSize, new Size(defaultBounds.width(), defaultBounds.height()));
+ assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
+ ASPECT_RATIO_ERROR_MARGIN);
+ }
+
+ @Test
+ public void getDefaultBounds_widerOverrideMinSize_matchesMinSizeWidthAndDefaultAspectRatio() {
+ overrideDefaultAspectRatio(1.0f);
+ // The min size's aspect ratio is greater than the default aspect ratio.
+ final Size overrideMinSize = new Size(150, 120);
+
+ mPipBoundsState.setOverrideMinSize(overrideMinSize);
+ final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+ // The default aspect ratio should trump the min size aspect ratio.
+ assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
+ ASPECT_RATIO_ERROR_MARGIN);
+ // The width of the min size is still used with the default aspect ratio.
+ assertEquals(overrideMinSize.getWidth(), defaultBounds.width());
+ }
+
+ @Test
+ public void getDefaultBounds_tallerOverrideMinSize_matchesMinSizeHeightAndDefaultAspectRatio() {
+ overrideDefaultAspectRatio(1.0f);
+ // The min size's aspect ratio is greater than the default aspect ratio.
+ final Size overrideMinSize = new Size(120, 150);
+
+ mPipBoundsState.setOverrideMinSize(overrideMinSize);
+ final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+ // The default aspect ratio should trump the min size aspect ratio.
+ assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
+ ASPECT_RATIO_ERROR_MARGIN);
+ // The height of the min size is still used with the default aspect ratio.
+ assertEquals(overrideMinSize.getHeight(), defaultBounds.height());
+ }
+
+ @Test
+ public void getDefaultBounds_imeShowing_offsetByImeHeight() {
+ final int imeHeight = 30;
+ mPipBoundsState.setImeVisibility(false, 0);
+ final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+ mPipBoundsState.setImeVisibility(true, imeHeight);
+ final Rect defaultBoundsWithIme = mPipBoundsAlgorithm.getDefaultBounds();
+
+ assertEquals(imeHeight, defaultBounds.top - defaultBoundsWithIme.top);
+ }
+
+ @Test
+ public void getDefaultBounds_shelfShowing_offsetByShelfHeight() {
+ final int shelfHeight = 30;
+ mPipBoundsState.setShelfVisibility(false, 0);
+ final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+ mPipBoundsState.setShelfVisibility(true, shelfHeight);
+ final Rect defaultBoundsWithShelf = mPipBoundsAlgorithm.getDefaultBounds();
+
+ assertEquals(shelfHeight, defaultBounds.top - defaultBoundsWithShelf.top);
+ }
+
+ @Test
+ public void getDefaultBounds_imeAndShelfShowing_offsetByTallest() {
+ final int imeHeight = 30;
+ final int shelfHeight = 40;
+ mPipBoundsState.setImeVisibility(false, 0);
+ mPipBoundsState.setShelfVisibility(false, 0);
+ final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+ mPipBoundsState.setImeVisibility(true, imeHeight);
+ mPipBoundsState.setShelfVisibility(true, shelfHeight);
+ final Rect defaultBoundsWithIme = mPipBoundsAlgorithm.getDefaultBounds();
+
+ assertEquals(shelfHeight, defaultBounds.top - defaultBoundsWithIme.top);
+ }
+
+ @Test
+ public void getDefaultBounds_boundsAtDefaultGravity() {
+ final Rect insetBounds = new Rect();
+ mPipBoundsAlgorithm.getInsetBounds(insetBounds);
+ overrideDefaultStackGravity(Gravity.END | Gravity.BOTTOM);
+
+ final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+ assertEquals(insetBounds.bottom, defaultBounds.bottom);
+ assertEquals(insetBounds.right, defaultBounds.right);
+ }
+
+ @Test
+ public void getNormalBounds_invalidAspectRatio_returnsDefaultBounds() {
+ final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+ // Set an invalid current aspect ratio.
+ mPipBoundsState.setAspectRatio(MIN_ASPECT_RATIO / 2);
+ final Rect normalBounds = mPipBoundsAlgorithm.getNormalBounds();
+
+ assertEquals(defaultBounds, normalBounds);
+ }
+
+ @Test
+ public void getNormalBounds_validAspectRatio_returnsAdjustedDefaultBounds() {
+ final Rect defaultBoundsAdjustedToAspectRatio = mPipBoundsAlgorithm.getDefaultBounds();
+ mPipBoundsAlgorithm.transformBoundsToAspectRatio(defaultBoundsAdjustedToAspectRatio,
+ MIN_ASPECT_RATIO, false /* useCurrentMinEdgeSize */, false /* useCurrentSize */);
+
+ // Set a valid current aspect ratio different that the default.
+ mPipBoundsState.setAspectRatio(MIN_ASPECT_RATIO);
+ final Rect normalBounds = mPipBoundsAlgorithm.getNormalBounds();
+
+ assertEquals(defaultBoundsAdjustedToAspectRatio, normalBounds);
+ }
+
+ @Test
public void getEntryDestinationBounds_returnBoundsMatchesAspectRatio() {
final float[] aspectRatios = new float[] {
(MIN_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2,
@@ -121,8 +244,7 @@
for (float aspectRatio : aspectRatios) {
mPipBoundsState.setAspectRatio(aspectRatio);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
- final float actualAspectRatio =
- destinationBounds.width() / (destinationBounds.height() * 1f);
+ final float actualAspectRatio = getRectAspectRatio(destinationBounds);
assertEquals("Destination bounds matches the given aspect ratio",
aspectRatio, actualAspectRatio, ASPECT_RATIO_ERROR_MARGIN);
}
@@ -274,6 +396,22 @@
assertBoundsInclusionWithMargin("useDefaultBounds", defaultBounds, actualBounds);
}
+ private void overrideDefaultAspectRatio(float aspectRatio) {
+ final TestableResources res = mContext.getOrCreateTestableResources();
+ res.addOverride(
+ com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio,
+ aspectRatio);
+ mPipBoundsAlgorithm.onConfigurationChanged(mContext);
+ }
+
+ private void overrideDefaultStackGravity(int stackGravity) {
+ final TestableResources res = mContext.getOrCreateTestableResources();
+ res.addOverride(
+ com.android.internal.R.integer.config_defaultPictureInPictureGravity,
+ stackGravity);
+ mPipBoundsAlgorithm.onConfigurationChanged(mContext);
+ }
+
private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
final Rect expectedWithMargin = new Rect(expected);
expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
@@ -282,4 +420,8 @@
+ " with error margin " + ROUNDING_ERROR_MARGIN,
expectedWithMargin.contains(actual));
}
+
+ private static float getRectAspectRatio(Rect rect) {
+ return rect.width() / (rect.height() * 1f);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 59e10c1..4bcca06 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -135,4 +135,38 @@
verify(callback, never()).accept(true, 100);
}
+
+ @Test
+ public void testSetOverrideMinSize_changed_callbackInvoked() {
+ final Runnable callback = mock(Runnable.class);
+ mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+ mPipBoundsState.setOnMinimalSizeChangeCallback(callback);
+
+ mPipBoundsState.setOverrideMinSize(new Size(10, 10));
+
+ verify(callback).run();
+ }
+
+ @Test
+ public void testSetOverrideMinSize_notChanged_callbackNotInvoked() {
+ final Runnable callback = mock(Runnable.class);
+ mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+ mPipBoundsState.setOnMinimalSizeChangeCallback(callback);
+
+ mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+
+ verify(callback, never()).run();
+ }
+
+ @Test
+ public void testGetOverrideMinEdgeSize() {
+ mPipBoundsState.setOverrideMinSize(null);
+ assertEquals(0, mPipBoundsState.getOverrideMinEdgeSize());
+
+ mPipBoundsState.setOverrideMinSize(new Size(5, 10));
+ assertEquals(5, mPipBoundsState.getOverrideMinEdgeSize());
+
+ mPipBoundsState.setOverrideMinSize(new Size(15, 10));
+ assertEquals(10, mPipBoundsState.getOverrideMinEdgeSize());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
new file mode 100644
index 0000000..dcee2e1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link PipSnapAlgorithm}. **/
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class PipSnapAlgorithmTest extends ShellTestCase {
+ private static final int DEFAULT_STASH_OFFSET = 32;
+ private static final Rect DISPLAY_BOUNDS = new Rect(0, 0, 2000, 2000);
+ private static final Rect STACK_BOUNDS_CENTERED = new Rect(900, 900, 1100, 1100);
+ private static final Rect MOVEMENT_BOUNDS = new Rect(0, 0,
+ DISPLAY_BOUNDS.width() - STACK_BOUNDS_CENTERED.width(),
+ DISPLAY_BOUNDS.width() - STACK_BOUNDS_CENTERED.width());
+
+ private PipSnapAlgorithm mPipSnapAlgorithm;
+
+ @Before
+ public void setUp() {
+ mPipSnapAlgorithm = new PipSnapAlgorithm();
+ }
+
+ @Test
+ public void testApplySnapFraction_topEdge() {
+ final float snapFraction = 0.25f;
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+ mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction);
+
+ assertEquals(MOVEMENT_BOUNDS.width() / 4, bounds.left);
+ assertEquals(MOVEMENT_BOUNDS.top, bounds.top);
+ }
+
+ @Test
+ public void testApplySnapFraction_rightEdge() {
+ final float snapFraction = 1.5f;
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+ mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction);
+
+ assertEquals(MOVEMENT_BOUNDS.right, bounds.left);
+ assertEquals(MOVEMENT_BOUNDS.height() / 2, bounds.top);
+ }
+
+ @Test
+ public void testApplySnapFraction_bottomEdge() {
+ final float snapFraction = 2.25f;
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+ mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction);
+
+ assertEquals((int) (MOVEMENT_BOUNDS.width() * 0.75f), bounds.left);
+ assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top);
+ }
+
+ @Test
+ public void testApplySnapFraction_leftEdge() {
+ final float snapFraction = 3.75f;
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+ mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction);
+
+ assertEquals(MOVEMENT_BOUNDS.left, bounds.left);
+ assertEquals((int) (MOVEMENT_BOUNDS.height() * 0.25f), bounds.top);
+ }
+
+ @Test
+ public void testApplySnapFraction_notStashed_isNotOffBounds() {
+ final float snapFraction = 2f;
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+ mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction,
+ PipBoundsState.STASH_TYPE_NONE, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS);
+
+ assertEquals(MOVEMENT_BOUNDS.right, bounds.left);
+ assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top);
+ }
+
+ @Test
+ public void testApplySnapFraction_stashedLeft() {
+ final float snapFraction = 3f;
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+ mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction,
+ PipBoundsState.STASH_TYPE_LEFT, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS);
+
+ final int offBoundsWidth = bounds.width() - DEFAULT_STASH_OFFSET;
+ assertEquals(MOVEMENT_BOUNDS.left - offBoundsWidth, bounds.left);
+ assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top);
+ }
+
+ @Test
+ public void testApplySnapFraction_stashedRight() {
+ final float snapFraction = 2f;
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+ mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction,
+ PipBoundsState.STASH_TYPE_RIGHT, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS);
+
+ assertEquals(DISPLAY_BOUNDS.right - DEFAULT_STASH_OFFSET, bounds.left);
+ assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top);
+ }
+
+ @Test
+ public void testSnapRectToClosestEdge_rightEdge() {
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+ // Move the centered rect slightly to the right side.
+ bounds.offset(10, 0);
+
+ mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds,
+ PipBoundsState.STASH_TYPE_NONE);
+
+ assertEquals(MOVEMENT_BOUNDS.right, bounds.left);
+ }
+
+ @Test
+ public void testSnapRectToClosestEdge_leftEdge() {
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+ // Move the centered rect slightly to the left side.
+ bounds.offset(-10, 0);
+
+ mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds,
+ PipBoundsState.STASH_TYPE_NONE);
+
+ assertEquals(MOVEMENT_BOUNDS.left, bounds.left);
+ }
+
+ @Test
+ public void testSnapRectToClosestEdge_topEdge() {
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+ // Move the centered rect slightly to the top half.
+ bounds.offset(0, -10);
+
+ mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds,
+ PipBoundsState.STASH_TYPE_NONE);
+
+ assertEquals(MOVEMENT_BOUNDS.top, bounds.top);
+ }
+
+ @Test
+ public void testSnapRectToClosestEdge_bottomEdge() {
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+ // Move the centered rect slightly to the bottom half.
+ bounds.offset(0, 10);
+
+ mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds,
+ PipBoundsState.STASH_TYPE_NONE);
+
+ assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top);
+ }
+
+ @Test
+ public void testSnapRectToClosestEdge_stashed_unStahesBounds() {
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+ // Stash it on the left side.
+ mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, 3.5f,
+ PipBoundsState.STASH_TYPE_LEFT, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS);
+
+ mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds,
+ PipBoundsState.STASH_TYPE_LEFT);
+
+ assertEquals(MOVEMENT_BOUNDS.left, bounds.left);
+ }
+
+ @Test
+ public void testGetSnapFraction_leftEdge() {
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+ // Move it slightly to the left side.
+ bounds.offset(-10, 0);
+
+ final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS);
+
+ assertEquals(3.5f, snapFraction, 0.1f);
+ }
+
+ @Test
+ public void testGetSnapFraction_rightEdge() {
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+ // Move it slightly to the right side.
+ bounds.offset(10, 0);
+
+ final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS);
+
+ assertEquals(1.5f, snapFraction, 0.1f);
+ }
+
+ @Test
+ public void testGetSnapFraction_topEdge() {
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+ // Move it slightly to the top half.
+ bounds.offset(0, -10);
+
+ final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS);
+
+ assertEquals(0.5f, snapFraction, 0.1f);
+ }
+
+ @Test
+ public void testGetSnapFraction_bottomEdge() {
+ final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+ // Move it slightly to the bottom half.
+ bounds.offset(0, 10);
+
+ final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS);
+
+ assertEquals(2.5f, snapFraction, 0.1f);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index b25c74d..abbc681 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -91,7 +91,7 @@
mPipBoundsState = new PipBoundsState(mContext);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState);
mPipSnapAlgorithm = mPipBoundsAlgorithm.getSnapAlgorithm();
- mPipSnapAlgorithm = new PipSnapAlgorithm(mContext);
+ mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipTouchHandler = new PipTouchHandler(mContext, mPipMenuActivityController,
mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
mFloatingContentCoordinator, mPipUiEventLogger);
@@ -115,7 +115,8 @@
@Test
public void updateMovementBounds_minBounds() {
Rect expectedMinMovementBounds = new Rect();
- mPipSnapAlgorithm.getMovementBounds(mMinBounds, mInsetBounds, expectedMinMovementBounds, 0);
+ mPipBoundsAlgorithm.getMovementBounds(mMinBounds, mInsetBounds, expectedMinMovementBounds,
+ 0);
mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds,
mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
@@ -129,12 +130,13 @@
public void updateMovementBounds_maxBounds() {
Point displaySize = new Point();
mContext.getDisplay().getRealSize(displaySize);
- Size maxSize = mPipSnapAlgorithm.getSizeForAspectRatio(1,
+ Size maxSize = mPipBoundsAlgorithm.getSizeForAspectRatio(1,
mContext.getResources().getDimensionPixelSize(
R.dimen.pip_expanded_shortest_edge_size), displaySize.x, displaySize.y);
Rect maxBounds = new Rect(0, 0, maxSize.getWidth(), maxSize.getHeight());
Rect expectedMaxMovementBounds = new Rect();
- mPipSnapAlgorithm.getMovementBounds(maxBounds, mInsetBounds, expectedMaxMovementBounds, 0);
+ mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, expectedMaxMovementBounds,
+ 0);
mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds,
mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 6d626ad..a2964d6 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -94,13 +94,27 @@
}
static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
- int weight, int italic) {
+ jlong fallbackPtr, int weight, int italic) {
ScopedLongArrayRO families(env, familyArray);
std::vector<std::shared_ptr<minikin::FontFamily>> familyVec;
- familyVec.reserve(families.size());
- for (size_t i = 0; i < families.size(); i++) {
- FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
- familyVec.emplace_back(family->family);
+ Typeface* typeface = (fallbackPtr == 0) ? nullptr : toTypeface(fallbackPtr);
+ if (typeface != nullptr) {
+ const std::vector<std::shared_ptr<minikin::FontFamily>>& fallbackFamilies =
+ toTypeface(fallbackPtr)->fFontCollection->getFamilies();
+ familyVec.reserve(families.size() + fallbackFamilies.size());
+ for (size_t i = 0; i < families.size(); i++) {
+ FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
+ familyVec.emplace_back(family->family);
+ }
+ for (size_t i = 0; i < fallbackFamilies.size(); i++) {
+ familyVec.emplace_back(fallbackFamilies[i]);
+ }
+ } else {
+ familyVec.reserve(families.size());
+ for (size_t i = 0; i < families.size(); i++) {
+ FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
+ familyVec.emplace_back(family->family);
+ }
}
return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic));
}
@@ -250,7 +264,7 @@
{ "nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc },
{ "nativeGetStyle", "(J)I", (void*)Typeface_getStyle },
{ "nativeGetWeight", "(J)I", (void*)Typeface_getWeight },
- { "nativeCreateFromArray", "([JII)J",
+ { "nativeCreateFromArray", "([JJII)J",
(void*)Typeface_createFromArray },
{ "nativeSetDefault", "(J)V", (void*)Typeface_setDefault },
{ "nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes },
diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index e023aa1..9dbdede 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -150,8 +150,8 @@
return mListenerId;
}
- /** Returns true if this represents a system identity. */
- public boolean isSystem() {
+ /** Returns true if this represents a system server identity. */
+ public boolean isSystemServer() {
return mUid == Process.SYSTEM_UID;
}
diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java
index 5f79dc5..2217eb3 100644
--- a/media/java/android/media/tv/tuner/Descrambler.java
+++ b/media/java/android/media/tv/tuner/Descrambler.java
@@ -22,6 +22,7 @@
import android.annotation.SystemApi;
import android.media.tv.tuner.Tuner.Result;
import android.media.tv.tuner.filter.Filter;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -115,9 +116,9 @@
* keys for different purposes.
*
* @param keyToken the token to be used to link the key slot. Use {@link Tuner.INVALID_KEYTOKEN}
- * to remove the to remove the current key from descrambler. If the current keyToken
- * comes from MediaCas session, use {@link Tuner.INVALID_KEYTOKEN} to remove current key
- * before close MediaCas session.
+ * to remove the current key from descrambler. If the current keyToken comes from a
+ * MediaCas session, use {@link Tuner.INVALID_KEYTOKEN} to remove current key before
+ * closing the MediaCas session.
* @return result status of the operation.
*/
@Result
@@ -125,6 +126,9 @@
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
Objects.requireNonNull(keyToken, "key token must not be null");
+ if (!isValidKeyToken(keyToken)) {
+ return Tuner.RESULT_INVALID_ARGUMENT;
+ }
return nativeSetKeyToken(keyToken);
}
}
@@ -147,4 +151,17 @@
}
}
+ private boolean isValidKeyToken(byte[] keyToken) {
+ if (keyToken.length == 0 || keyToken.length > 16) {
+ Log.d(TAG, "Invalid key token size: " + (keyToken.length * 8) + " bit.");
+ return false;
+ }
+ for (int i = 0; i < keyToken.length; i++) {
+ if (keyToken[i] < 0) {
+ Log.d(TAG, "Invalid key token.");
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 27b33ac..a9da772 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -878,11 +878,14 @@
}
/**
- * Connects Conditional Access Modules (CAM) through Common Interface (CI)
+ * Connects Conditional Access Modules (CAM) through Common Interface (CI).
*
* <p>The demux uses the output from the frontend as the input by default, and must change to
* use the output from CI-CAM as the input after this call.
*
+ * <p> Note that this API is used to connect the CI-CAM to the Demux module while
+ * {@link connectFrontendToCiCam(int)} is used to connect CI-CAM to the Frontend module.
+ *
* @param ciCamId specify CI-CAM Id to connect.
* @return result status of the operation.
*/
@@ -895,23 +898,30 @@
}
/**
- * Link Conditional Access Modules (CAM) Frontend to support Common Interface (CI) by-pass mode.
+ * Connect Conditional Access Modules (CAM) Frontend to support Common Interface (CI)
+ * by-pass mode.
*
* <p>It is used by the client to link CI-CAM to a Frontend. CI by-pass mode requires that
* the CICAM also receives the TS concurrently from the frontend when the Demux is receiving
* the TS directly from the frontend.
*
- * <p>Use {@link #unlinkFrontendToCicam(int)} to disconnect.
+ * <p> Note that this API is used to connect the CI-CAM to the Frontend module while
+ * {@link connectCiCam(int)} is used to connect CI-CAM to the Demux module.
+ *
+ * <p>Use {@link #disconnectFrontendToCiCam(int)} to disconnect.
*
* <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
* no-op and return {@link INVALID_LTS_ID}. Use {@link TunerVersionChecker.getTunerVersion()} to
* check the version.
*
- * @param ciCamId specify CI-CAM Id to link.
+ * @param ciCamId specify CI-CAM Id, which is the id of the Conditional Access Modules (CAM)
+ * Common Interface (CI), to link.
* @return Local transport stream id when connection is successfully established. Failed
- * operation returns {@link INVALID_LTS_ID}.
+ * operation returns {@link INVALID_LTS_ID} while unsupported version also returns
+ * {@link INVALID_LTS_ID}. Check the current HAL version using
+ * {@link TunerVersionChecker.getTunerVersion()}.
*/
- public int linkFrontendToCiCam(int ciCamId) {
+ public int connectFrontendToCiCam(int ciCamId) {
if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
"linkFrontendToCiCam")) {
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
@@ -922,10 +932,13 @@
}
/**
- * Disconnects Conditional Access Modules (CAM)
+ * Disconnects Conditional Access Modules (CAM).
*
* <p>The demux will use the output from the frontend as the input after this call.
*
+ * <p> Note that this API is used to disconnect the CI-CAM to the Demux module while
+ * {@link disconnectFrontendToCiCam(int)} is used to disconnect CI-CAM to the Frontend module.
+ *
* @return result status of the operation.
*/
@Result
@@ -937,18 +950,23 @@
}
/**
- * Unlink Conditional Access Modules (CAM) Frontend.
+ * Disconnect Conditional Access Modules (CAM) Frontend.
*
* <p>It is used by the client to unlink CI-CAM to a Frontend.
*
+ * <p> Note that this API is used to disconnect the CI-CAM to the Demux module while
+ * {@link disconnectCiCam(int)} is used to disconnect CI-CAM to the Frontend module.
+ *
* <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
* no-op. Use {@link TunerVersionChecker.getTunerVersion()} to check the version.
*
- * @param ciCamId specify CI-CAM Id to unlink.
- * @return result status of the operation.
+ * @param ciCamId specify CI-CAM Id, which is the id of the Conditional Access Modules (CAM)
+ * Common Interface (CI), to disconnect.
+ * @return result status of the operation. Unsupported version would return
+ * {@link RESULT_UNAVAILABLE}
*/
@Result
- public int unlinkFrontendToCiCam(int ciCamId) {
+ public int disconnectFrontendToCiCam(int ciCamId) {
if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
"unlinkFrontendToCiCam")) {
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 82cc78d..451f54c 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -186,7 +186,7 @@
value = {SCRAMBLING_STATUS_UNKNOWN, SCRAMBLING_STATUS_NOT_SCRAMBLED,
SCRAMBLING_STATUS_SCRAMBLED})
@Retention(RetentionPolicy.SOURCE)
- public @interface ScramblingStatusMask {}
+ public @interface ScramblingStatus {}
/**
* Content’s scrambling status is unknown
@@ -204,6 +204,23 @@
public static final int SCRAMBLING_STATUS_SCRAMBLED =
android.hardware.tv.tuner.V1_1.Constants.ScramblingStatus.SCRAMBLED;
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MONITOR_EVENT_",
+ value = {MONITOR_EVENT_SCRAMBLING_STATUS, MONITOR_EVENT_IP_CID_CHANGE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MonitorEventTypeMask {}
+
+ /**
+ * Monitor scrambling status change.
+ */
+ public static final int MONITOR_EVENT_SCRAMBLING_STATUS =
+ android.hardware.tv.tuner.V1_1.Constants.DemuxFilterMonitorEventType.SCRAMBLING_STATUS;
+ /**
+ * Monitor ip cid change.
+ */
+ public static final int MONITOR_EVENT_IP_CID_CHANGE =
+ android.hardware.tv.tuner.V1_1.Constants.DemuxFilterMonitorEventType.IP_CID_CHANGE;
private static final String TAG = "Filter";
@@ -222,7 +239,7 @@
int type, int subType, FilterConfiguration settings);
private native int nativeGetId();
private native long nativeGetId64Bit();
- private native int nativeconfigureScramblingEvent(int scramblingStatusMask);
+ private native int nativeConfigureMonitorEvent(int monitorEventTypesMask);
private native int nativeSetDataSource(Filter source);
private native int nativeStartFilter();
private native int nativeStopFilter();
@@ -306,34 +323,40 @@
}
/**
- * Configure the Filter to monitor specific Scrambling Status through
- * {@link ScramblingStatusEvent}.
+ * Configure the Filter to monitor scrambling status and ip cid change. Set corresponding bit of
+ * {@link MonitorEventTypeMask} to monitor the change. Reset to stop monitoring.
*
* <p>{@link ScramblingStatusEvent} should be sent at the following two scenarios:
- *
* <ul>
- * <li>When this method is called, the first detected scrambling status should be sent.
- * <li>When the filter transits into the monitored statuses configured in
- * {@code scramblingStatusMask}, event should be sent.
+ * <li>When this method is called with {@link MONITOR_EVENT_SCRAMBLING_STATUS}, the first
+ * detected scrambling status should be sent.
+ * <li>When the Scrambling status transits into different status, event should be sent.
+ * <ul/>
+ *
+ * <p>{@link IpCidChangeEvent} should be sent at the following two scenarios:
+ * <ul>
+ * <li>When this method is called with {@link MONITOR_EVENT_IP_CID_CHANGE}, the first detected
+ * CID for the IP should be sent.
+ * <li>When the CID is changed to different value for the IP filter, event should be sent.
* <ul/>
*
* <p>This configuration is only supported in Tuner 1.1 or higher version. Unsupported version
* will cause no-op. Use {@link TunerVersionChecker.getTunerVersion()} to get the version
* information.
*
- * @param scramblingStatusMask Scrambling Statuses to be monitored. Set corresponding bit to
- * monitor it. Reset to stop monitoring.
+ * @param monitorEventTypesMask Types of event to be monitored. Set corresponding bit to
+ * monitor it. Reset to stop monitoring.
* @return result status of the operation.
*/
@Result
- public int configureScramblingStatusEvent(@ScramblingStatusMask int scramblingStatusMask) {
+ public int configureMonitorEvent(@MonitorEventTypeMask int monitorEventTypesMask) {
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
- TunerVersionChecker.TUNER_VERSION_1_1, "configureScramblingStatusEvent")) {
+ TunerVersionChecker.TUNER_VERSION_1_1, "configureMonitorEvent")) {
return Tuner.RESULT_UNAVAILABLE;
}
- return nativeconfigureScramblingEvent(scramblingStatusMask);
+ return nativeConfigureMonitorEvent(monitorEventTypesMask);
}
}
diff --git a/media/java/android/media/tv/tuner/filter/IpCidChangeEvent.java b/media/java/android/media/tv/tuner/filter/IpCidChangeEvent.java
new file mode 100644
index 0000000..c0043f3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/IpCidChangeEvent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.annotation.SystemApi;
+
+/**
+ * Ip Cid Change event sent from {@link Filter} objects new ip cid.
+ *
+ * <p>This event is only sent in Tuner 1.1 or higher version. Use
+ * {@link TunerVersionChecker.getTunerVersion()} to get the version information.
+ *
+ * @hide
+ */
+@SystemApi
+public final class IpCidChangeEvent extends FilterEvent {
+ private final int mCid;
+
+ private IpCidChangeEvent(int cid) {
+ mCid = cid;
+ }
+
+ /**
+ * Gets ip cid.
+ *
+ * <p>This event is only sent in Tuner 1.1 or higher version. Use
+ * {@link TunerVersionChecker.getTunerVersion()} to get the version information.
+ */
+ public int getIpCid() {
+ return mCid;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
index 52ce208..91992af 100644
--- a/media/java/android/media/tv/tuner/filter/RecordSettings.java
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -51,7 +51,7 @@
public @interface TsIndexMask {}
/**
- * Invalid TS index.
+ * Invalid Transport Stream (TS) index.
*/
public static final int TS_INDEX_INVALID = 0;
/**
diff --git a/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java b/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java
index a78b96e..fef5396 100644
--- a/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java
+++ b/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java
@@ -30,18 +30,17 @@
public final class ScramblingStatusEvent extends FilterEvent {
private final int mScramblingStatus;
- private ScramblingStatusEvent(@Filter.ScramblingStatusMask int scramblingStatus) {
+ private ScramblingStatusEvent(@Filter.ScramblingStatus int scramblingStatus) {
mScramblingStatus = scramblingStatus;
}
/**
* Gets Scrambling Status Type.
*
- * <p>This event field is only sent in Tuner 1.1 or higher version. Unsupported version returns
- * default value 0. Use {@link TunerVersionChecker.getTunerVersion()} to get the version
- * information.
+ * <p>This event field is only sent in Tuner 1.1 or higher version. Use
+ * {@link TunerVersionChecker.getTunerVersion()} to get the version information.
*/
- @Filter.ScramblingStatusMask
+ @Filter.ScramblingStatus
public int getScramblingStatus() {
return mScramblingStatus;
}
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index ed56b43..798bf6e 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -19,8 +19,7 @@
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
-import android.content.ContentUris;
-import android.content.ContentValues;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,7 +31,6 @@
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.BatteryManager;
-import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.storage.StorageVolume;
import android.provider.MediaStore;
@@ -103,8 +101,6 @@
private MtpStorageManager mManager;
private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
- private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
- private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
private static final String NO_MEDIA = ".nomedia";
static {
@@ -431,7 +427,7 @@
}
// Add the new file to MediaProvider
if (succeeded) {
- MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
+ updateMediaStore(mContext, obj.getPath().toFile());
}
}
@@ -580,32 +576,8 @@
return MtpConstants.RESPONSE_GENERAL_ERROR;
}
- // finally update MediaProvider
- ContentValues values = new ContentValues();
- values.put(Files.FileColumns.DATA, newPath.toString());
- String[] whereArgs = new String[]{oldPath.toString()};
- try {
- // note - we are relying on a special case in MediaProvider.update() to update
- // the paths for all children in the case where this is a directory.
- final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
- mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in mMediaProvider.update", e);
- }
-
- // check if nomedia status changed
- if (obj.isDir()) {
- // for directories, check if renamed from something hidden to something non-hidden
- if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
- MediaStore.scanFile(mContext.getContentResolver(), newPath.toFile());
- }
- } else {
- // for files, check if renamed from .nomedia to something else
- if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
- && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
- MediaStore.scanFile(mContext.getContentResolver(), newPath.getParent().toFile());
- }
- }
+ updateMediaStore(mContext, oldPath.toFile());
+ updateMediaStore(mContext, newPath.toFile());
return MtpConstants.RESPONSE_OK;
}
@@ -635,48 +607,15 @@
Log.e(TAG, "Failed to end move object");
return;
}
-
obj = mManager.getObject(objId);
if (!success || obj == null)
return;
- // Get parent info from MediaProvider, since the id is different from MTP's
- ContentValues values = new ContentValues();
+
Path path = newParentObj.getPath().resolve(name);
Path oldPath = oldParentObj.getPath().resolve(name);
- values.put(Files.FileColumns.DATA, path.toString());
- if (obj.getParent().isRoot()) {
- values.put(Files.FileColumns.PARENT, 0);
- } else {
- int parentId = findInMedia(newParentObj, path.getParent());
- if (parentId != -1) {
- values.put(Files.FileColumns.PARENT, parentId);
- } else {
- // The new parent isn't in MediaProvider, so delete the object instead
- deleteFromMedia(obj, oldPath, obj.isDir());
- return;
- }
- }
- // update MediaProvider
- Cursor c = null;
- String[] whereArgs = new String[]{oldPath.toString()};
- try {
- int parentId = -1;
- if (!oldParentObj.isRoot()) {
- parentId = findInMedia(oldParentObj, oldPath.getParent());
- }
- if (oldParentObj.isRoot() || parentId != -1) {
- // Old parent exists in MediaProvider - perform a move
- // note - we are relying on a special case in MediaProvider.update() to update
- // the paths for all children in the case where this is a directory.
- final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
- mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
- } else {
- // Old parent doesn't exist - add the object
- MediaStore.scanFile(mContext.getContentResolver(), path.toFile());
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in mMediaProvider.update", e);
- }
+
+ updateMediaStore(mContext, oldPath.toFile());
+ updateMediaStore(mContext, path.toFile());
}
@VisibleForNative
@@ -699,7 +638,19 @@
if (!success) {
return;
}
- MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
+
+ updateMediaStore(mContext, obj.getPath().toFile());
+ }
+
+ private static void updateMediaStore(@NonNull Context context, @NonNull File file) {
+ final ContentResolver resolver = context.getContentResolver();
+ // For file, check whether the file name is .nomedia or not.
+ // If yes, scan the parent directory to update all files in the directory.
+ if (!file.isDirectory() && file.getName().toLowerCase(Locale.ROOT).endsWith(NO_MEDIA)) {
+ MediaStore.scanFile(resolver, file.getParentFile());
+ } else {
+ MediaStore.scanFile(resolver, file);
+ }
}
@VisibleForNative
@@ -928,26 +879,6 @@
deleteFromMedia(obj, obj.getPath(), obj.isDir());
}
- private int findInMedia(MtpStorageManager.MtpObject obj, Path path) {
- final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
-
- int ret = -1;
- Cursor c = null;
- try {
- c = mMediaProvider.query(objectsUri, ID_PROJECTION, PATH_WHERE,
- new String[]{path.toString()}, null, null);
- if (c != null && c.moveToNext()) {
- ret = c.getInt(0);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error finding " + path + " in MediaProvider");
- } finally {
- if (c != null)
- c.close();
- }
- return ret;
- }
-
private void deleteFromMedia(MtpStorageManager.MtpObject obj, Path path, boolean isDir) {
final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
try {
@@ -963,13 +894,10 @@
}
String[] whereArgs = new String[]{path.toString()};
- if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
- if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
- MediaStore.scanFile(mContext.getContentResolver(), path.getParent().toFile());
- }
- } else {
- Log.i(TAG, "Mediaprovider didn't delete " + path);
+ if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) == 0) {
+ Log.i(TAG, "MediaProvider didn't delete " + path);
}
+ updateMediaStore(mContext, path.toFile());
} catch (Exception e) {
Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index b255a48..72dc32e 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -604,11 +604,16 @@
jlong byteNumber = static_cast<jlong>(tsRecordEvent.byteNumber);
- jlong pts = (eventsExt.size() > i) ? static_cast<jlong>(eventsExt[i].tsRecord().pts)
- : static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
- jlong firstMbInSlice = (eventsExt.size() > i)
- ? static_cast<jint>(eventsExt[i].tsRecord().firstMbInSlice)
- : static_cast<jint>(Constant::INVALID_FIRST_MACROBLOCK_IN_SLICE);
+ jlong pts;
+ jlong firstMbInSlice;
+ if (eventsExt.size() > i && eventsExt[i].getDiscriminator() ==
+ DemuxFilterEventExt::Event::hidl_discriminator::tsRecord) {
+ pts = static_cast<jlong>(eventsExt[i].tsRecord().pts);
+ firstMbInSlice = static_cast<jint>(eventsExt[i].tsRecord().firstMbInSlice);
+ } else {
+ pts = static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
+ firstMbInSlice = static_cast<jint>(Constant::INVALID_FIRST_MACROBLOCK_IN_SLICE);
+ }
jobject obj =
env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber,
@@ -632,16 +637,25 @@
jint scHevcIndexMask = static_cast<jint>(mmtpRecordEvent.scHevcIndexMask);
jlong byteNumber = static_cast<jlong>(mmtpRecordEvent.byteNumber);
- jint mpuSequenceNumber = (eventsExt.size() > i)
- ? static_cast<jint>(eventsExt[i].mmtpRecord().mpuSequenceNumber)
- : static_cast<jint>(Constant::INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM);
- jlong pts = (eventsExt.size() > i) ? static_cast<jlong>(eventsExt[i].mmtpRecord().pts)
- : static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
- jlong firstMbInSlice = (eventsExt.size() > i)
- ? static_cast<jint>(eventsExt[i].mmtpRecord().firstMbInSlice)
- : static_cast<jint>(Constant::INVALID_FIRST_MACROBLOCK_IN_SLICE);
- jlong tsIndexMask = (eventsExt.size() > i)
- ? static_cast<jint>(eventsExt[i].mmtpRecord().tsIndexMask) : 0;
+
+ jint mpuSequenceNumber;
+ jlong pts;
+ jlong firstMbInSlice;
+ jlong tsIndexMask;
+
+ if (eventsExt.size() > i && eventsExt[i].getDiscriminator() ==
+ DemuxFilterEventExt::Event::hidl_discriminator::mmtpRecord) {
+ mpuSequenceNumber = static_cast<jint>(eventsExt[i].mmtpRecord().mpuSequenceNumber);
+ pts = static_cast<jlong>(eventsExt[i].mmtpRecord().pts);
+ firstMbInSlice = static_cast<jint>(eventsExt[i].mmtpRecord().firstMbInSlice);
+ tsIndexMask = static_cast<jint>(eventsExt[i].mmtpRecord().tsIndexMask);
+ } else {
+ mpuSequenceNumber =
+ static_cast<jint>(Constant::INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM);
+ pts = static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
+ firstMbInSlice = static_cast<jint>(Constant::INVALID_FIRST_MACROBLOCK_IN_SLICE);
+ tsIndexMask = 0;
+ }
jobject obj =
env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
@@ -720,11 +734,21 @@
jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/ScramblingStatusEvent");
jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
- for (int i = 0; i < eventsExt.size(); i++) {
- auto scramblingStatus = eventsExt[i].scramblingStatus();
- jobject obj = env->NewObject(eventClazz, eventInit, static_cast<jint>(scramblingStatus));
- env->SetObjectArrayElement(arr, i, obj);
- }
+ auto scramblingStatus = eventsExt[0].monitorEvent().scramblingStatus();
+ jobject obj = env->NewObject(eventClazz, eventInit, static_cast<jint>(scramblingStatus));
+ env->SetObjectArrayElement(arr, 0, obj);
+ return arr;
+}
+
+jobjectArray FilterCallback::getIpCidChangeEvent(
+ jobjectArray& arr, const std::vector<DemuxFilterEventExt::Event>& eventsExt) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/IpCidChangeEvent");
+ jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
+
+ auto cid = eventsExt[0].monitorEvent().cid();
+ jobject obj = env->NewObject(eventClazz, eventInit, static_cast<jint>(cid));
+ env->SetObjectArrayElement(arr, 0, obj);
return arr;
}
@@ -734,11 +758,9 @@
jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/RestartEvent");
jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
- for (int i = 0; i < eventsExt.size(); i++) {
- auto startId = eventsExt[i].startId();
- jobject obj = env->NewObject(eventClazz, eventInit, static_cast<jint>(startId));
- env->SetObjectArrayElement(arr, i, obj);
- }
+ auto startId = eventsExt[0].startId();
+ jobject obj = env->NewObject(eventClazz, eventInit, static_cast<jint>(startId));
+ env->SetObjectArrayElement(arr, 0, obj);
return arr;
}
@@ -747,17 +769,31 @@
ALOGD("FilterCallback::onFilterEvent_1_1");
JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jobjectArray array;
std::vector<DemuxFilterEvent::Event> events = filterEvent.events;
std::vector<DemuxFilterEventExt::Event> eventsExt = filterEventExt.events;
jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/FilterEvent");
- jobjectArray array = env->NewObjectArray(events.size(), eventClazz, NULL);
if (events.empty() && !eventsExt.empty()) {
+ // Monitor event should be sent with one DemuxFilterMonitorEvent in DemuxFilterEventExt.
+ array = env->NewObjectArray(1, eventClazz, NULL);
auto eventExt = eventsExt[0];
switch (eventExt.getDiscriminator()) {
- case DemuxFilterEventExt::Event::hidl_discriminator::scramblingStatus: {
- array = getScramblingStatusEvent(array, eventsExt);
+ case DemuxFilterEventExt::Event::hidl_discriminator::monitorEvent: {
+ switch (eventExt.monitorEvent().getDiscriminator()) {
+ case DemuxFilterMonitorEvent::hidl_discriminator::scramblingStatus: {
+ array = getScramblingStatusEvent(array, eventsExt);
+ break;
+ }
+ case DemuxFilterMonitorEvent::hidl_discriminator::cid: {
+ array = getIpCidChangeEvent(array, eventsExt);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
break;
}
case DemuxFilterEventExt::Event::hidl_discriminator::startId: {
@@ -771,6 +807,7 @@
}
if (!events.empty()) {
+ array = env->NewObjectArray(events.size(), eventClazz, NULL);
auto event = events[0];
switch (event.getDiscriminator()) {
case DemuxFilterEvent::Event::hidl_discriminator::media: {
@@ -4069,8 +4106,8 @@
::android::hardware::tv::tuner::V1_1::Constant64Bit::INVALID_FILTER_ID_64BIT);
}
-static jint android_media_tv_Tuner_configure_scrambling_status_event(
- JNIEnv* env, jobject filter, int scramblingStatusMask) {
+static jint android_media_tv_Tuner_configure_monitor_event(
+ JNIEnv* env, jobject filter, int monitorEventType) {
sp<IFilter> iFilterSp = getFilter(env, filter)->getIFilter();
if (iFilterSp == NULL) {
ALOGD("Failed to configure scrambling event: filter not found");
@@ -4082,7 +4119,7 @@
Result res;
if (iFilterSp_1_1 != NULL) {
- res = iFilterSp_1_1->configureScramblingEvent(scramblingStatusMask);
+ res = iFilterSp_1_1->configureMonitorEvent(monitorEventType);
} else {
ALOGW("configureScramblingEvent is not supported with the current HAL implementation.");
return (jint) Result::INVALID_STATE;
@@ -4762,8 +4799,8 @@
{ "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id },
{ "nativeGetId64Bit", "()J",
(void *)android_media_tv_Tuner_get_filter_64bit_id },
- { "nativeconfigureScramblingEvent", "(I)I",
- (void *)android_media_tv_Tuner_configure_scrambling_status_event },
+ { "nativeConfigureMonitorEvent", "(I)I",
+ (void *)android_media_tv_Tuner_configure_monitor_event },
{ "nativeSetDataSource", "(Landroid/media/tv/tuner/filter/Filter;)I",
(void *)android_media_tv_Tuner_set_filter_data_source },
{ "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index ebb95f4..e79b5c2 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -44,7 +44,6 @@
using ::android::hardware::hidl_vec;
using ::android::hardware::kSynchronizedReadWrite;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterEvent;
-using ::android::hardware::tv::tuner::V1_1::DemuxFilterEventExt;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterStatus;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterType;
using ::android::hardware::tv::tuner::V1_0::DemuxPid;
@@ -73,6 +72,8 @@
using ::android::hardware::tv::tuner::V1_0::PlaybackStatus;
using ::android::hardware::tv::tuner::V1_0::RecordStatus;
using ::android::hardware::tv::tuner::V1_0::Result;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterEventExt;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterMonitorEvent;
using ::android::hardware::tv::tuner::V1_1::FrontendScanMessageExt1_1;
using ::android::hardware::tv::tuner::V1_1::FrontendScanMessageTypeExt1_1;
using ::android::hardware::tv::tuner::V1_1::IFrontendCallback;
@@ -188,6 +189,8 @@
jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
jobjectArray getScramblingStatusEvent(
jobjectArray& arr, const std::vector<DemuxFilterEventExt::Event>& eventsExt);
+ jobjectArray getIpCidChangeEvent(
+ jobjectArray& arr, const std::vector<DemuxFilterEventExt::Event>& eventsExt);
jobjectArray getRestartEvent(
jobjectArray& arr, const std::vector<DemuxFilterEventExt::Event>& eventsExt);
};
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index d134b37..eca67bd 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -151,6 +151,7 @@
AHardwareBuffer_allocate; # introduced=26
AHardwareBuffer_describe; # introduced=26
AHardwareBuffer_fromHardwareBuffer; # introduced=26
+ AHardwareBuffer_getId; # introduced=31
AHardwareBuffer_getNativeHandle; # introduced=26
AHardwareBuffer_isSupported; # introduced=29
AHardwareBuffer_lock; # introduced=26
diff --git a/packages/CompanionDeviceManager/res/layout/buttons.xml b/packages/CompanionDeviceManager/res/layout/buttons.xml
index b190a7f..a80720c 100644
--- a/packages/CompanionDeviceManager/res/layout/buttons.xml
+++ b/packages/CompanionDeviceManager/res/layout/buttons.xml
@@ -29,14 +29,15 @@
android:id="@+id/button_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@android:string/cancel"
+ android:text="@string/consent_no"
+ android:textColor="?android:attr/textColorSecondary"
style="@android:style/Widget.Material.Button.Borderless.Colored"
/>
<Button
android:id="@+id/button_pair"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@android:string/ok"
+ android:text="@string/consent_yes"
style="@android:style/Widget.Material.Button.Borderless.Colored"
/>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/device_chooser.xml b/packages/CompanionDeviceManager/res/layout/device_chooser.xml
index db014ae..273347a 100644
--- a/packages/CompanionDeviceManager/res/layout/device_chooser.xml
+++ b/packages/CompanionDeviceManager/res/layout/device_chooser.xml
@@ -23,11 +23,13 @@
<include layout="@layout/title" />
+ <include layout="@layout/profile_summary" />
+
<ListView
android:id="@+id/device_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_below="@+id/title"
+ android:layout_below="@+id/profile_summary"
android:layout_above="@+id/buttons"
style="@android:style/Widget.Material.ListView"
/>
diff --git a/packages/CompanionDeviceManager/res/layout/device_confirmation.xml b/packages/CompanionDeviceManager/res/layout/device_confirmation.xml
index 7cde41a..1336e79 100644
--- a/packages/CompanionDeviceManager/res/layout/device_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/device_confirmation.xml
@@ -23,6 +23,8 @@
<include layout="@layout/title" />
+ <include layout="@layout/profile_summary" />
+
<include layout="@layout/buttons" />
</LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/profile_summary.xml b/packages/CompanionDeviceManager/res/layout/profile_summary.xml
new file mode 100644
index 0000000..80fec59
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/layout/profile_summary.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/profile_summary"
+ android:layout_below="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="14sp"
+ android:gravity="center"
+/>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/title.xml b/packages/CompanionDeviceManager/res/layout/title.xml
index 0a44fbb..9a50366 100644
--- a/packages/CompanionDeviceManager/res/layout/title.xml
+++ b/packages/CompanionDeviceManager/res/layout/title.xml
@@ -20,5 +20,6 @@
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:gravity="center"
style="@*android:style/TextAppearance.Widget.Toolbar.Title"
/>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index c4372eb..1b96b00 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -20,9 +20,21 @@
<string name="app_label">Companion Device Manager</string>
<!-- Title of the device selection dialog. -->
- <string name="chooser_title">Link with <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong></string>
+ <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by <strong><xliff:g id="app_name" example="Android Wear">%2$s</xliff:g></strong></string>
- <!-- Title of the device pairing confirmation dialog. -->
- <string name="confirmation_title">Link <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> with <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong></string>
+ <!-- The generic placeholder for a device type when nothing specific is known about it [CHAR LIMIT=30] -->
+ <string name="profile_name_generic">device</string>
+
+ <!-- Title of the device association confirmation dialog. -->
+ <string name="confirmation_title">Set <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to manage your <xliff:g id="profile_name" example="watch">%2$s</xliff:g> - <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%3$s</xliff:g></strong></string>
+
+ <!-- Text of the device profile permissions explanation in the association dialog. -->
+ <string name="profile_summary"><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g> is needed to manage your <xliff:g id="profile_name" example="watch">%2$s</xliff:g>. <xliff:g id="app_name2" example="Android Wear">%3$s</xliff:g> will get access to <xliff:g id="permissions" example="Notifications, Calendar and Phone">%4$s</xliff:g> while the <xliff:g id="profile_name2" example="watch">%5$s</xliff:g> is connected.</string>
+
+ <!-- Positive button for the device-app association consent dialog [CHAR LIMIT=30] -->
+ <string name="consent_yes">Yes</string>
+
+ <!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] -->
+ <string name="consent_no">No thanks</string>
</resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
index bdfbf82..f42a51d 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -17,11 +17,13 @@
package com.android.companiondevicemanager;
import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
+import static android.text.TextUtils.withoutPrefix;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static java.util.Objects.requireNonNull;
import android.app.Activity;
+import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -62,12 +64,19 @@
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- if (getService().mRequest.isSingleDevice()) {
+ String deviceProfile = getRequest().getDeviceProfile();
+ String profileName = deviceProfile == null
+ ? getString(R.string.profile_name_generic)
+ //TODO introduce PermissionController APIs to resolve UI values
+ : withoutPrefix("android.app.role.COMPANION_DEVICE_", deviceProfile).toLowerCase();
+
+ if (getRequest().isSingleDevice()) {
setContentView(R.layout.device_confirmation);
final DeviceFilterPair selectedDevice = getService().mDevicesFound.get(0);
setTitle(Html.fromHtml(getString(
R.string.confirmation_title,
getCallingAppName(),
+ profileName,
selectedDevice.getDisplayName()), 0));
mPairButton = findViewById(R.id.button_pair);
mPairButton.setOnClickListener(v -> onDeviceConfirmed(getService().mSelectedDevice));
@@ -77,7 +86,9 @@
setContentView(R.layout.device_chooser);
mPairButton = findViewById(R.id.button_pair);
mPairButton.setVisibility(View.GONE);
- setTitle(Html.fromHtml(getString(R.string.chooser_title, getCallingAppName()), 0));
+ setTitle(Html.fromHtml(getString(R.string.chooser_title,
+ profileName,
+ getCallingAppName()), 0));
mDeviceListView = findViewById(R.id.device_list);
final DeviceDiscoveryService.DevicesAdapter adapter = getService().mDevicesAdapter;
mDeviceListView.setAdapter(adapter);
@@ -97,12 +108,33 @@
});
mDeviceListView.addFooterView(mLoadingIndicator = getProgressBar(), null, false);
}
+
+ TextView profileSummary = findViewById(R.id.profile_summary);
+
+ if (deviceProfile != null) {
+ //TODO introduce PermissionController APIs to resolve UI values
+ String privileges = "Notifications, Phone, Contacts and Calendar";
+ profileSummary.setVisibility(View.VISIBLE);
+ profileSummary.setText(getString(R.string.profile_summary,
+ getCallingAppName(),
+ profileName,
+ getCallingAppName(),
+ privileges,
+ profileName));
+ } else {
+ profileSummary.setVisibility(View.GONE);
+ }
+
getService().mActivity = this;
mCancelButton = findViewById(R.id.button_cancel);
mCancelButton.setOnClickListener(v -> cancel());
}
+ private AssociationRequest getRequest() {
+ return getService().mRequest;
+ }
+
private void cancel() {
getService().onCancel();
setResult(RESULT_CANCELED);
@@ -132,7 +164,7 @@
@Override
public String getCallingPackage() {
- return requireNonNull(getService().mRequest.getCallingPackage());
+ return requireNonNull(getRequest().getCallingPackage());
}
@Override
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index a7e397e..2fe351e 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -64,7 +64,7 @@
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
import android.widget.TextView;
import com.android.internal.infra.AndroidFuture;
@@ -329,7 +329,7 @@
mServiceCallback.cancel(true);
}
- class DevicesAdapter extends ArrayAdapter<DeviceFilterPair> {
+ class DevicesAdapter extends BaseAdapter {
private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth);
private Drawable WIFI_ICON = icon(com.android.internal.R.drawable.ic_wifi_signal_3);
@@ -341,10 +341,6 @@
return icon;
}
- public DevicesAdapter() {
- super(DeviceDiscoveryService.this, 0, mDevicesFound);
- }
-
@Override
public View getView(
int position,
@@ -391,6 +387,21 @@
mColors.put(colorAttr, result);
return result;
}
+
+ @Override
+ public int getCount() {
+ return mDevicesFound.size();
+ }
+
+ @Override
+ public DeviceFilterPair getItem(int position) {
+ return mDevicesFound.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
}
/**
diff --git a/packages/InputDevices/res/values-mn/strings.xml b/packages/InputDevices/res/values-mn/strings.xml
index fcaf321..1697097 100644
--- a/packages/InputDevices/res/values-mn/strings.xml
+++ b/packages/InputDevices/res/values-mn/strings.xml
@@ -22,9 +22,9 @@
<string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Болгар хэл, Авиа зүй"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Итали"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Дани"</string>
- <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвеги"</string>
+ <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвег"</string>
<string name="keyboard_layout_swedish" msgid="732959109088479351">"Швед"</string>
- <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Финлянд"</string>
+ <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Финланд"</string>
<string name="keyboard_layout_croatian" msgid="4172229471079281138">"Хорват"</string>
<string name="keyboard_layout_czech" msgid="1349256901452975343">"Чех"</string>
<string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Чех хэлний QWERTY загвар"</string>
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 3af4b25..64dc2af 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -51,6 +51,7 @@
"SettingsLibRadioButtonPreference",
"SettingsLibDisplayDensityUtils",
"SettingsLibUtils",
+ "SettingsLibEmergencyNumber",
"SettingsLibTopIntroPreference",
],
}
diff --git a/packages/SettingsLib/EmergencyNumber/Android.bp b/packages/SettingsLib/EmergencyNumber/Android.bp
new file mode 100644
index 0000000..3c41f78
--- /dev/null
+++ b/packages/SettingsLib/EmergencyNumber/Android.bp
@@ -0,0 +1,10 @@
+android_library {
+ name: "SettingsLibEmergencyNumber",
+
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ ],
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/EmergencyNumber/AndroidManifest.xml b/packages/SettingsLib/EmergencyNumber/AndroidManifest.xml
new file mode 100644
index 0000000..b877fc4
--- /dev/null
+++ b/packages/SettingsLib/EmergencyNumber/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.emergencynumber">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java
new file mode 100644
index 0000000..12d21ca
--- /dev/null
+++ b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.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.settingslib.emergencynumber;
+
+import static android.telephony.emergency.EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE;
+import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Util class to help manage emergency numbers
+ */
+public class EmergencyNumberUtils {
+ private static final String TAG = "EmergencyNumberUtils";
+ private static final String EMERGENCY_GESTURE_CALL_NUMBER = "emergency_gesture_call_number";
+ @VisibleForTesting
+ static final String FALL_BACK_NUMBER = "112";
+
+ private final Context mContext;
+ private final TelephonyManager mTelephonyManager;
+
+
+ public EmergencyNumberUtils(Context context) {
+ mContext = context;
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ } else {
+ mTelephonyManager = null;
+ }
+ }
+
+ /**
+ * Returns the most appropriate number for police.
+ */
+ public String getDefaultPoliceNumber() {
+ if (mTelephonyManager == null) {
+ return FALL_BACK_NUMBER;
+ }
+ final List<EmergencyNumber> promotedPoliceNumber = getPromotedEmergencyNumbers(
+ EMERGENCY_SERVICE_CATEGORY_POLICE);
+ if (promotedPoliceNumber == null || promotedPoliceNumber.isEmpty()) {
+ return FALL_BACK_NUMBER;
+ }
+ return promotedPoliceNumber.get(0).getNumber();
+ }
+
+ /**
+ * Returns the number chosen by user. If user has not provided any number, use default ({@link
+ * #getDefaultPoliceNumber()}).
+ */
+ public String getPoliceNumber() {
+ final String userProvidedNumber = Settings.Secure.getString(mContext.getContentResolver(),
+ EMERGENCY_GESTURE_CALL_NUMBER);
+ return TextUtils.isEmpty(userProvidedNumber)
+ ? getDefaultPoliceNumber() : userProvidedNumber;
+ }
+
+ private List<EmergencyNumber> getPromotedEmergencyNumbers(int categories) {
+ Map<Integer, List<EmergencyNumber>> allLists = mTelephonyManager.getEmergencyNumberList(
+ categories);
+ if (allLists == null || allLists.isEmpty()) {
+ Log.w(TAG, "Unable to retrieve emergency number lists!");
+ return new ArrayList<>();
+ }
+ Map<Integer, List<EmergencyNumber>> promotedEmergencyNumberLists = new ArrayMap<>();
+ for (Map.Entry<Integer, List<EmergencyNumber>> entry : allLists.entrySet()) {
+ if (entry.getKey() == null || entry.getValue() == null) {
+ continue;
+ }
+ List<EmergencyNumber> emergencyNumberList = entry.getValue();
+ Log.d(TAG, "Emergency numbers for subscription id " + entry.getKey());
+
+ // The list of promoted emergency numbers which will be visible on shortcut view.
+ List<EmergencyNumber> promotedList = new ArrayList<>();
+ // A temporary list for non-prioritized emergency numbers.
+ List<EmergencyNumber> tempList = new ArrayList<>();
+
+ for (EmergencyNumber emergencyNumber : emergencyNumberList) {
+ // Emergency numbers in DATABASE are prioritized since they were well-categorized.
+ boolean isFromPrioritizedSource =
+ emergencyNumber.getEmergencyNumberSources().contains(
+ EMERGENCY_NUMBER_SOURCE_DATABASE);
+
+ Log.d(TAG, String.format("Number %s, isFromPrioritizedSource %b",
+ emergencyNumber, isFromPrioritizedSource));
+ if (isFromPrioritizedSource) {
+ promotedList.add(emergencyNumber);
+ } else {
+ tempList.add(emergencyNumber);
+ }
+ }
+ // Puts numbers in temp list after prioritized numbers.
+ promotedList.addAll(tempList);
+
+ if (!promotedList.isEmpty()) {
+ promotedEmergencyNumberLists.put(entry.getKey(), promotedList);
+ }
+ }
+
+ if (promotedEmergencyNumberLists.isEmpty()) {
+ Log.w(TAG, "No promoted emergency number found!");
+ }
+ return promotedEmergencyNumberLists.get(SubscriptionManager.getDefaultSubscriptionId());
+ }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index cea7903..9f1c96d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1109,6 +1109,8 @@
<string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until charged</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
<string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> until charged</string>
+ <!-- [CHAR_LIMIT=40] Label for battery level chart when charge been limited -->
+ <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Battery limited temporarily</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java
new file mode 100644
index 0000000..1403631
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.emergencynumber;
+
+import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(RobolectricTestRunner.class)
+public class EmergencyNumberUtilsTest {
+ private static final String TELEPHONY_EMERGENCY_NUMBER = "1234";
+ private static final String USER_OVERRIDE_EMERGENCY_NUMBER = "5678";
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ private EmergencyNumberUtils mUtils;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ }
+
+ @Test
+ public void getDefaultPoliceNumber_noTelephony_shouldReturnDefault() {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
+ mUtils = new EmergencyNumberUtils(mContext);
+
+ assertThat(mUtils.getDefaultPoliceNumber()).isEqualTo(
+ EmergencyNumberUtils.FALL_BACK_NUMBER);
+ }
+
+ @Test
+ public void getDefaultPoliceNumber_hasTelephony_shouldLoadFromTelephony() {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
+ when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+ addEmergencyNumberToTelephony();
+ mUtils = new EmergencyNumberUtils(mContext);
+
+
+ assertThat(mUtils.getDefaultPoliceNumber()).isEqualTo(TELEPHONY_EMERGENCY_NUMBER);
+ }
+
+ @Test
+ public void getPoliceNumber_hasUserOverride_shouldLoadFromUserOverride() {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
+ when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+ addEmergencyNumberToTelephony();
+
+ ContentResolver resolver = RuntimeEnvironment.application.getContentResolver();
+ when(mContext.getContentResolver()).thenReturn(resolver);
+ Settings.Secure.putString(resolver, Settings.Secure.EMERGENCY_GESTURE_CALL_NUMBER,
+ USER_OVERRIDE_EMERGENCY_NUMBER);
+
+ mUtils = new EmergencyNumberUtils(mContext);
+
+ assertThat(mUtils.getPoliceNumber()).isEqualTo(USER_OVERRIDE_EMERGENCY_NUMBER);
+ }
+
+ @Test
+ public void getPoliceNumber_noUserOverride_shouldLoadFromTelephony() {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
+ when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+ addEmergencyNumberToTelephony();
+
+ mUtils = new EmergencyNumberUtils(mContext);
+
+ assertThat(mUtils.getPoliceNumber()).isEqualTo(TELEPHONY_EMERGENCY_NUMBER);
+ }
+
+ private void addEmergencyNumberToTelephony() {
+ final int subId = SubscriptionManager.getDefaultSubscriptionId();
+ EmergencyNumber emergencyNumber = mock(EmergencyNumber.class);
+ Map<Integer, List<EmergencyNumber>> numbers = new ArrayMap<>();
+ List<EmergencyNumber> numbersForSubId = new ArrayList<>();
+ numbersForSubId.add(emergencyNumber);
+ numbers.put(subId, numbersForSubId);
+ when(mTelephonyManager.getEmergencyNumberList(
+ EMERGENCY_SERVICE_CATEGORY_POLICE)).thenReturn(numbers);
+ when(emergencyNumber.getNumber()).thenReturn(TELEPHONY_EMERGENCY_NUMBER);
+ }
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index a0a5fcb..af12ddd 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -315,7 +315,6 @@
Settings.Global.KERNEL_CPU_THREAD_READER,
Settings.Global.LANG_ID_UPDATE_CONTENT_URL,
Settings.Global.LANG_ID_UPDATE_METADATA_URL,
- Settings.Global.LATENCY_TRACKER,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3de0fbd..7120cc2 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -623,8 +623,7 @@
<service
android:name=".keyguard.KeyguardService"
- android:exported="true"
- android:enabled="@bool/config_enableKeyguardService" />
+ android:exported="true" />
<activity android:name=".keyguard.WorkLockActivity"
android:label="@string/accessibility_desc_work_lock"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index 63f8b1f..0f94bca 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -21,6 +21,7 @@
import com.android.systemui.plugins.annotations.ProvidesInterface;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
@@ -30,7 +31,7 @@
*/
@ProvidesInterface(version = FalsingManager.VERSION)
public interface FalsingManager {
- int VERSION = 5;
+ int VERSION = 6;
void onSuccessfulUnlock();
@@ -45,6 +46,42 @@
/** Returns true if the gesture should be rejected. */
boolean isFalseTouch(int interactionType);
+ /**
+ * Returns true if the FalsingManager thinks the last gesure was not a valid tap.
+ *
+ * Accepts one parameter, robustCheck, that distinctly changes behavior. When set to false,
+ * this method simply looks at the last gesture and returns whether it is a tap or not, (as
+ * opposed to a swipe or other non-tap gesture). When set to true, a more thorough analysis
+ * is performed that can include historical interactions and other contextual cues to see
+ * if the tap looks accidental.
+ *
+ * Set robustCheck to true if you want to validate a tap for launching an action, like opening
+ * a notification. Set to false if you simply want to know if the last gesture looked like a
+ * tap.
+ */
+ boolean isFalseTap(boolean robustCheck);
+
+ /**
+ * Returns true if the last two gestures do not look like a double tap.
+ *
+ * Only works on data that has already been reported to the FalsingManager. Be sure that
+ * {@link #onTouchEvent(MotionEvent, int, int)} has already been called for all of the
+ * taps you want considered.
+ *
+ * This looks at the last two gestures on the screen, ensuring that they meet the following
+ * criteria:
+ *
+ * a) There are at least two gestures.
+ * b) The last two gestures look like taps.
+ * c) The last two gestures look like a double tap taken together.
+ *
+ * This method is _not_ context aware. That is to say, if two taps occur on two neighboring
+ * views, but are otherwise close to one another, this will report a successful double tap.
+ * It is up to the caller to decide
+ * @return
+ */
+ boolean isFalseDoubleTap();
+
void onNotificatonStopDraggingDown();
void setNotificationExpanded();
@@ -103,7 +140,8 @@
void onTouchEvent(MotionEvent ev, int width, int height);
- void dump(PrintWriter pw);
+ /** From com.android.systemui.Dumpable. */
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
void cleanup();
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 7816d62..b75c2c4 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -82,6 +82,28 @@
android:elegantTextHeight="false"
/>
</FrameLayout>
+ <FrameLayout
+ android:id="@+id/new_lockscreen_clock_view_large"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/keyguard_status_area"
+ android:paddingTop="20dp"
+ android:visibility="gone">
+ <com.android.keyguard.AnimatableClockView
+ android:id="@+id/animatable_clock_view_large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center_horizontal"
+ android:textSize="170dp"
+ android:letterSpacing="0.02"
+ android:lineSpacingMultiplier=".8"
+ android:includeFontPadding="false"
+ android:fontFamily="@font/clock"
+ android:typeface="monospace"
+ android:elegantTextHeight="false"
+ />
+ </FrameLayout>
<include layout="@layout/keyguard_status_area"
android:id="@+id/keyguard_status_area"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f7e9fed..d95ab37 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -79,6 +79,9 @@
is not fully charged, and it's plugged into a slow charger, say that it's charging slowly. -->
<string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
+ <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that it's limited temporarily. -->
+ <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Battery limited temporarily</string>
+
<!-- When the lock screen is showing and the battery is low, warn user to plug
in the phone soon. -->
<string name="keyguard_low_battery">Connect your charger.</string>
diff --git a/packages/SystemUI/res-product/values-de/strings.xml b/packages/SystemUI/res-product/values-de/strings.xml
index 5c8f842..a84413d 100644
--- a/packages/SystemUI/res-product/values-de/strings.xml
+++ b/packages/SystemUI/res-product/values-de/strings.xml
@@ -21,7 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="dock_alignment_slow_charging" product="default" msgid="6997633396534416792">"Smartphone genau platzieren, um es schneller zu laden"</string>
<string name="dock_alignment_not_charging" product="default" msgid="3980752926226749808">"Smartphone genau platzieren, um es kabellos zu laden"</string>
- <string name="inattentive_sleep_warning_message" product="tv" msgid="6844464574089665063">"Das Android TV-Gerät wird gleich ausgeschaltet. Falls es eingeschaltet bleiben soll, drücke beispielsweise eine Taste oder berühre den Bildschirm."</string>
+ <string name="inattentive_sleep_warning_message" product="tv" msgid="6844464574089665063">"Das Android TV-Gerät wird gleich ausgeschaltet. Falls es eingeschaltet bleiben soll, drücke eine Taste."</string>
<string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"Das Gerät wird gleich ausgeschaltet. Falls es eingeschaltet bleiben soll, drücke beispielsweise eine Taste oder berühre den Bildschirm."</string>
<string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"Keine SIM-Karte im Tablet."</string>
<string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"Keine SIM-Karte im Smartphone."</string>
diff --git a/packages/SystemUI/res/layout/controls_dialog_pin.xml b/packages/SystemUI/res/layout/controls_dialog_pin.xml
index 170b32b..d0ef10b 100644
--- a/packages/SystemUI/res/layout/controls_dialog_pin.xml
+++ b/packages/SystemUI/res/layout/controls_dialog_pin.xml
@@ -27,6 +27,7 @@
android:layout_height="wrap_content"
android:minHeight="48dp"
android:longClickable="false"
+ android:textAlignment="viewStart"
android:inputType="numberPassword" />
<CheckBox
android:id="@+id/controls_pin_use_alpha"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 0ba546e..f984100 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -78,7 +78,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/status_bar_height"
- android:layout_gravity="top|center_horizontal">
+ android:layout_gravity="top|center_horizontal"
+ android:gravity="center_horizontal">
<com.android.systemui.statusbar.phone.LockIcon
android:id="@+id/lock_icon"
android:layout_width="@dimen/keyguard_lock_width"
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 8d86478..6b598da 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -944,7 +944,7 @@
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Application ouverte sans avoir été installée."</string>
<string name="instant_apps_message_with_help" msgid="1816952263531203932">"Application ouverte sans avoir été installée. Touchez ici pour en savoir plus."</string>
- <string name="app_info" msgid="5153758994129963243">"Détails de l\'applic."</string>
+ <string name="app_info" msgid="5153758994129963243">"Détails de l\'appli"</string>
<string name="go_to_web" msgid="636673528981366511">"Ouvrir le navigateur"</string>
<string name="mobile_data" msgid="4564407557775397216">"Données cellulaires"</string>
<string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> : <xliff:g id="ID_2">%2$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index cebe1e8..09ec439 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -44,6 +44,9 @@
<item>com.android.systemui.wmshell.WMShell</item>
</string-array>
+ <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
+ <integer name="recents_svelte_level">3</integer>
+
<!-- Show a separate icon for low and high volume on the volume dialog -->
<bool name="config_showLowMediaVolumeIcon">true</bool>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 4407d8f..880dd378 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -161,9 +161,6 @@
<!-- The number of milliseconds to extend ambient pulse by when prompted (e.g. on touch) -->
<integer name="ambient_notification_extension_time">10000</integer>
- <!-- Whether to enable KeyguardService or not -->
- <bool name="config_enableKeyguardService">true</bool>
-
<!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
card. -->
<integer name="keyguard_max_notification_count">3</integer>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewTreeObserverWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewTreeObserverWrapper.java
new file mode 100644
index 0000000..4a870f1
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewTreeObserverWrapper.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.shared.system;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+
+import java.util.HashMap;
+
+public class ViewTreeObserverWrapper {
+
+ private static final HashMap<OnComputeInsetsListener, OnComputeInternalInsetsListener>
+ sOnComputeInsetsListenerMap = new HashMap<>();
+
+ /**
+ * Register a callback to be invoked when the invoked when it is time to
+ * compute the window's insets.
+ *
+ * @param listener The callback to add
+ * @throws IllegalStateException If {@link ViewTreeObserver#isAlive()} returns false
+ */
+ public static void addOnComputeInsetsListener(
+ ViewTreeObserver observer, OnComputeInsetsListener listener) {
+ final OnComputeInternalInsetsListener internalListener = internalInOutInfo -> {
+ final InsetsInfo inOutInfo = new InsetsInfo();
+ inOutInfo.contentInsets.set(internalInOutInfo.contentInsets);
+ inOutInfo.visibleInsets.set(internalInOutInfo.visibleInsets);
+ inOutInfo.touchableRegion.set(internalInOutInfo.touchableRegion);
+ listener.onComputeInsets(inOutInfo);
+ internalInOutInfo.contentInsets.set(inOutInfo.contentInsets);
+ internalInOutInfo.visibleInsets.set(inOutInfo.visibleInsets);
+ internalInOutInfo.touchableRegion.set(inOutInfo.touchableRegion);
+ internalInOutInfo.setTouchableInsets(inOutInfo.mTouchableInsets);
+ };
+ sOnComputeInsetsListenerMap.put(listener, internalListener);
+ observer.addOnComputeInternalInsetsListener(internalListener);
+ }
+
+ /**
+ * Remove a previously installed insets computation callback
+ *
+ * @param victim The callback to remove
+ * @throws IllegalStateException If {@link ViewTreeObserver#isAlive()} returns false
+ * @see #addOnComputeInsetsListener(ViewTreeObserver, OnComputeInsetsListener)
+ */
+ public void removeOnComputeInsetsListener(
+ ViewTreeObserver observer, OnComputeInsetsListener victim) {
+ final OnComputeInternalInsetsListener listener = sOnComputeInsetsListenerMap.get(victim);
+ if (listener != null) {
+ observer.removeOnComputeInternalInsetsListener(listener);
+ }
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when layout has
+ * completed and the client can compute its interior insets.
+ */
+ public interface OnComputeInsetsListener {
+ /**
+ * Callback method to be invoked when layout has completed and the
+ * client can compute its interior insets.
+ *
+ * @param inoutInfo Should be filled in by the implementation with
+ * the information about the insets of the window. This is called
+ * with whatever values the previous OnComputeInsetsListener
+ * returned, if there are multiple such listeners in the window.
+ */
+ void onComputeInsets(InsetsInfo inoutInfo);
+ }
+
+ /**
+ * Parameters used with OnComputeInsetsListener.
+ */
+ public final static class InsetsInfo {
+
+ /**
+ * Offsets from the frame of the window at which the content of
+ * windows behind it should be placed.
+ */
+ public final Rect contentInsets = new Rect();
+
+ /**
+ * Offsets from the frame of the window at which windows behind it
+ * are visible.
+ */
+ public final Rect visibleInsets = new Rect();
+
+ /**
+ * Touchable region defined relative to the origin of the frame of the window.
+ * Only used when {@link #setTouchableInsets(int)} is called with
+ * the option {@link #TOUCHABLE_INSETS_REGION}.
+ */
+ public final Region touchableRegion = new Region();
+
+ /**
+ * Option for {@link #setTouchableInsets(int)}: the entire window frame
+ * can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_FRAME =
+ ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+
+ /**
+ * Option for {@link #setTouchableInsets(int)}: the area inside of
+ * the content insets can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_CONTENT =
+ ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
+
+ /**
+ * Option for {@link #setTouchableInsets(int)}: the area inside of
+ * the visible insets can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_VISIBLE =
+ ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+
+ /**
+ * Option for {@link #setTouchableInsets(int)}: the area inside of
+ * the provided touchable region in {@link #touchableRegion} can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_REGION =
+ ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+
+ /**
+ * Set which parts of the window can be touched: either
+ * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT},
+ * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}.
+ */
+ public void setTouchableInsets(int val) {
+ mTouchableInsets = val;
+ }
+
+ int mTouchableInsets;
+
+ @Override
+ public int hashCode() {
+ int result = contentInsets.hashCode();
+ result = 31 * result + visibleInsets.hashCode();
+ result = 31 * result + touchableRegion.hashCode();
+ result = 31 * result + mTouchableInsets;
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final InsetsInfo other = (InsetsInfo) o;
+ return mTouchableInsets == other.mTouchableInsets &&
+ contentInsets.equals(other.contentInsets) &&
+ visibleInsets.equals(other.visibleInsets) &&
+ touchableRegion.equals(other.touchableRegion);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index d731840..68405a1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -76,6 +76,7 @@
* Frame for clock when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
*/
private FrameLayout mNewLockscreenClockFrame;
+ private FrameLayout mNewLockscreenLargeClockFrame;
/**
* Frame for default and custom clock.
@@ -154,14 +155,12 @@
mNewLockscreenClockFrame.setVisibility(VISIBLE);
statusAreaLP.removeRule(RelativeLayout.BELOW);
- statusAreaLP.addRule(RelativeLayout.LEFT_OF, R.id.new_lockscreen_clock_view);
statusAreaLP.addRule(RelativeLayout.ALIGN_PARENT_START);
} else {
setPaddingRelative(0, 0, 0, 0);
mSmallClockFrame.setVisibility(VISIBLE);
mNewLockscreenClockFrame.setVisibility(GONE);
- statusAreaLP.removeRule(RelativeLayout.LEFT_OF);
statusAreaLP.removeRule(RelativeLayout.ALIGN_PARENT_START);
statusAreaLP.addRule(RelativeLayout.BELOW, R.id.clock_view);
}
@@ -175,6 +174,7 @@
mClockView = findViewById(R.id.default_clock_view);
mClockViewBold = findViewById(R.id.default_clock_view_bold);
mNewLockscreenClockFrame = findViewById(R.id.new_lockscreen_clock_view);
+ mNewLockscreenLargeClockFrame = findViewById(R.id.new_lockscreen_clock_view_large);
mSmallClockFrame = findViewById(R.id.clock_view);
mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
}
@@ -292,6 +292,33 @@
mClockViewBold.setFormat24Hour(format);
}
+ private void updateClockLayout(boolean useLargeClock) {
+ if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) return;
+
+ Fade fadeIn = new Fade();
+ fadeIn.setDuration(KeyguardSliceView.DEFAULT_ANIM_DURATION);
+ fadeIn.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+
+ Fade fadeOut = new Fade();
+ fadeOut.setDuration(KeyguardSliceView.DEFAULT_ANIM_DURATION / 2);
+ fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+
+ if (useLargeClock) {
+ TransitionManager.beginDelayedTransition(mNewLockscreenClockFrame, fadeOut);
+ TransitionManager.beginDelayedTransition(mNewLockscreenLargeClockFrame, fadeIn);
+
+ mNewLockscreenClockFrame.setVisibility(View.INVISIBLE);
+ addView(mNewLockscreenLargeClockFrame);
+ mNewLockscreenLargeClockFrame.setVisibility(View.VISIBLE);
+ } else {
+ TransitionManager.beginDelayedTransition(mNewLockscreenClockFrame, fadeIn);
+ TransitionManager.beginDelayedTransition(mNewLockscreenLargeClockFrame, fadeOut);
+
+ removeView(mNewLockscreenLargeClockFrame);
+ mNewLockscreenClockFrame.setVisibility(View.VISIBLE);
+ }
+ }
+
/**
* Set the amount (ratio) that the device has transitioned to doze.
*
@@ -312,6 +339,8 @@
if (hasVisibleNotifications == mHasVisibleNotifications) {
return;
}
+ updateClockLayout(!hasVisibleNotifications);
+
mHasVisibleNotifications = hasVisibleNotifications;
if (mDarkAmount == 0f && mBigClockContainer != null) {
// Starting a fade transition since the visibility of the big clock will change.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 6c69534..c5c36e9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -61,6 +61,7 @@
*/
private AnimatableClockController mNewLockScreenClockViewController;
private FrameLayout mNewLockScreenClockFrame;
+ private AnimatableClockController mNewLockScreenLargeClockViewController;
private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@@ -189,6 +190,7 @@
void refresh() {
if (mNewLockScreenClockViewController != null) {
mNewLockScreenClockViewController.refreshTime();
+ mNewLockScreenLargeClockViewController.refreshTime();
}
mView.refresh();
@@ -221,9 +223,15 @@
mView.findViewById(R.id.animatable_clock_view),
mStatusBarStateController);
mNewLockScreenClockViewController.init();
+ mNewLockScreenLargeClockViewController =
+ new AnimatableClockController(
+ mView.findViewById(R.id.animatable_clock_view_large),
+ mStatusBarStateController);
+ mNewLockScreenLargeClockViewController.init();
}
} else {
mNewLockScreenClockViewController = null;
+ mNewLockScreenLargeClockViewController = null;
}
mView.updateLockScreenMode(mLockScreenMode);
updateAodIcons();
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 8aa3493c..6151ac7 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -24,6 +24,7 @@
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Handler;
@@ -78,9 +79,9 @@
private float mInitialTouchPos;
private float mPerpendicularInitialTouchPos;
- private boolean mDragging;
+ private boolean mIsSwiping;
private boolean mSnappingChild;
- private View mCurrView;
+ private View mTouchedView;
private boolean mCanCurrViewBeDimissed;
private float mDensityScale;
private float mTranslation = 0;
@@ -95,14 +96,14 @@
@Override
public void run() {
- if (mCurrView != null && !mLongPressSent) {
+ if (mTouchedView != null && !mLongPressSent) {
mLongPressSent = true;
- if (mCurrView instanceof ExpandableNotificationRow) {
- mCurrView.getLocationOnScreen(mViewOffset);
+ if (mTouchedView instanceof ExpandableNotificationRow) {
+ mTouchedView.getLocationOnScreen(mViewOffset);
final int x = (int) mDownLocation[0] - mViewOffset[0];
final int y = (int) mDownLocation[1] - mViewOffset[1];
- mCurrView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
- ((ExpandableNotificationRow) mCurrView).doLongClickCallback(x, y);
+ mTouchedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+ ((ExpandableNotificationRow) mTouchedView).doLongClickCallback(x, y);
}
}
}
@@ -281,10 +282,10 @@
@Override
public boolean onInterceptTouchEvent(final MotionEvent ev) {
- if (mCurrView instanceof ExpandableNotificationRow) {
- NotificationMenuRowPlugin nmr = ((ExpandableNotificationRow) mCurrView).getProvider();
+ if (mTouchedView instanceof ExpandableNotificationRow) {
+ NotificationMenuRowPlugin nmr = ((ExpandableNotificationRow) mTouchedView).getProvider();
if (nmr != null) {
- mMenuRowIntercepting = nmr.onInterceptTouchEvent(mCurrView, ev);
+ mMenuRowIntercepting = nmr.onInterceptTouchEvent(mTouchedView, ev);
}
}
final int action = ev.getAction();
@@ -292,19 +293,19 @@
switch (action) {
case MotionEvent.ACTION_DOWN:
mTouchAboveFalsingThreshold = false;
- mDragging = false;
+ mIsSwiping = false;
mSnappingChild = false;
mLongPressSent = false;
mVelocityTracker.clear();
- mCurrView = mCallback.getChildAtPosition(ev);
+ mTouchedView = mCallback.getChildAtPosition(ev);
- if (mCurrView != null) {
- onDownUpdate(mCurrView, ev);
- mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
+ if (mTouchedView != null) {
+ onDownUpdate(mTouchedView, ev);
+ mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mTouchedView);
mVelocityTracker.addMovement(ev);
mInitialTouchPos = getPos(ev);
mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
- mTranslation = getTranslation(mCurrView);
+ mTranslation = getTranslation(mTouchedView);
mDownLocation[0] = ev.getRawX();
mDownLocation[1] = ev.getRawY();
mHandler.postDelayed(mPerformLongPress, mLongPressTimeout);
@@ -312,7 +313,7 @@
break;
case MotionEvent.ACTION_MOVE:
- if (mCurrView != null && !mLongPressSent) {
+ if (mTouchedView != null && !mLongPressSent) {
mVelocityTracker.addMovement(ev);
float pos = getPos(ev);
float perpendicularPos = getPerpendicularPos(ev);
@@ -325,11 +326,11 @@
: mPagingTouchSlop;
if (Math.abs(delta) > pagingTouchSlop
&& Math.abs(delta) > Math.abs(deltaPerpendicular)) {
- if (mCallback.canChildBeDragged(mCurrView)) {
- mCallback.onBeginDrag(mCurrView);
- mDragging = true;
+ if (mCallback.canChildBeDragged(mTouchedView)) {
+ mCallback.onBeginDrag(mTouchedView);
+ mIsSwiping = true;
mInitialTouchPos = getPos(ev);
- mTranslation = getTranslation(mCurrView);
+ mTranslation = getTranslation(mTouchedView);
}
cancelLongPress();
} else if (ev.getClassification() == MotionEvent.CLASSIFICATION_DEEP_PRESS
@@ -343,16 +344,16 @@
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- final boolean captured = (mDragging || mLongPressSent || mMenuRowIntercepting);
- mDragging = false;
- mCurrView = null;
+ final boolean captured = (mIsSwiping || mLongPressSent || mMenuRowIntercepting);
+ mIsSwiping = false;
+ mTouchedView = null;
mLongPressSent = false;
mMenuRowIntercepting = false;
cancelLongPress();
if (captured) return true;
break;
}
- return mDragging || mLongPressSent || mMenuRowIntercepting;
+ return mIsSwiping || mLongPressSent || mMenuRowIntercepting;
}
/**
@@ -451,6 +452,7 @@
}
if (!mCancelled || wasRemoved) {
mCallback.onChildDismissed(animView);
+ resetSwipeState();
}
if (endAction != null) {
endAction.run();
@@ -501,6 +503,7 @@
updateSwipeProgressFromOffset(animView, canBeDismissed);
onChildSnappedBack(animView, targetLeft);
mCallback.onChildSnappedBack(animView, targetLeft);
+ resetSwipeState();
}
}
});
@@ -563,7 +566,7 @@
* @param targetLeft the target to snap to.
*/
public void snapChildIfNeeded(final View view, boolean animate, float targetLeft) {
- if ((mDragging && mCurrView == view) || mSnappingChild) {
+ if ((mIsSwiping && mTouchedView == view) || mSnappingChild) {
return;
}
boolean needToSnap = false;
@@ -589,7 +592,7 @@
return true;
}
- if (!mDragging && !mMenuRowIntercepting) {
+ if (!mIsSwiping && !mMenuRowIntercepting) {
if (mCallback.getChildAtPosition(ev) != null) {
// We are dragging directly over a card, make sure that we also catch the gesture
@@ -610,7 +613,7 @@
switch (action) {
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_MOVE:
- if (mCurrView != null) {
+ if (mTouchedView != null) {
float delta = getPos(ev) - mInitialTouchPos;
float absDelta = Math.abs(delta);
if (absDelta >= getFalsingThreshold()) {
@@ -618,9 +621,10 @@
}
// don't let items that can't be dismissed be dragged more than
// maxScrollDistance
- if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(mCurrView,
+ if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(
+ mTouchedView,
delta > 0)) {
- float size = getSize(mCurrView);
+ float size = getSize(mTouchedView);
float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size;
if (absDelta >= size) {
delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
@@ -636,32 +640,30 @@
}
}
- setTranslation(mCurrView, mTranslation + delta);
- updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed);
- onMoveUpdate(mCurrView, ev, mTranslation + delta, delta);
+ setTranslation(mTouchedView, mTranslation + delta);
+ updateSwipeProgressFromOffset(mTouchedView, mCanCurrViewBeDimissed);
+ onMoveUpdate(mTouchedView, ev, mTranslation + delta, delta);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- if (mCurrView == null) {
+ if (mTouchedView == null) {
break;
}
mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity());
float velocity = getVelocity(mVelocityTracker);
- if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) {
+ if (!handleUpEvent(ev, mTouchedView, velocity, getTranslation(mTouchedView))) {
if (isDismissGesture(ev)) {
- // flingadingy
- dismissChild(mCurrView, velocity,
+ dismissChild(mTouchedView, velocity,
!swipedFastEnough() /* useAccelerateInterpolator */);
} else {
- // snappity
- mCallback.onDragCancelled(mCurrView);
- snapChild(mCurrView, 0 /* leftTarget */, velocity);
+ mCallback.onDragCancelled(mTouchedView);
+ snapChild(mTouchedView, 0 /* leftTarget */, velocity);
}
- mCurrView = null;
+ mTouchedView = null;
}
- mDragging = false;
+ mIsSwiping = false;
break;
}
return true;
@@ -689,17 +691,18 @@
}
protected boolean swipedFarEnough() {
- float translation = getTranslation(mCurrView);
+ float translation = getTranslation(mTouchedView);
return DISMISS_IF_SWIPED_FAR_ENOUGH
- && Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(mCurrView);
+ && Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(
+ mTouchedView);
}
public boolean isDismissGesture(MotionEvent ev) {
- float translation = getTranslation(mCurrView);
+ float translation = getTranslation(mTouchedView);
return ev.getActionMasked() == MotionEvent.ACTION_UP
&& !mFalsingManager.isUnlockingDisabled()
&& !isFalseGesture() && (swipedFastEnough() || swipedFarEnough())
- && mCallback.canChildBeDismissedInDirection(mCurrView, translation > 0);
+ && mCallback.canChildBeDismissedInDirection(mTouchedView, translation > 0);
}
/** Returns true if the gesture should be rejected. */
@@ -715,7 +718,7 @@
protected boolean swipedFastEnough() {
float velocity = getVelocity(mVelocityTracker);
- float translation = getTranslation(mCurrView);
+ float translation = getTranslation(mTouchedView);
boolean ret = (Math.abs(velocity) > getEscapeVelocity())
&& (velocity > 0) == (translation > 0);
return ret;
@@ -726,6 +729,20 @@
return false;
}
+ public boolean isSwiping() {
+ return mIsSwiping;
+ }
+
+ @Nullable
+ public View getSwipedView() {
+ return mIsSwiping ? mTouchedView : null;
+ }
+
+ public void resetSwipeState() {
+ mTouchedView = null;
+ mIsSwiping = false;
+ }
+
public interface Callback {
View getChildAtPosition(MotionEvent ev);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index bb7906e..714d267bb 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -23,6 +23,7 @@
import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -94,7 +95,7 @@
} catch (NoSuchMethodException
| IllegalAccessException
| InvocationTargetException e) {
- // no-op
+ Log.w(TAG, "No injector for class: " + contentProvider.getClass(), e);
}
}
);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 2b33f8c..182b3e1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -101,7 +101,7 @@
@VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
- private @ContainerState int mContainerState = STATE_UNKNOWN;
+ @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
// Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
@Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
@@ -632,10 +632,11 @@
mWindowManager.removeView(this);
}
- private void onDialogAnimatedIn() {
+ @VisibleForTesting
+ void onDialogAnimatedIn() {
if (mContainerState == STATE_PENDING_DISMISS) {
Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
- animateAway(false /* sendReason */, 0);
+ animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
return;
}
mContainerState = STATE_SHOWING;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3fc9f4d..7bb8a84 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -187,12 +187,14 @@
// TODO(b/152419866): Use the UDFPS window type when it becomes available.
WindowManager.LayoutParams.TYPE_BOOT_PROGRESS,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
PixelFormat.TRANSLUCENT);
mCoreLayoutParams.setTitle(TAG);
mCoreLayoutParams.setFitInsetsTypes(0);
+ mCoreLayoutParams.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);
mView.setSensorProperties(mSensorProps);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AccelerationClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/AccelerationClassifier.java
deleted file mode 100644
index 4585fe9..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/AccelerationClassifier.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-import android.view.MotionEvent;
-
-import java.util.HashMap;
-
-/**
- * A classifier which looks at the speed and distance between successive points of a Stroke.
- * It looks at two consecutive speeds between two points and calculates the ratio between them.
- * The final result is the maximum of these values. It does the same for distances. If some speed
- * or distance is equal to zero then the ratio between this and the next part is not calculated. To
- * the duration of each part there is added one nanosecond so that it is always possible to
- * calculate the speed of a part.
- */
-public class AccelerationClassifier extends StrokeClassifier {
- private final HashMap<Stroke, Data> mStrokeMap = new HashMap<>();
-
- public AccelerationClassifier(ClassifierData classifierData) {
- mClassifierData = classifierData;
- }
-
- @Override
- public String getTag() {
- return "ACC";
- }
-
- @Override
- public void onTouchEvent(MotionEvent event) {
- int action = event.getActionMasked();
-
- if (action == MotionEvent.ACTION_DOWN) {
- mStrokeMap.clear();
- }
-
- for (int i = 0; i < event.getPointerCount(); i++) {
- Stroke stroke = mClassifierData.getStroke(event.getPointerId(i));
- Point point = stroke.getPoints().get(stroke.getPoints().size() - 1);
- if (mStrokeMap.get(stroke) == null) {
- mStrokeMap.put(stroke, new Data(point));
- } else {
- mStrokeMap.get(stroke).addPoint(point);
- }
- }
- }
-
- @Override
- public float getFalseTouchEvaluation(int type, Stroke stroke) {
- Data data = mStrokeMap.get(stroke);
- return 2 * SpeedRatioEvaluator.evaluate(data.maxSpeedRatio);
- }
-
- private static class Data {
-
- static final float MILLIS_TO_NANOS = 1e6f;
-
- Point previousPoint;
- float previousSpeed = 0;
- float maxSpeedRatio = 0;
-
- public Data(Point point) {
- previousPoint = point;
- }
-
- public void addPoint(Point point) {
- float distance = previousPoint.dist(point);
- float duration = (float) (point.timeOffsetNano - previousPoint.timeOffsetNano + 1);
- float speed = distance / duration;
-
- if (duration > 20 * MILLIS_TO_NANOS || duration < 5 * MILLIS_TO_NANOS) {
- // reject this segment and ensure we won't use data about it in the next round.
- previousSpeed = 0;
- previousPoint = point;
- return;
- }
- if (previousSpeed != 0.0f) {
- maxSpeedRatio = Math.max(maxSpeedRatio, speed / previousSpeed);
- }
-
- previousSpeed = speed;
- previousPoint = point;
- }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
deleted file mode 100644
index 6d13973..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-import android.os.Build;
-import android.os.SystemProperties;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * A classifier which calculates the variance of differences between successive angles in a stroke.
- * For each stroke it keeps its last three points. If some successive points are the same, it
- * ignores the repetitions. If a new point is added, the classifier calculates the angle between
- * the last three points. After that, it calculates the difference between this angle and the
- * previously calculated angle. Then it calculates the variance of the differences from a stroke.
- * To the differences there is artificially added value 0.0 and the difference between the first
- * angle and PI (angles are in radians). It helps with strokes which have few points and punishes
- * more strokes which are not smooth.
- *
- * This classifier also tries to split the stroke into two parts in the place in which the biggest
- * angle is. It calculates the angle variance of the two parts and sums them up. The reason the
- * classifier is doing this, is because some human swipes at the beginning go for a moment in one
- * direction and then they rapidly change direction for the rest of the stroke (like a tick). The
- * final result is the minimum of angle variance of the whole stroke and the sum of angle variances
- * of the two parts split up. The classifier tries the tick option only if the first part is
- * shorter than the second part.
- *
- * Additionally, the classifier classifies the angles as left angles (those angles which value is
- * in [0.0, PI - ANGLE_DEVIATION) interval), straight angles
- * ([PI - ANGLE_DEVIATION, PI + ANGLE_DEVIATION] interval) and right angles
- * ((PI + ANGLE_DEVIATION, 2 * PI) interval) and then calculates the percentage of angles which are
- * in the same direction (straight angles can be left angels or right angles)
- */
-public class AnglesClassifier extends StrokeClassifier {
- private HashMap<Stroke, Data> mStrokeMap = new HashMap<>();
-
- public static final boolean VERBOSE = SystemProperties.getBoolean("debug.falsing_log.ang",
- Build.IS_DEBUGGABLE);
-
- private static String TAG = "ANG";
-
- public AnglesClassifier(ClassifierData classifierData) {
- mClassifierData = classifierData;
- }
-
- @Override
- public String getTag() {
- return TAG;
- }
-
- @Override
- public void onTouchEvent(MotionEvent event) {
- int action = event.getActionMasked();
-
- if (action == MotionEvent.ACTION_DOWN) {
- mStrokeMap.clear();
- }
-
- for (int i = 0; i < event.getPointerCount(); i++) {
- Stroke stroke = mClassifierData.getStroke(event.getPointerId(i));
-
- if (mStrokeMap.get(stroke) == null) {
- mStrokeMap.put(stroke, new Data());
- }
- mStrokeMap.get(stroke).addPoint(stroke.getPoints().get(stroke.getPoints().size() - 1));
- }
- }
-
- @Override
- public float getFalseTouchEvaluation(int type, Stroke stroke) {
- Data data = mStrokeMap.get(stroke);
- return AnglesVarianceEvaluator.evaluate(data.getAnglesVariance(), type)
- + AnglesPercentageEvaluator.evaluate(data.getAnglesPercentage(), type);
- }
-
- private static class Data {
- private final float ANGLE_DEVIATION = (float) Math.PI / 20.0f;
-
- private List<Point> mLastThreePoints = new ArrayList<>();
- private float mFirstAngleVariance;
- private float mPreviousAngle;
- private float mBiggestAngle;
- private float mSumSquares;
- private float mSecondSumSquares;
- private float mSum;
- private float mSecondSum;
- private float mCount;
- private float mSecondCount;
- private float mFirstLength;
- private float mLength;
- private float mAnglesCount;
- private float mLeftAngles;
- private float mRightAngles;
- private float mStraightAngles;
-
- public Data() {
- mFirstAngleVariance = 0.0f;
- mPreviousAngle = (float) Math.PI;
- mBiggestAngle = 0.0f;
- mSumSquares = mSecondSumSquares = 0.0f;
- mSum = mSecondSum = 0.0f;
- mCount = mSecondCount = 1.0f;
- mLength = mFirstLength = 0.0f;
- mAnglesCount = mLeftAngles = mRightAngles = mStraightAngles = 0.0f;
- }
-
- public void addPoint(Point point) {
- // Checking if the added point is different than the previously added point
- // Repetitions are being ignored so that proper angles are calculated.
- if (mLastThreePoints.isEmpty()
- || !mLastThreePoints.get(mLastThreePoints.size() - 1).equals(point)) {
- if (!mLastThreePoints.isEmpty()) {
- mLength += mLastThreePoints.get(mLastThreePoints.size() - 1).dist(point);
- }
- mLastThreePoints.add(point);
- if (mLastThreePoints.size() == 4) {
- mLastThreePoints.remove(0);
-
- float angle = mLastThreePoints.get(1).getAngle(mLastThreePoints.get(0),
- mLastThreePoints.get(2));
-
- mAnglesCount++;
- if (angle < Math.PI - ANGLE_DEVIATION) {
- mLeftAngles++;
- } else if (angle <= Math.PI + ANGLE_DEVIATION) {
- mStraightAngles++;
- } else {
- mRightAngles++;
- }
-
- float difference = angle - mPreviousAngle;
-
- // If this is the biggest angle of the stroke so then we save the value of
- // the angle variance so far and start to count the values for the angle
- // variance of the second part.
- if (mBiggestAngle < angle) {
- mBiggestAngle = angle;
- mFirstLength = mLength;
- mFirstAngleVariance = getAnglesVariance(mSumSquares, mSum, mCount);
- mSecondSumSquares = 0.0f;
- mSecondSum = 0.0f;
- mSecondCount = 1.0f;
- } else {
- mSecondSum += difference;
- mSecondSumSquares += difference * difference;
- mSecondCount += 1.0;
- }
-
- mSum += difference;
- mSumSquares += difference * difference;
- mCount += 1.0;
- mPreviousAngle = angle;
- }
- }
- }
-
- public float getAnglesVariance(float sumSquares, float sum, float count) {
- return sumSquares / count - (sum / count) * (sum / count);
- }
-
- public float getAnglesVariance() {
- float anglesVariance = getAnglesVariance(mSumSquares, mSum, mCount);
- if (VERBOSE) {
- FalsingLog.i(TAG, "getAnglesVariance: (first pass) " + anglesVariance);
- FalsingLog.i(TAG, " - mFirstLength=" + mFirstLength);
- FalsingLog.i(TAG, " - mLength=" + mLength);
- }
- if (mFirstLength < mLength / 2f) {
- anglesVariance = Math.min(anglesVariance, mFirstAngleVariance
- + getAnglesVariance(mSecondSumSquares, mSecondSum, mSecondCount));
- if (VERBOSE) FalsingLog.i(TAG, "getAnglesVariance: (second pass) " + anglesVariance);
- }
- return anglesVariance;
- }
-
- public float getAnglesPercentage() {
- if (mAnglesCount == 0.0f) {
- if (VERBOSE) FalsingLog.i(TAG, "getAnglesPercentage: count==0, result=1");
- return 1.0f;
- }
- final float result = (Math.max(mLeftAngles, mRightAngles) + mStraightAngles) / mAnglesCount;
- if (VERBOSE) {
- FalsingLog.i(TAG, "getAnglesPercentage: left=" + mLeftAngles + " right="
- + mRightAngles + " straight=" + mStraightAngles + " count=" + mAnglesCount
- + " result=" + result);
- }
- return result;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesPercentageEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesPercentageEvaluator.java
deleted file mode 100644
index e6e42f2..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/AnglesPercentageEvaluator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-public class AnglesPercentageEvaluator {
- public static float evaluate(float value, int type) {
- final boolean secureUnlock = type == Classifier.BOUNCER_UNLOCK;
- float evaluation = 0.0f;
- if (value < 1.00 && !secureUnlock) evaluation++;
- if (value < 0.90 && !secureUnlock) evaluation++;
- if (value < 0.70) evaluation++;
- return evaluation;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceEvaluator.java
deleted file mode 100644
index 9ffe783..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceEvaluator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-public class AnglesVarianceEvaluator {
- public static float evaluate(float value, int type) {
- float evaluation = 0.0f;
- if (value > 0.20) evaluation++;
- if (value > 0.40) evaluation++;
- if (value > 0.80) evaluation++;
- if (value > 1.50) evaluation++;
- return evaluation;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
index ae7d142..c7ad3e8 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -54,11 +54,6 @@
public @interface InteractionType {}
/**
- * Contains all the information about touch events from which the classifier can query
- */
- protected ClassifierData mClassifierData;
-
- /**
* Informs the classifier that a new touch event has occurred
*/
public void onTouchEvent(MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java b/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java
deleted file mode 100644
index 587abba..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-import android.util.SparseArray;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-
-/**
- * Contains data which is used to classify interaction sequences on the lockscreen. It does, for
- * example, provide information on the current touch state.
- */
-public class ClassifierData {
- private static final long MINIMUM_DT_NANOS = 16666666; // 60Hz
- private static final long MINIMUM_DT_SMEAR_NANOS = 2500000; // 2.5ms
-
- private SparseArray<Stroke> mCurrentStrokes = new SparseArray<>();
- private ArrayList<Stroke> mEndingStrokes = new ArrayList<>();
- private final float mDpi;
-
- public ClassifierData(float dpi) {
- mDpi = dpi;
- }
-
- /** Returns true if the event should be considered, false otherwise. */
- public boolean update(MotionEvent event) {
- // We limit to 60hz sampling. Drop anything happening faster than that.
- // Legacy code was created with an assumed sampling rate. As devices increase their
- // sampling rate, this creates potentialy false positives.
- if (event.getActionMasked() == MotionEvent.ACTION_MOVE
- && mCurrentStrokes.size() != 0
- && event.getEventTimeNano() - mCurrentStrokes.valueAt(0).getLastEventTimeNano()
- < MINIMUM_DT_NANOS - MINIMUM_DT_SMEAR_NANOS) {
- return false;
- }
-
- mEndingStrokes.clear();
- int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_DOWN) {
- mCurrentStrokes.clear();
- }
-
- for (int i = 0; i < event.getPointerCount(); i++) {
- int id = event.getPointerId(i);
- if (mCurrentStrokes.get(id) == null) {
- mCurrentStrokes.put(id, new Stroke(event.getEventTimeNano(), mDpi));
- }
- mCurrentStrokes.get(id).addPoint(event.getX(i), event.getY(i),
- event.getEventTimeNano());
-
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL
- || (action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) {
- mEndingStrokes.add(getStroke(id));
- }
- }
-
- return true;
- }
-
- public void cleanUp(MotionEvent event) {
- mEndingStrokes.clear();
- int action = event.getActionMasked();
- for (int i = 0; i < event.getPointerCount(); i++) {
- int id = event.getPointerId(i);
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL
- || (action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) {
- mCurrentStrokes.remove(id);
- }
- }
- }
-
- /**
- * @return the list of Strokes which are ending in the recently added MotionEvent
- */
- public ArrayList<Stroke> getEndingStrokes() {
- return mEndingStrokes;
- }
-
- /**
- * @param id the id from MotionEvent
- * @return the Stroke assigned to the id
- */
- public Stroke getStroke(int id) {
- return mCurrentStrokes.get(id);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DirectionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DirectionClassifier.java
deleted file mode 100644
index 610e219..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/DirectionClassifier.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-/**
- * A classifier which looks at the general direction of a stroke and evaluates it depending on
- * the type of action that takes place.
- */
-public class DirectionClassifier extends StrokeClassifier {
- public DirectionClassifier(ClassifierData classifierData) {
- }
-
- @Override
- public String getTag() {
- return "DIR";
- }
-
- @Override
- public float getFalseTouchEvaluation(int type, Stroke stroke) {
- Point firstPoint = stroke.getPoints().get(0);
- Point lastPoint = stroke.getPoints().get(stroke.getPoints().size() - 1);
- return DirectionEvaluator.evaluate(lastPoint.x - firstPoint.x, lastPoint.y - firstPoint.y,
- type);
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DirectionEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/DirectionEvaluator.java
deleted file mode 100644
index 78b4168..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/DirectionEvaluator.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-public class DirectionEvaluator {
- public static float evaluate(float xDiff, float yDiff, int type) {
- float falsingEvaluation = 5.5f;
- boolean vertical = Math.abs(yDiff) >= Math.abs(xDiff);
- switch (type) {
- case Classifier.QUICK_SETTINGS:
- case Classifier.PULSE_EXPAND:
- case Classifier.NOTIFICATION_DRAG_DOWN:
- if (!vertical || yDiff <= 0.0) {
- return falsingEvaluation;
- }
- break;
- case Classifier.NOTIFICATION_DISMISS:
- if (vertical) {
- return falsingEvaluation;
- }
- break;
- case Classifier.UNLOCK:
- case Classifier.BOUNCER_UNLOCK:
- if (!vertical || yDiff >= 0.0) {
- return falsingEvaluation;
- }
- break;
- case Classifier.LEFT_AFFORDANCE:
- if (xDiff < 0.0 && yDiff > 0.0) {
- return falsingEvaluation;
- }
- break;
- case Classifier.RIGHT_AFFORDANCE:
- if (xDiff > 0.0 && yDiff > 0.0) {
- return falsingEvaluation;
- }
- default:
- break;
- }
- return 0.0f;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DurationCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DurationCountClassifier.java
deleted file mode 100644
index 77fda20..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/DurationCountClassifier.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-/**
- * A classifier which looks at the ratio between the duration of the stroke and its number of
- * points.
- */
-public class DurationCountClassifier extends StrokeClassifier {
- public DurationCountClassifier(ClassifierData classifierData) {
- }
-
- @Override
- public String getTag() {
- return "DUR";
- }
-
- @Override
- public float getFalseTouchEvaluation(int type, Stroke stroke) {
- return DurationCountEvaluator.evaluate(stroke.getDurationSeconds() / stroke.getCount());
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DurationCountEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/DurationCountEvaluator.java
deleted file mode 100644
index 5395983..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/DurationCountEvaluator.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-
-public class DurationCountEvaluator {
- public static float evaluate(float value) {
- float evaluation = 0.0f;
- if (value < 0.0105) evaluation++;
- if (value < 0.00909) evaluation++;
- if (value < 0.00667) evaluation++;
- if (value > 0.0333) evaluation++;
- if (value > 0.0500) evaluation++;
- return evaluation;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthClassifier.java
deleted file mode 100644
index de8a188..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthClassifier.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-/**
- * A classifier which looks at the distance between the first and the last point from the stroke.
- */
-public class EndPointLengthClassifier extends StrokeClassifier {
- public EndPointLengthClassifier(ClassifierData classifierData) {
- }
-
- @Override
- public String getTag() {
- return "END_LNGTH";
- }
-
- @Override
- public float getFalseTouchEvaluation(int type, Stroke stroke) {
- return EndPointLengthEvaluator.evaluate(stroke.getEndPointLength());
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthEvaluator.java
deleted file mode 100644
index bb2f1c4..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthEvaluator.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-public class EndPointLengthEvaluator {
- public static float evaluate(float value) {
- float evaluation = 0.0f;
- if (value < 0.05) evaluation += 2.0;
- if (value < 0.1) evaluation += 2.0;
- if (value < 0.2) evaluation += 2.0;
- if (value < 0.3) evaluation += 2.0;
- if (value < 0.4) evaluation += 2.0;
- if (value < 0.5) evaluation += 2.0;
- return evaluation;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java
deleted file mode 100644
index 9b6ddc8..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-/**
- * A classifier which looks at the ratio between the total length covered by the stroke and the
- * distance between the first and last point from this stroke.
- */
-public class EndPointRatioClassifier extends StrokeClassifier {
- public EndPointRatioClassifier(ClassifierData classifierData) {
- mClassifierData = classifierData;
- }
-
- @Override
- public String getTag() {
- return "END_RTIO";
- }
-
- @Override
- public float getFalseTouchEvaluation(int type, Stroke stroke) {
- float ratio;
- if (stroke.getTotalLength() == 0.0f) {
- ratio = 1.0f;
- } else {
- ratio = stroke.getEndPointLength() / stroke.getTotalLength();
- }
- return EndPointRatioEvaluator.evaluate(ratio);
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioEvaluator.java
deleted file mode 100644
index 529fcec..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioEvaluator.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-public class EndPointRatioEvaluator {
- public static float evaluate(float value) {
- float evaluation = 0.0f;
- if (value < 0.85) evaluation++;
- if (value < 0.75) evaluation++;
- if (value < 0.65) evaluation++;
- if (value < 0.55) evaluation++;
- if (value < 0.45) evaluation++;
- if (value < 0.35) evaluation++;
- return evaluation;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java
deleted file mode 100644
index 8105c6a..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-import android.app.ActivityThread;
-import android.app.Application;
-import android.os.Build;
-import android.os.SystemProperties;
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayDeque;
-import java.util.Date;
-import java.util.Locale;
-
-/**
- * Keeps track of interesting falsing data.
- *
- * By default the log only gets collected on userdebug builds. To turn it on on user:
- * adb shell setprop debug.falsing_log true
- *
- * The log gets dumped as part of the SystemUI services. To dump on demand:
- * adb shell dumpsys activity service com.android.systemui StatusBar | grep -A 999 FALSING | less
- *
- * To dump into logcat:
- * adb shell setprop debug.falsing_logcat true
- *
- * To adjust the log buffer size:
- * adb shell setprop debug.falsing_log_size 200
- */
-public class FalsingLog {
- public static final boolean ENABLED = SystemProperties.getBoolean("debug.falsing_log",
- Build.IS_DEBUGGABLE);
- private static final boolean LOGCAT = SystemProperties.getBoolean("debug.falsing_logcat",
- false);
-
- public static final boolean VERBOSE = false;
-
- private static final int MAX_SIZE = SystemProperties.getInt("debug.falsing_log_size", 100);
-
- private static final String TAG = "FalsingLog";
-
- private final ArrayDeque<String> mLog = new ArrayDeque<>(MAX_SIZE);
- private final SimpleDateFormat mFormat = new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US);
-
- private static FalsingLog sInstance;
-
- private FalsingLog() {
- }
-
- public static void v(String tag, String s) {
- if (!VERBOSE) {
- return;
- }
- if (LOGCAT) {
- Log.v(TAG, tag + "\t" + s);
- }
- log("V", tag, s);
- }
-
- public static void i(String tag, String s) {
- if (LOGCAT) {
- Log.i(TAG, tag + "\t" + s);
- }
- log("I", tag, s);
- }
-
- public static void wLogcat(String tag, String s) {
- Log.w(TAG, tag + "\t" + s);
- log("W", tag, s);
- }
-
- public static void w(String tag, String s) {
- if (LOGCAT) {
- Log.w(TAG, tag + "\t" + s);
- }
- log("W", tag, s);
- }
-
- public static void e(String tag, String s) {
- if (LOGCAT) {
- Log.e(TAG, tag + "\t" + s);
- }
- log("E", tag, s);
- }
-
- public static synchronized void log(String level, String tag, String s) {
- if (!ENABLED) {
- return;
- }
- if (sInstance == null) {
- sInstance = new FalsingLog();
- }
-
- if (sInstance.mLog.size() >= MAX_SIZE) {
- sInstance.mLog.removeFirst();
- }
- String entry = new StringBuilder().append(sInstance.mFormat.format(new Date()))
- .append(" ").append(level).append(" ")
- .append(tag).append(" ").append(s).toString();
- sInstance.mLog.add(entry);
- }
-
- public static synchronized void dump(PrintWriter pw) {
- pw.println("FALSING LOG:");
- if (!ENABLED) {
- pw.println("Disabled, to enable: setprop debug.falsing_log 1");
- pw.println();
- return;
- }
- if (sInstance == null || sInstance.mLog.isEmpty()) {
- pw.println("<empty>");
- pw.println();
- return;
- }
- for (String s : sInstance.mLog) {
- pw.println(s);
- }
- pw.println();
- }
-
- public static synchronized void wtf(String tag, String s, Throwable here) {
- if (!ENABLED) {
- return;
- }
- e(tag, s);
-
- Application application = ActivityThread.currentApplication();
- String fileMessage = "";
- if (Build.IS_DEBUGGABLE && application != null) {
- File f = new File(application.getDataDir(), "falsing-"
- + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + ".txt");
- PrintWriter pw = null;
- try {
- pw = new PrintWriter(f);
- dump(pw);
- pw.close();
- fileMessage = "Log written to " + f.getAbsolutePath();
- } catch (IOException e) {
- Log.e(TAG, "Unable to write falsing log", e);
- } finally {
- if (pw != null) {
- pw.close();
- }
- }
- } else {
- Log.e(TAG, "Unable to write log, build must be debuggable.");
- }
-
- Log.wtf(TAG, tag + " " + s + "; " + fileMessage, here);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
index 6961b45..1214843 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -22,6 +22,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.plugins.FalsingManager;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
@@ -29,10 +30,13 @@
*/
public class FalsingManagerFake implements FalsingManager {
private boolean mIsFalseTouch;
+ private boolean mIsFalseTap;
+ private boolean mIsFalseDoubleTap;
private boolean mIsUnlockingDisabled;
private boolean mIsClassiferEnabled;
private boolean mShouldEnforceBouncer;
private boolean mIsReportingEnabled;
+ private boolean mIsFalseRobustTap;
@Override
public void onSuccessfulUnlock() {
@@ -74,6 +78,28 @@
return mIsFalseTouch;
}
+ public void setFalseRobustTap(boolean falseRobustTap) {
+ mIsFalseRobustTap = falseRobustTap;
+ }
+
+ public void setFalseTap(boolean falseTap) {
+ mIsFalseTap = falseTap;
+ }
+
+ public void setFalseDoubleTap(boolean falseDoubleTap) {
+ mIsFalseDoubleTap = falseDoubleTap;
+ }
+
+ @Override
+ public boolean isFalseTap(boolean robustCheck) {
+ return robustCheck ? mIsFalseRobustTap : mIsFalseTap;
+ }
+
+ @Override
+ public boolean isFalseDoubleTap() {
+ return mIsFalseDoubleTap;
+ }
+
@Override
public void onNotificatonStopDraggingDown() {
@@ -236,8 +262,7 @@
}
@Override
- public void dump(PrintWriter pw) {
-
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
deleted file mode 100644
index decaec1..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ /dev/null
@@ -1,579 +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.classifier;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.biometrics.BiometricSourceType;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.Dependency;
-import com.android.systemui.analytics.DataCollector;
-import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.util.sensors.AsyncSensorManager;
-
-import java.io.PrintWriter;
-import java.util.concurrent.Executor;
-
-/**
- * When the phone is locked, listens to touch, sensor and phone events and sends them to
- * DataCollector and HumanInteractionClassifier.
- *
- * It does not collect touch events when the bouncer shows up.
- */
-public class FalsingManagerImpl implements FalsingManager {
- private static final String ENFORCE_BOUNCER = "falsing_manager_enforce_bouncer";
-
- private static final int[] CLASSIFIER_SENSORS = new int[] {
- Sensor.TYPE_PROXIMITY,
- };
-
- private static final int[] COLLECTOR_SENSORS = new int[] {
- Sensor.TYPE_ACCELEROMETER,
- Sensor.TYPE_GYROSCOPE,
- Sensor.TYPE_PROXIMITY,
- Sensor.TYPE_LIGHT,
- Sensor.TYPE_ROTATION_VECTOR,
- };
- public static final String FALSING_REMAIN_LOCKED = "falsing_failure_after_attempts";
- public static final String FALSING_SUCCESS = "falsing_success_after_attempts";
-
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final Context mContext;
-
- private final SensorManager mSensorManager;
- private final DataCollector mDataCollector;
- private final HumanInteractionClassifier mHumanInteractionClassifier;
- private final AccessibilityManager mAccessibilityManager;
- private final Executor mUiBgExecutor;
-
- private boolean mEnforceBouncer = false;
- private boolean mBouncerOn = false;
- private boolean mBouncerOffOnDown = false;
- private boolean mSessionActive = false;
- private boolean mIsTouchScreen = true;
- private boolean mJustUnlockedWithFace = false;
- private int mState = StatusBarState.SHADE;
- private boolean mScreenOn;
- private boolean mShowingAod;
- private Runnable mPendingWtf;
- private int mIsFalseTouchCalls;
- private MetricsLogger mMetricsLogger;
-
- private SensorEventListener mSensorEventListener = new SensorEventListener() {
- @Override
- public synchronized void onSensorChanged(SensorEvent event) {
- mDataCollector.onSensorChanged(event);
- mHumanInteractionClassifier.onSensorChanged(event);
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- mDataCollector.onAccuracyChanged(sensor, accuracy);
- }
- };
-
- public StateListener mStatusBarStateListener = new StateListener() {
- @Override
- public void onStateChanged(int newState) {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("setStatusBarState", new StringBuilder()
- .append("from=").append(StatusBarState.toShortString(mState))
- .append(" to=").append(StatusBarState.toShortString(newState))
- .toString());
- }
- mState = newState;
- updateSessionActive();
- }
- };
-
- protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateConfiguration();
- }
- };
- private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onBiometricAuthenticated(int userId,
- BiometricSourceType biometricSourceType,
- boolean isStrongBiometric) {
- if (userId == KeyguardUpdateMonitor.getCurrentUser()
- && biometricSourceType == BiometricSourceType.FACE) {
- mJustUnlockedWithFace = true;
- }
- }
- };
-
- FalsingManagerImpl(Context context, @UiBackground Executor uiBgExecutor) {
- mContext = context;
- mSensorManager = Dependency.get(AsyncSensorManager.class);
- mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
- mDataCollector = DataCollector.getInstance(mContext);
- mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
- mUiBgExecutor = uiBgExecutor;
- mScreenOn = context.getSystemService(PowerManager.class).isInteractive();
- mMetricsLogger = new MetricsLogger();
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(ENFORCE_BOUNCER), false,
- mSettingsObserver,
- UserHandle.USER_ALL);
-
- updateConfiguration();
- Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
- Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mKeyguardUpdateCallback);
- }
-
- private void updateConfiguration() {
- mEnforceBouncer = 0 != Settings.Secure.getInt(mContext.getContentResolver(),
- ENFORCE_BOUNCER, 0);
- }
-
- private boolean shouldSessionBeActive() {
- if (FalsingLog.ENABLED && FalsingLog.VERBOSE) {
- FalsingLog.v("shouldBeActive", new StringBuilder()
- .append("enabled=").append(isEnabled() ? 1 : 0)
- .append(" mScreenOn=").append(mScreenOn ? 1 : 0)
- .append(" mState=").append(StatusBarState.toShortString(mState))
- .append(" mShowingAod=").append(mShowingAod ? 1 : 0)
- .toString()
- );
- }
- return isEnabled() && mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod;
- }
-
- private boolean sessionEntrypoint() {
- if (!mSessionActive && shouldSessionBeActive()) {
- onSessionStart();
- return true;
- }
- return false;
- }
-
- private void sessionExitpoint(boolean force) {
- if (mSessionActive && (force || !shouldSessionBeActive())) {
- mSessionActive = false;
- if (mIsFalseTouchCalls != 0) {
- if (FalsingLog.ENABLED) {
- FalsingLog.i(
- "isFalseTouchCalls", "Calls before failure: " + mIsFalseTouchCalls);
- }
- mMetricsLogger.histogram(FALSING_REMAIN_LOCKED, mIsFalseTouchCalls);
- mIsFalseTouchCalls = 0;
- }
-
- // This can be expensive, and doesn't need to happen on the main thread.
- mUiBgExecutor.execute(() -> {
- mSensorManager.unregisterListener(mSensorEventListener);
- });
- }
- }
-
- public void updateSessionActive() {
- if (shouldSessionBeActive()) {
- sessionEntrypoint();
- } else {
- sessionExitpoint(false /* force */);
- }
- }
-
- private void onSessionStart() {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onSessionStart", "classifierEnabled=" + isClassifierEnabled());
- clearPendingWtf();
- }
- mBouncerOn = false;
- mSessionActive = true;
- mJustUnlockedWithFace = false;
- mIsFalseTouchCalls = 0;
-
- if (mHumanInteractionClassifier.isEnabled()) {
- registerSensors(CLASSIFIER_SENSORS);
- }
- if (mDataCollector.isEnabledFull()) {
- registerSensors(COLLECTOR_SENSORS);
- }
- if (mDataCollector.isEnabled()) {
- mDataCollector.onFalsingSessionStarted();
- }
- }
-
- private void registerSensors(int [] sensors) {
- for (int sensorType : sensors) {
- Sensor s = mSensorManager.getDefaultSensor(sensorType);
- if (s != null) {
-
- // This can be expensive, and doesn't need to happen on the main thread.
- mUiBgExecutor.execute(() -> {
- mSensorManager.registerListener(
- mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME);
- });
- }
- }
- }
-
- public boolean isClassifierEnabled() {
- return mHumanInteractionClassifier.isEnabled();
- }
-
- private boolean isEnabled() {
- return mHumanInteractionClassifier.isEnabled() || mDataCollector.isEnabled();
- }
-
- public boolean isUnlockingDisabled() {
- return mDataCollector.isUnlockingDisabled();
- }
- /**
- * @return true if the classifier determined that this is not a human interacting with the phone
- */
- public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
- if (FalsingLog.ENABLED) {
- // We're getting some false wtfs from touches that happen after the device went
- // to sleep. Only report missing sessions that happen when the device is interactive.
- if (!mSessionActive && mContext.getSystemService(PowerManager.class).isInteractive()
- && mPendingWtf == null) {
- int enabled = isEnabled() ? 1 : 0;
- int screenOn = mScreenOn ? 1 : 0;
- String state = StatusBarState.toShortString(mState);
- Throwable here = new Throwable("here");
- FalsingLog.wLogcat("isFalseTouch", new StringBuilder()
- .append("Session is not active, yet there's a query for a false touch.")
- .append(" enabled=").append(enabled)
- .append(" mScreenOn=").append(screenOn)
- .append(" mState=").append(state)
- .append(". Escalating to WTF if screen does not turn on soon.")
- .toString());
-
- // Unfortunately we're also getting false positives for touches that happen right
- // after the screen turns on, but before that notification has made it to us.
- // Unfortunately there's no good way to catch that, except to wait and see if we get
- // the screen on notification soon.
- mPendingWtf = () -> FalsingLog.wtf("isFalseTouch", new StringBuilder()
- .append("Session did not become active after query for a false touch.")
- .append(" enabled=").append(enabled)
- .append('/').append(isEnabled() ? 1 : 0)
- .append(" mScreenOn=").append(screenOn)
- .append('/').append(mScreenOn ? 1 : 0)
- .append(" mState=").append(state)
- .append('/').append(StatusBarState.toShortString(mState))
- .append(". Look for warnings ~1000ms earlier to see root cause.")
- .toString(), here);
- mHandler.postDelayed(mPendingWtf, 1000);
- }
- }
- if (ActivityManager.isRunningInUserTestHarness()) {
- // This is a test device running UiAutomator code.
- return false;
- }
- if (mAccessibilityManager.isTouchExplorationEnabled()) {
- // Touch exploration triggers false positives in the classifier and
- // already sufficiently prevents false unlocks.
- return false;
- }
- if (!mIsTouchScreen) {
- // Unlocking with input devices besides the touchscreen should already be sufficiently
- // anti-falsed.
- return false;
- }
- if (mJustUnlockedWithFace) {
- // Unlocking with face is a strong user presence signal, we can assume the user
- // is present until the next session starts.
- return false;
- }
- mIsFalseTouchCalls++;
- boolean isFalse = mHumanInteractionClassifier.isFalseTouch();
- if (!isFalse) {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("isFalseTouchCalls", "Calls before success: " + mIsFalseTouchCalls);
- }
- mMetricsLogger.histogram(FALSING_SUCCESS, mIsFalseTouchCalls);
- mIsFalseTouchCalls = 0;
- }
- return isFalse;
- }
-
- private void clearPendingWtf() {
- if (mPendingWtf != null) {
- mHandler.removeCallbacks(mPendingWtf);
- mPendingWtf = null;
- }
- }
-
-
- public boolean shouldEnforceBouncer() {
- return mEnforceBouncer;
- }
-
- public void setShowingAod(boolean showingAod) {
- mShowingAod = showingAod;
- updateSessionActive();
- }
-
- public void onScreenTurningOn() {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onScreenTurningOn", new StringBuilder()
- .append("from=").append(mScreenOn ? 1 : 0)
- .toString());
- clearPendingWtf();
- }
- mScreenOn = true;
- if (sessionEntrypoint()) {
- mDataCollector.onScreenTurningOn();
- }
- }
-
- public void onScreenOnFromTouch() {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onScreenOnFromTouch", new StringBuilder()
- .append("from=").append(mScreenOn ? 1 : 0)
- .toString());
- }
- mScreenOn = true;
- if (sessionEntrypoint()) {
- mDataCollector.onScreenOnFromTouch();
- }
- }
-
- public void onScreenOff() {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onScreenOff", new StringBuilder()
- .append("from=").append(mScreenOn ? 1 : 0)
- .toString());
- }
- mDataCollector.onScreenOff();
- mScreenOn = false;
- sessionExitpoint(false /* force */);
- }
-
- public void onSuccessfulUnlock() {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onSucccessfulUnlock", "");
- }
- mDataCollector.onSucccessfulUnlock();
- }
-
- public void onBouncerShown() {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onBouncerShown", new StringBuilder()
- .append("from=").append(mBouncerOn ? 1 : 0)
- .toString());
- }
- if (!mBouncerOn) {
- mBouncerOn = true;
- mDataCollector.onBouncerShown();
- }
- }
-
- public void onBouncerHidden() {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onBouncerHidden", new StringBuilder()
- .append("from=").append(mBouncerOn ? 1 : 0)
- .toString());
- }
- if (mBouncerOn) {
- mBouncerOn = false;
- mDataCollector.onBouncerHidden();
- }
- }
-
- public void onQsDown() {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onQsDown", "");
- }
- mHumanInteractionClassifier.setType(Classifier.QUICK_SETTINGS);
- mDataCollector.onQsDown();
- }
-
- public void setQsExpanded(boolean expanded) {
- mDataCollector.setQsExpanded(expanded);
- }
-
- public void onTrackingStarted(boolean secure) {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onTrackingStarted", "");
- }
- mHumanInteractionClassifier.setType(secure
- ? Classifier.BOUNCER_UNLOCK : Classifier.UNLOCK);
- mDataCollector.onTrackingStarted();
- }
-
- public void onTrackingStopped() {
- mDataCollector.onTrackingStopped();
- }
-
- public void onNotificationActive() {
- mDataCollector.onNotificationActive();
- }
-
- public void onNotificationDoubleTap(boolean accepted, float dx, float dy) {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onNotificationDoubleTap", "accepted=" + accepted
- + " dx=" + dx + " dy=" + dy + " (px)");
- }
- mDataCollector.onNotificationDoubleTap();
- }
-
- public void setNotificationExpanded() {
- mDataCollector.setNotificationExpanded();
- }
-
- public void onNotificatonStartDraggingDown() {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onNotificatonStartDraggingDown", "");
- }
- mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DRAG_DOWN);
- mDataCollector.onNotificatonStartDraggingDown();
- }
-
- public void onStartExpandingFromPulse() {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onStartExpandingFromPulse", "");
- }
- mHumanInteractionClassifier.setType(Classifier.PULSE_EXPAND);
- mDataCollector.onStartExpandingFromPulse();
- }
-
- public void onNotificatonStopDraggingDown() {
- mDataCollector.onNotificatonStopDraggingDown();
- }
-
- public void onExpansionFromPulseStopped() {
- mDataCollector.onExpansionFromPulseStopped();
- }
-
- public void onNotificationDismissed() {
- mDataCollector.onNotificationDismissed();
- }
-
- public void onNotificationStartDismissing() {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onNotificationStartDismissing", "");
- }
- mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DISMISS);
- mDataCollector.onNotificatonStartDismissing();
- }
-
- public void onNotificationStopDismissing() {
- mDataCollector.onNotificatonStopDismissing();
- }
-
- public void onCameraOn() {
- mDataCollector.onCameraOn();
- }
-
- public void onLeftAffordanceOn() {
- mDataCollector.onLeftAffordanceOn();
- }
-
- public void onAffordanceSwipingStarted(boolean rightCorner) {
- if (FalsingLog.ENABLED) {
- FalsingLog.i("onAffordanceSwipingStarted", "");
- }
- if (rightCorner) {
- mHumanInteractionClassifier.setType(Classifier.RIGHT_AFFORDANCE);
- } else {
- mHumanInteractionClassifier.setType(Classifier.LEFT_AFFORDANCE);
- }
- mDataCollector.onAffordanceSwipingStarted(rightCorner);
- }
-
- public void onAffordanceSwipingAborted() {
- mDataCollector.onAffordanceSwipingAborted();
- }
-
- public void onUnlockHintStarted() {
- mDataCollector.onUnlockHintStarted();
- }
-
- public void onCameraHintStarted() {
- mDataCollector.onCameraHintStarted();
- }
-
- public void onLeftAffordanceHintStarted() {
- mDataCollector.onLeftAffordanceHintStarted();
- }
-
- public void onTouchEvent(MotionEvent event, int width, int height) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- mIsTouchScreen = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
- // If the bouncer was not shown during the down event,
- // we want the entire gesture going to HumanInteractionClassifier
- mBouncerOffOnDown = !mBouncerOn;
- }
- if (mSessionActive) {
- if (!mBouncerOn) {
- // In case bouncer is "visible", but onFullyShown has not yet been called,
- // avoid adding the event to DataCollector
- mDataCollector.onTouchEvent(event, width, height);
- }
- if (mBouncerOffOnDown) {
- mHumanInteractionClassifier.onTouchEvent(event);
- }
- }
- }
-
- public void dump(PrintWriter pw) {
- pw.println("FALSING MANAGER");
- pw.print("classifierEnabled="); pw.println(isClassifierEnabled() ? 1 : 0);
- pw.print("mSessionActive="); pw.println(mSessionActive ? 1 : 0);
- pw.print("mBouncerOn="); pw.println(mSessionActive ? 1 : 0);
- pw.print("mState="); pw.println(StatusBarState.toShortString(mState));
- pw.print("mScreenOn="); pw.println(mScreenOn ? 1 : 0);
- pw.println();
- }
-
- @Override
- public void cleanup() {
- mSensorManager.unregisterListener(mSensorEventListener);
- mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
- Dependency.get(StatusBarStateController.class).removeCallback(mStatusBarStateListener);
- Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mKeyguardUpdateCallback);
- }
-
- public Uri reportRejectedTouch() {
- if (mDataCollector.isEnabled()) {
- return mDataCollector.reportRejectedTouch();
- }
- return null;
- }
-
- public boolean isReportingEnabled() {
- return mDataCollector.isReportingEnabled();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 2c31862..814fff9 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -16,13 +16,13 @@
package com.android.systemui.classifier;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_MANAGER_ENABLED;
-
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.SensorManager;
import android.net.Uri;
import android.provider.DeviceConfig;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
@@ -33,7 +33,6 @@
import com.android.systemui.classifier.brightline.FalsingDataProvider;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.FalsingManager;
@@ -53,52 +52,63 @@
/**
* Simple passthrough implementation of {@link FalsingManager} allowing plugins to swap in.
*
- * {@link FalsingManagerImpl} is used when a Plugin is not loaded.
+ * {@link BrightLineFalsingManager} is used when a Plugin is not loaded.
*/
@SysUISingleton
public class FalsingManagerProxy implements FalsingManager, Dumpable {
private static final String PROXIMITY_SENSOR_TAG = "FalsingManager";
+ private static final String DUMPABLE_TAG = "FalsingManager";
+ public static final String FALSING_REMAIN_LOCKED = "falsing_failure_after_attempts";
+ public static final String FALSING_SUCCESS = "falsing_success_after_attempts";
+ private final PluginManager mPluginManager;
private final ProximitySensor mProximitySensor;
+ private final Resources mResources;
+ private final ViewConfiguration mViewConfiguration;
private final FalsingDataProvider mFalsingDataProvider;
- private FalsingManager mInternalFalsingManager;
- private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener;
private final DeviceConfigProxy mDeviceConfig;
- private boolean mBrightlineEnabled;
private final DockManager mDockManager;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private Executor mUiBgExecutor;
+ private final DumpManager mDumpManager;
private final StatusBarStateController mStatusBarStateController;
+ final PluginListener<FalsingPlugin> mPluginListener;
+
+ private FalsingManager mInternalFalsingManager;
+
+ private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
+ properties -> onDeviceConfigPropertiesChanged(properties.getNamespace());
@Inject
- FalsingManagerProxy(Context context, PluginManager pluginManager, @Main Executor executor,
+ FalsingManagerProxy(PluginManager pluginManager, @Main Executor executor,
ProximitySensor proximitySensor,
DeviceConfigProxy deviceConfig, DockManager dockManager,
KeyguardUpdateMonitor keyguardUpdateMonitor,
DumpManager dumpManager,
- @UiBackground Executor uiBgExecutor,
StatusBarStateController statusBarStateController,
+ @Main Resources resources,
+ ViewConfiguration viewConfiguration,
FalsingDataProvider falsingDataProvider) {
+ mPluginManager = pluginManager;
mProximitySensor = proximitySensor;
mDockManager = dockManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mUiBgExecutor = uiBgExecutor;
+ mDumpManager = dumpManager;
mStatusBarStateController = statusBarStateController;
+ mResources = resources;
+ mViewConfiguration = viewConfiguration;
mFalsingDataProvider = falsingDataProvider;
mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
mProximitySensor.setDelay(SensorManager.SENSOR_DELAY_GAME);
mDeviceConfig = deviceConfig;
- mDeviceConfigListener =
- properties -> onDeviceConfigPropertiesChanged(context, properties.getNamespace());
- setupFalsingManager(context);
+ setupFalsingManager();
mDeviceConfig.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_SYSTEMUI,
executor,
mDeviceConfigListener
);
- final PluginListener<FalsingPlugin> mPluginListener = new PluginListener<FalsingPlugin>() {
+ mPluginListener = new PluginListener<FalsingPlugin>() {
public void onPluginConnected(FalsingPlugin plugin, Context context) {
FalsingManager pluginFalsingManager = plugin.getFalsingManager(context);
if (pluginFalsingManager != null) {
@@ -108,49 +118,42 @@
}
public void onPluginDisconnected(FalsingPlugin plugin) {
- mInternalFalsingManager = new FalsingManagerImpl(context, mUiBgExecutor);
+ setupFalsingManager();
}
};
- pluginManager.addPluginListener(mPluginListener, FalsingPlugin.class);
+ mPluginManager.addPluginListener(mPluginListener, FalsingPlugin.class);
- dumpManager.registerDumpable("FalsingManager", this);
+ mDumpManager.registerDumpable(DUMPABLE_TAG, this);
}
- private void onDeviceConfigPropertiesChanged(Context context, String namespace) {
+ private void onDeviceConfigPropertiesChanged(String namespace) {
if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(namespace)) {
return;
}
- setupFalsingManager(context);
+ setupFalsingManager();
}
/**
- * Chooses the FalsingManager implementation.
+ * Setup the FalsingManager implementation.
+ *
+ * If multiple implementations are available, this is where the choice is made.
*/
- private void setupFalsingManager(Context context) {
- boolean brightlineEnabled = mDeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_MANAGER_ENABLED, true);
- if (brightlineEnabled == mBrightlineEnabled && mInternalFalsingManager != null) {
- return;
- }
- mBrightlineEnabled = brightlineEnabled;
-
+ private void setupFalsingManager() {
if (mInternalFalsingManager != null) {
mInternalFalsingManager.cleanup();
}
- if (!brightlineEnabled) {
- mInternalFalsingManager = new FalsingManagerImpl(context, mUiBgExecutor);
- } else {
- mInternalFalsingManager = new BrightLineFalsingManager(
- mFalsingDataProvider,
- mKeyguardUpdateMonitor,
- mProximitySensor,
- mDeviceConfig,
- mDockManager,
- mStatusBarStateController
- );
- }
+ mInternalFalsingManager = new BrightLineFalsingManager(
+ mFalsingDataProvider,
+ mKeyguardUpdateMonitor,
+ mProximitySensor,
+ mDeviceConfig,
+ mResources,
+ mViewConfiguration,
+ mDockManager,
+ mStatusBarStateController
+ );
}
/**
@@ -191,6 +194,17 @@
return mInternalFalsingManager.isFalseTouch(interactionType);
}
+
+ @Override
+ public boolean isFalseTap(boolean robustCheck) {
+ return mInternalFalsingManager.isFalseTap(robustCheck);
+ }
+
+ @Override
+ public boolean isFalseDoubleTap() {
+ return mInternalFalsingManager.isFalseDoubleTap();
+ }
+
@Override
public void onNotificatonStopDraggingDown() {
mInternalFalsingManager.onNotificatonStartDraggingDown();
@@ -338,17 +352,14 @@
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
- mInternalFalsingManager.dump(pw);
- }
-
- @Override
- public void dump(PrintWriter pw) {
- mInternalFalsingManager.dump(pw);
+ mInternalFalsingManager.dump(fd, pw, args);
}
@Override
public void cleanup() {
mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener);
+ mPluginManager.removePluginListener(mPluginListener);
+ mDumpManager.unregisterDumpable(DUMPABLE_TAG);
mInternalFalsingManager.cleanup();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/GestureClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/GestureClassifier.java
deleted file mode 100644
index 11388fc..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/GestureClassifier.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-/**
- * An abstract class for classifiers which classify the whole gesture (all the strokes which
- * occurred from DOWN event to UP/CANCEL event)
- */
-public abstract class GestureClassifier extends Classifier {
-
- /**
- * @param type the type of action for which this method is called
- * @return a non-negative value which is used to determine whether the most recent gesture is a
- * false interaction; the bigger the value the greater the chance that this a false
- * interaction.
- */
- public abstract float getFalseTouchEvaluation(int type);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java
deleted file mode 100644
index 4c64711..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-import android.os.SystemClock;
-
-import java.util.ArrayList;
-
-/**
- * Holds the evaluations for ended strokes and gestures. These values are decreased through time.
- */
-public class HistoryEvaluator {
- private static final float INTERVAL = 50.0f;
- private static final float HISTORY_FACTOR = 0.9f;
- private static final float EPSILON = 1e-5f;
-
- private final ArrayList<Data> mStrokes = new ArrayList<>();
- private final ArrayList<Data> mGestureWeights = new ArrayList<>();
- private long mLastUpdate;
-
- public HistoryEvaluator() {
- mLastUpdate = SystemClock.elapsedRealtime();
- }
-
- public void addStroke(float evaluation) {
- decayValue();
- mStrokes.add(new Data(evaluation));
- }
-
- public void addGesture(float evaluation) {
- decayValue();
- mGestureWeights.add(new Data(evaluation));
- }
-
- /**
- * Calculates the weighted average of strokes and adds to it the weighted average of gestures
- */
- public float getEvaluation() {
- return weightedAverage(mStrokes) + weightedAverage(mGestureWeights);
- }
-
- private float weightedAverage(ArrayList<Data> list) {
- float sumValue = 0.0f;
- float sumWeight = 0.0f;
- int size = list.size();
- for (int i = 0; i < size; i++) {
- Data data = list.get(i);
- sumValue += data.evaluation * data.weight;
- sumWeight += data.weight;
- }
-
- if (sumWeight == 0.0f) {
- return 0.0f;
- }
-
- return sumValue / sumWeight;
- }
-
- private void decayValue() {
- long time = SystemClock.elapsedRealtime();
-
- if (time <= mLastUpdate) {
- return;
- }
-
- // All weights are multiplied by HISTORY_FACTOR after each INTERVAL milliseconds.
- float factor = (float) Math.pow(HISTORY_FACTOR, (time - mLastUpdate) / INTERVAL);
-
- decayValue(mStrokes, factor);
- decayValue(mGestureWeights, factor);
- mLastUpdate = time;
- }
-
- private void decayValue(ArrayList<Data> list, float factor) {
- int size = list.size();
- for (int i = 0; i < size; i++) {
- list.get(i).weight *= factor;
- }
-
- // Removing evaluations with such small weights that they do not matter anymore
- while (!list.isEmpty() && isZero(list.get(0).weight)) {
- list.remove(0);
- }
- }
-
- private boolean isZero(float x) {
- return x <= EPSILON && x >= -EPSILON;
- }
-
- /**
- * For each stroke it holds its initial value and the current weight. Initially the
- * weight is set to 1.0
- */
- private static class Data {
- public float evaluation;
- public float weight;
-
- public Data(float evaluation) {
- this.evaluation = evaluation;
- weight = 1.0f;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
deleted file mode 100644
index 86dccb2..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.hardware.SensorEvent;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.DisplayMetrics;
-import android.view.MotionEvent;
-
-import com.android.systemui.R;
-
-import java.util.ArrayDeque;
-
-/**
- * An classifier trying to determine whether it is a human interacting with the phone or not.
- */
-public class HumanInteractionClassifier extends Classifier {
- private static final String HIC_ENABLE = "HIC_enable";
- private static final float FINGER_DISTANCE = 0.1f;
-
- private static HumanInteractionClassifier sInstance = null;
-
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final Context mContext;
-
- private final StrokeClassifier[] mStrokeClassifiers;
- private final GestureClassifier[] mGestureClassifiers;
- private final ArrayDeque<MotionEvent> mBufferedEvents = new ArrayDeque<>();
- private final HistoryEvaluator mHistoryEvaluator;
- private final float mDpi;
-
- private boolean mEnableClassifier = false;
- private int mCurrentType = Classifier.GENERIC;
-
- protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateConfiguration();
- }
- };
-
- private HumanInteractionClassifier(Context context) {
- mContext = context;
- DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
-
- // If the phone is rotated to landscape, the calculations would be wrong if xdpi and ydpi
- // were to be used separately. Due negligible differences in xdpi and ydpi we can just
- // take the average.
- // Note that xdpi and ydpi are the physical pixels per inch and are not affected by scaling.
- mDpi = (displayMetrics.xdpi + displayMetrics.ydpi) / 2.0f;
- mClassifierData = new ClassifierData(mDpi);
- mHistoryEvaluator = new HistoryEvaluator();
-
- mStrokeClassifiers = new StrokeClassifier[]{
- new AnglesClassifier(mClassifierData),
- new SpeedClassifier(mClassifierData),
- new DurationCountClassifier(mClassifierData),
- new EndPointRatioClassifier(mClassifierData),
- new EndPointLengthClassifier(mClassifierData),
- new AccelerationClassifier(mClassifierData),
- new SpeedAnglesClassifier(mClassifierData),
- new LengthCountClassifier(mClassifierData),
- new DirectionClassifier(mClassifierData),
- };
-
- mGestureClassifiers = new GestureClassifier[] {
- new PointerCountClassifier(mClassifierData),
- new ProximityClassifier(mClassifierData)
- };
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(HIC_ENABLE), false,
- mSettingsObserver,
- UserHandle.USER_ALL);
-
- updateConfiguration();
- }
-
- public static HumanInteractionClassifier getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new HumanInteractionClassifier(context);
- }
- return sInstance;
- }
-
- private void updateConfiguration() {
- boolean defaultValue = mContext.getResources().getBoolean(
- R.bool.config_lockscreenAntiFalsingClassifierEnabled);
-
- mEnableClassifier = 0 != Settings.Global.getInt(
- mContext.getContentResolver(),
- HIC_ENABLE, defaultValue ? 1 : 0);
- }
-
- public void setType(int type) {
- mCurrentType = type;
- }
-
- @Override
- public void onTouchEvent(MotionEvent event) {
- if (!mEnableClassifier) {
- return;
- }
-
- // If the user is dragging down the notification, they might want to drag it down
- // enough to see the content, read it for a while and then lift the finger to open
- // the notification. This kind of motion scores very bad in the Classifier so the
- // MotionEvents which are close to the current position of the finger are not
- // sent to the classifiers until the finger moves far enough. When the finger if lifted
- // up, the last MotionEvent which was far enough from the finger is set as the final
- // MotionEvent and sent to the Classifiers.
- if (mCurrentType == Classifier.NOTIFICATION_DRAG_DOWN
- || mCurrentType == Classifier.PULSE_EXPAND) {
- mBufferedEvents.add(MotionEvent.obtain(event));
- Point pointEnd = new Point(event.getX() / mDpi, event.getY() / mDpi);
-
- while (pointEnd.dist(new Point(mBufferedEvents.getFirst().getX() / mDpi,
- mBufferedEvents.getFirst().getY() / mDpi)) > FINGER_DISTANCE) {
- addTouchEvent(mBufferedEvents.getFirst());
- mBufferedEvents.remove();
- }
-
- int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_UP) {
- mBufferedEvents.getFirst().setAction(MotionEvent.ACTION_UP);
- addTouchEvent(mBufferedEvents.getFirst());
- mBufferedEvents.clear();
- }
- } else {
- addTouchEvent(event);
- }
- }
-
- private void addTouchEvent(MotionEvent event) {
- if (!mClassifierData.update(event)) {
- return;
- }
-
- for (StrokeClassifier c : mStrokeClassifiers) {
- c.onTouchEvent(event);
- }
-
- for (GestureClassifier c : mGestureClassifiers) {
- c.onTouchEvent(event);
- }
-
- int size = mClassifierData.getEndingStrokes().size();
- for (int i = 0; i < size; i++) {
- Stroke stroke = mClassifierData.getEndingStrokes().get(i);
- float evaluation = 0.0f;
- StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("stroke") : null;
- for (StrokeClassifier c : mStrokeClassifiers) {
- float e = c.getFalseTouchEvaluation(mCurrentType, stroke);
- if (FalsingLog.ENABLED) {
- String tag = c.getTag();
- sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
- }
- evaluation += e;
- }
-
- if (FalsingLog.ENABLED) {
- FalsingLog.i(" addTouchEvent", sb.toString());
- }
- mHistoryEvaluator.addStroke(evaluation);
- }
-
- int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- float evaluation = 0.0f;
- StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("gesture") : null;
- for (GestureClassifier c : mGestureClassifiers) {
- float e = c.getFalseTouchEvaluation(mCurrentType);
- if (FalsingLog.ENABLED) {
- String tag = c.getTag();
- sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
- }
- evaluation += e;
- }
- if (FalsingLog.ENABLED) {
- FalsingLog.i(" addTouchEvent", sb.toString());
- }
- mHistoryEvaluator.addGesture(evaluation);
- setType(Classifier.GENERIC);
- }
-
- mClassifierData.cleanUp(event);
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- for (Classifier c : mStrokeClassifiers) {
- c.onSensorChanged(event);
- }
-
- for (Classifier c : mGestureClassifiers) {
- c.onSensorChanged(event);
- }
- }
-
- public boolean isFalseTouch() {
- if (mEnableClassifier) {
- float evaluation = mHistoryEvaluator.getEvaluation();
- boolean result = evaluation >= 5.0f;
- if (FalsingLog.ENABLED) {
- FalsingLog.i("isFalseTouch", new StringBuilder()
- .append("eval=").append(evaluation).append(" result=")
- .append(result ? 1 : 0).toString());
- }
- return result;
- }
- return false;
- }
-
- public boolean isEnabled() {
- return mEnableClassifier;
- }
-
- @Override
- public String getTag() {
- return "HIC";
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/LengthCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/LengthCountClassifier.java
deleted file mode 100644
index 53678a6..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/LengthCountClassifier.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-/**
- * A classifier which looks at the ratio between the length of the stroke and its number of
- * points. The number of points is subtracted by 2 because the UP event comes in with some delay
- * and it should not influence the ratio and also strokes which are long and have a small number
- * of points are punished more (these kind of strokes are usually bad ones and they tend to score
- * well in other classifiers).
- */
-public class LengthCountClassifier extends StrokeClassifier {
- public LengthCountClassifier(ClassifierData classifierData) {
- }
-
- @Override
- public String getTag() {
- return "LEN_CNT";
- }
-
- @Override
- public float getFalseTouchEvaluation(int type, Stroke stroke) {
- return LengthCountEvaluator.evaluate(stroke.getTotalLength()
- / Math.max(1.0f, stroke.getCount() - 2));
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/LengthCountEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/LengthCountEvaluator.java
deleted file mode 100644
index dac7a6f..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/LengthCountEvaluator.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-/**
- * A classifier which looks at the ratio between the length of the stroke and its number of
- * points.
- */
-public class LengthCountEvaluator {
- public static float evaluate(float value) {
- float evaluation = 0.0f;
- if (value < 0.09) evaluation++;
- if (value < 0.05) evaluation++;
- if (value < 0.02) evaluation++;
- if (value > 0.6) evaluation++;
- if (value > 0.9) evaluation++;
- if (value > 1.2) evaluation++;
- return evaluation;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Point.java b/packages/SystemUI/src/com/android/systemui/classifier/Point.java
deleted file mode 100644
index f3dc2be..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/Point.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-public class Point {
- public float x;
- public float y;
- public long timeOffsetNano;
-
- public Point(float x, float y) {
- this.x = x;
- this.y = y;
- this.timeOffsetNano = 0;
- }
-
- public Point(float x, float y, long timeOffsetNano) {
- this.x = x;
- this.y = y;
- this.timeOffsetNano = timeOffsetNano;
- }
-
- public boolean equals(Point p) {
- return x == p.x && y == p.y;
- }
-
- public float dist(Point a) {
- return (float) Math.hypot(a.x - x, a.y - y);
- }
-
- /**
- * Calculates the cross product of vec(this, a) and vec(this, b) where vec(x,y) is the
- * vector from point x to point y
- */
- public float crossProduct(Point a, Point b) {
- return (a.x - x) * (b.y - y) - (a.y - y) * (b.x - x);
- }
-
- /**
- * Calculates the dot product of vec(this, a) and vec(this, b) where vec(x,y) is the
- * vector from point x to point y
- */
- public float dotProduct(Point a, Point b) {
- return (a.x - x) * (b.x - x) + (a.y - y) * (b.y - y);
- }
-
- /**
- * Calculates the angle in radians created by points (a, this, b). If any two of these points
- * are the same, the method will return 0.0f
- *
- * @return the angle in radians
- */
- public float getAngle(Point a, Point b) {
- float dist1 = dist(a);
- float dist2 = dist(b);
-
- if (dist1 == 0.0f || dist2 == 0.0f) {
- return 0.0f;
- }
-
- float crossProduct = crossProduct(a, b);
- float dotProduct = dotProduct(a, b);
- float cos = Math.min(1.0f, Math.max(-1.0f, dotProduct / dist1 / dist2));
- float angle = (float) Math.acos(cos);
- if (crossProduct < 0.0) {
- angle = 2.0f * (float) Math.PI - angle;
- }
- return angle;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java
deleted file mode 100644
index 136c433..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-import android.view.MotionEvent;
-
-/**
- * A classifier which looks at the total number of traces in the whole gesture.
- */
-public class PointerCountClassifier extends GestureClassifier {
- private int mCount;
-
- public PointerCountClassifier(ClassifierData classifierData) {
- mCount = 0;
- }
-
- @Override
- public String getTag() {
- return "PTR_CNT";
- }
-
- @Override
- public void onTouchEvent(MotionEvent event) {
- int action = event.getActionMasked();
-
- if (action == MotionEvent.ACTION_DOWN) {
- mCount = 1;
- }
-
- if (action == MotionEvent.ACTION_POINTER_DOWN) {
- ++mCount;
- }
- }
-
- @Override
- public float getFalseTouchEvaluation(int type) {
- return PointerCountEvaluator.evaluate(mCount);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
deleted file mode 100644
index 62adfc8..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.view.MotionEvent;
-
-/**
- * A classifier which looks at the proximity sensor during the gesture. It calculates the percentage
- * the proximity sensor showing the near state during the whole gesture
- */
-public class ProximityClassifier extends GestureClassifier {
- private long mGestureStartTimeNano;
- private long mNearStartTimeNano;
- private long mNearDuration;
- private boolean mNear;
- private float mAverageNear;
-
- public ProximityClassifier(ClassifierData classifierData) {
- }
-
- @Override
- public String getTag() {
- return "PROX";
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
- update(event.values[0] < event.sensor.getMaximumRange(), event.timestamp);
- }
- }
-
- @Override
- public void onTouchEvent(MotionEvent event) {
- int action = event.getActionMasked();
-
- if (action == MotionEvent.ACTION_DOWN) {
- mGestureStartTimeNano = event.getEventTimeNano();
- mNearStartTimeNano = event.getEventTimeNano();
- mNearDuration = 0;
- }
-
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- update(mNear, event.getEventTimeNano());
- long duration = event.getEventTimeNano() - mGestureStartTimeNano;
-
- if (duration == 0) {
- mAverageNear = mNear ? 1.0f : 0.0f;
- } else {
- mAverageNear = (float) mNearDuration / (float) duration;
- }
- }
- }
-
-
- /**
- * @param near is the sensor showing the near state right now
- * @param timestampNano time of this event in nanoseconds
- */
- private void update(boolean near, long timestampNano) {
- // This if is necessary because MotionEvents and SensorEvents do not come in
- // chronological order
- if (timestampNano > mNearStartTimeNano) {
- // if the state before was near then add the difference of the current time and
- // mNearStartTimeNano to mNearDuration.
- if (mNear) {
- mNearDuration += timestampNano - mNearStartTimeNano;
- }
-
- // if the new state is near, set mNearStartTimeNano equal to this moment.
- if (near) {
- mNearStartTimeNano = timestampNano;
- }
- }
- mNear = near;
- }
-
- @Override
- public float getFalseTouchEvaluation(int type) {
- return ProximityEvaluator.evaluate(mAverageNear, type);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityEvaluator.java
deleted file mode 100644
index 91002bf..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityEvaluator.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-public class ProximityEvaluator {
- public static float evaluate(float value, int type) {
- float evaluation = 0.0f;
- float threshold = 0.1f;
- if (type == Classifier.QUICK_SETTINGS) {
- threshold = 1.0f;
- }
- if (value >= threshold) evaluation += 2.0;
- return evaluation;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java
deleted file mode 100644
index 66f0cf6..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-import android.os.Build;
-import android.os.SystemProperties;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * A classifier which for each point from a stroke, it creates a point on plane with coordinates
- * (timeOffsetNano, distanceCoveredUpToThisPoint) (scaled by DURATION_SCALE and LENGTH_SCALE)
- * and then it calculates the angle variance of these points like the class
- * {@link AnglesClassifier} (without splitting it into two parts). The classifier ignores
- * the last point of a stroke because the UP event comes in with some delay and this ruins the
- * smoothness of this curve. Additionally, the classifier classifies calculates the percentage of
- * angles which value is in [PI - ANGLE_DEVIATION, 2* PI) interval. The reason why the classifier
- * does that is because the speed of a good stroke is most often increases, so most of these angels
- * should be in this interval.
- */
-public class SpeedAnglesClassifier extends StrokeClassifier {
- public static final boolean VERBOSE = SystemProperties.getBoolean("debug.falsing_log.spd_ang",
- Build.IS_DEBUGGABLE);
- public static final String TAG = "SPD_ANG";
-
- private HashMap<Stroke, Data> mStrokeMap = new HashMap<>();
-
- public SpeedAnglesClassifier(ClassifierData classifierData) {
- mClassifierData = classifierData;
- }
-
- @Override
- public String getTag() {
- return TAG;
- }
-
- @Override
- public void onTouchEvent(MotionEvent event) {
- int action = event.getActionMasked();
-
- if (action == MotionEvent.ACTION_DOWN) {
- mStrokeMap.clear();
- }
-
- for (int i = 0; i < event.getPointerCount(); i++) {
- Stroke stroke = mClassifierData.getStroke(event.getPointerId(i));
-
- if (mStrokeMap.get(stroke) == null) {
- mStrokeMap.put(stroke, new Data());
- }
-
- if (action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_CANCEL
- && !(action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) {
- mStrokeMap.get(stroke).addPoint(
- stroke.getPoints().get(stroke.getPoints().size() - 1));
- }
- }
- }
-
- @Override
- public float getFalseTouchEvaluation(int type, Stroke stroke) {
- Data data = mStrokeMap.get(stroke);
- return SpeedVarianceEvaluator.evaluate(data.getAnglesVariance())
- + SpeedAnglesPercentageEvaluator.evaluate(data.getAnglesPercentage());
- }
-
- private static class Data {
- private final float DURATION_SCALE = 1e8f;
- private final float LENGTH_SCALE = 1.0f;
- private final float ANGLE_DEVIATION = (float) Math.PI / 10.0f;
-
- private List<Point> mLastThreePoints = new ArrayList<>();
- private Point mPreviousPoint;
- private float mPreviousAngle;
- private float mSumSquares;
- private float mSum;
- private float mCount;
- private float mDist;
- private float mAnglesCount;
- private float mAcceleratingAngles;
-
- public Data() {
- mPreviousPoint = null;
- mPreviousAngle = (float) Math.PI;
- mSumSquares = 0.0f;
- mSum = 0.0f;
- mCount = 1.0f;
- mDist = 0.0f;
- mAnglesCount = mAcceleratingAngles = 0.0f;
- }
-
- public void addPoint(Point point) {
- if (mPreviousPoint != null) {
- mDist += mPreviousPoint.dist(point);
- }
-
- mPreviousPoint = point;
- Point speedPoint = new Point((float) point.timeOffsetNano / DURATION_SCALE,
- mDist / LENGTH_SCALE);
-
- // Checking if the added point is different than the previously added point
- // Repetitions are being ignored so that proper angles are calculated.
- if (mLastThreePoints.isEmpty()
- || !mLastThreePoints.get(mLastThreePoints.size() - 1).equals(speedPoint)) {
- mLastThreePoints.add(speedPoint);
- if (mLastThreePoints.size() == 4) {
- mLastThreePoints.remove(0);
-
- float angle = mLastThreePoints.get(1).getAngle(mLastThreePoints.get(0),
- mLastThreePoints.get(2));
-
- mAnglesCount++;
- if (angle >= (float) Math.PI - ANGLE_DEVIATION) {
- mAcceleratingAngles++;
- }
-
- float difference = angle - mPreviousAngle;
- mSum += difference;
- mSumSquares += difference * difference;
- mCount += 1.0;
- mPreviousAngle = angle;
- }
- }
- }
-
- public float getAnglesVariance() {
- final float v = mSumSquares / mCount - (mSum / mCount) * (mSum / mCount);
- if (VERBOSE) {
- FalsingLog.i(TAG, "getAnglesVariance: sum^2=" + mSumSquares
- + " count=" + mCount + " result=" + v);
- }
- return v;
- }
-
- public float getAnglesPercentage() {
- if (mAnglesCount == 0.0f) {
- return 1.0f;
- }
- final float v = (mAcceleratingAngles) / mAnglesCount;
- if (VERBOSE) {
- FalsingLog.i(TAG, "getAnglesPercentage: angles=" + mAcceleratingAngles
- + " count=" + mAnglesCount + " result=" + v);
- }
- return v;
- }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesPercentageEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesPercentageEvaluator.java
deleted file mode 100644
index d50d406..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesPercentageEvaluator.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-public class SpeedAnglesPercentageEvaluator {
- public static float evaluate(float value) {
- float evaluation = 0.0f;
- if (value < 1.00) evaluation++;
- if (value < 0.90) evaluation++;
- if (value < 0.70) evaluation++;
- return evaluation;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedClassifier.java
deleted file mode 100644
index 01fcc37..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedClassifier.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-/**
- * A classifier that looks at the speed of the stroke. It calculates the speed of a stroke in
- * inches per second.
- */
-public class SpeedClassifier extends StrokeClassifier {
- private final float NANOS_TO_SECONDS = 1e9f;
-
- public SpeedClassifier(ClassifierData classifierData) {
- }
-
- @Override
- public String getTag() {
- return "SPD";
- }
-
- @Override
- public float getFalseTouchEvaluation(int type, Stroke stroke) {
- float duration = (float) stroke.getDurationNanos() / NANOS_TO_SECONDS;
- if (duration == 0.0f) {
- return SpeedEvaluator.evaluate(0.0f);
- }
- return SpeedEvaluator.evaluate(stroke.getTotalLength() / duration);
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedEvaluator.java
deleted file mode 100644
index afd8d01..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedEvaluator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-public class SpeedEvaluator {
- public static float evaluate(float value) {
- float evaluation = 0.0f;
- if (value < 4.0) evaluation++;
- if (value < 2.2) evaluation++;
- if (value > 35.0) evaluation++;
- if (value > 50.0) evaluation++;
- return evaluation;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedRatioEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedRatioEvaluator.java
deleted file mode 100644
index e34f222..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedRatioEvaluator.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-public class SpeedRatioEvaluator {
- public static float evaluate(float value) {
- float evaluation = 0.0f;
- if (value == 0) return 0;
- if (value <= 1.0) evaluation++;
- if (value <= 0.5) evaluation++;
- if (value > 9.0) evaluation++;
- if (value > 18.0) evaluation++;
- return evaluation;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedVarianceEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedVarianceEvaluator.java
deleted file mode 100644
index 48b1b6e..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedVarianceEvaluator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-public class SpeedVarianceEvaluator {
- public static float evaluate(float value) {
- float evaluation = 0.0f;
- if (value > 0.06) evaluation++;
- if (value > 0.15) evaluation++;
- if (value > 0.3) evaluation++;
- if (value > 0.6) evaluation++;
- return evaluation;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java b/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java
deleted file mode 100644
index 977a2d0..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-import java.util.ArrayList;
-
-/**
- * Contains data about a stroke (a single trace, all the events from a given id from the
- * DOWN/POINTER_DOWN event till the UP/POINTER_UP/CANCEL event.)
- */
-public class Stroke {
- private final float NANOS_TO_SECONDS = 1e9f;
-
- private ArrayList<Point> mPoints = new ArrayList<>();
- private long mStartTimeNano;
- private long mEndTimeNano;
- private float mLength;
- private final float mDpi;
-
- public Stroke(long eventTimeNano, float dpi) {
- mDpi = dpi;
- mStartTimeNano = mEndTimeNano = eventTimeNano;
- }
-
- public void addPoint(float x, float y, long eventTimeNano) {
- mEndTimeNano = eventTimeNano;
- Point point = new Point(x / mDpi, y / mDpi, eventTimeNano - mStartTimeNano);
- if (!mPoints.isEmpty()) {
- mLength += mPoints.get(mPoints.size() - 1).dist(point);
- }
- mPoints.add(point);
- }
-
- public int getCount() {
- return mPoints.size();
- }
-
- public float getTotalLength() {
- return mLength;
- }
-
- public float getEndPointLength() {
- return mPoints.get(0).dist(mPoints.get(mPoints.size() - 1));
- }
-
- public long getDurationNanos() {
- return mEndTimeNano - mStartTimeNano;
- }
-
- public float getDurationSeconds() {
- return (float) getDurationNanos() / NANOS_TO_SECONDS;
- }
-
- public ArrayList<Point> getPoints() {
- return mPoints;
- }
-
- public long getLastEventTimeNano() {
- if (mPoints.isEmpty()) {
- return mStartTimeNano;
- }
-
- return mPoints.get(mPoints.size() - 1).timeOffsetNano;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/StrokeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/StrokeClassifier.java
deleted file mode 100644
index 5da392f..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/StrokeClassifier.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.classifier;
-
-/**
- * An abstract class for classifiers which classify each stroke separately.
- */
-public abstract class StrokeClassifier extends Classifier {
-
- /**
- * @param type the type of action for which this method is called
- * @param stroke the stroke for which the evaluation will be calculated
- * @return a non-negative value which is used to determine whether this a false touch; the
- * bigger the value the greater the chance that this a false touch
- */
- public abstract float getFalseTouchEvaluation(int type, Stroke stroke);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index 9d847ca..334102d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -16,29 +16,37 @@
package com.android.systemui.classifier.brightline;
-import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_REMAIN_LOCKED;
-import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_SUCCESS;
+import static com.android.systemui.classifier.FalsingManagerProxy.FALSING_REMAIN_LOCKED;
+import static com.android.systemui.classifier.FalsingManagerProxy.FALSING_SUCCESS;
import android.app.ActivityManager;
+import android.content.res.Resources;
import android.hardware.biometrics.BiometricSourceType;
import android.net.Uri;
import android.os.Build;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.R;
import com.android.systemui.classifier.Classifier;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.NotificationTapHelper;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -64,6 +72,8 @@
private final ProximitySensor mProximitySensor;
private final DockManager mDockManager;
private final StatusBarStateController mStatusBarStateController;
+ private final SingleTapClassifier mSingleTapClassifier;
+ private final DoubleTapClassifier mDoubleTapClassifier;
private boolean mSessionStarted;
private MetricsLogger mMetricsLogger;
private int mIsFalseTouchCalls;
@@ -106,8 +116,9 @@
public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
KeyguardUpdateMonitor keyguardUpdateMonitor, ProximitySensor proximitySensor,
- DeviceConfigProxy deviceConfigProxy,
- DockManager dockManager, StatusBarStateController statusBarStateController) {
+ DeviceConfigProxy deviceConfigProxy, @Main Resources resources,
+ ViewConfiguration viewConfiguration, DockManager dockManager,
+ StatusBarStateController statusBarStateController) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mDataProvider = falsingDataProvider;
mProximitySensor = proximitySensor;
@@ -129,6 +140,12 @@
mClassifiers.add(distanceClassifier);
mClassifiers.add(proximityClassifier);
mClassifiers.add(new ZigZagClassifier(mDataProvider, deviceConfigProxy));
+
+ mSingleTapClassifier = new SingleTapClassifier(
+ mDataProvider, viewConfiguration.getScaledTouchSlop());
+ mDoubleTapClassifier = new DoubleTapClassifier(mDataProvider, mSingleTapClassifier,
+ resources.getDimension(R.dimen.double_tap_slop),
+ NotificationTapHelper.DOUBLE_TAP_TIMEOUT_MS);
}
private void registerSensors() {
@@ -237,6 +254,36 @@
}
@Override
+ public boolean isFalseTap(boolean robustCheck) {
+ if (!mSingleTapClassifier.isTap(mDataProvider.getRecentMotionEvents())) {
+ logInfo(String.format(
+ (Locale) null, "{classifier=%s}", mSingleTapClassifier.getClass().getName()));
+ String reason = mSingleTapClassifier.getReason();
+ if (reason != null) {
+ logInfo(reason);
+ }
+ return true;
+ }
+
+ // TODO(b/172655679): we always reject single-taps when doing a robust check for now.
+ return robustCheck;
+ }
+
+ @Override
+ public boolean isFalseDoubleTap() {
+ boolean result = mDoubleTapClassifier.isFalseTouch();
+ if (result) {
+ logInfo(String.format(
+ (Locale) null, "{classifier=%s}", mDoubleTapClassifier.getClass().getName()));
+ String reason = mDoubleTapClassifier.getReason();
+ if (reason != null) {
+ logInfo(reason);
+ }
+ }
+ return result;
+ }
+
+ @Override
public void onTouchEvent(MotionEvent motionEvent, int width, int height) {
// TODO: some of these classifiers might allow us to abort early, meaning we don't have to
// make these calls.
@@ -413,7 +460,7 @@
}
@Override
- public void dump(PrintWriter pw) {
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.println("BRIGHTLINE FALSING MANAGER");
ipw.print("classifierEnabled=");
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DoubleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DoubleTapClassifier.java
new file mode 100644
index 0000000..d3af1c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DoubleTapClassifier.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classifier.brightline;
+
+import android.view.MotionEvent;
+
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Returns a false touch if the most two recent gestures are not taps or are too far apart.
+ */
+public class DoubleTapClassifier extends FalsingClassifier {
+
+ private final SingleTapClassifier mSingleTapClassifier;
+ private final float mDoubleTapSlop;
+ private final long mDoubleTapTimeMs;
+
+ private StringBuilder mReason = new StringBuilder();
+
+ DoubleTapClassifier(FalsingDataProvider dataProvider, SingleTapClassifier singleTapClassifier,
+ float doubleTapSlop, long doubleTapTimeMs) {
+ super(dataProvider);
+ mSingleTapClassifier = singleTapClassifier;
+ mDoubleTapSlop = doubleTapSlop;
+ mDoubleTapTimeMs = doubleTapTimeMs;
+ }
+
+ @Override
+ boolean isFalseTouch() {
+ List<MotionEvent> secondTapEvents = getRecentMotionEvents();
+ Queue<? extends List<MotionEvent>> historicalEvents = getHistoricalEvents();
+ List<MotionEvent> firstTapEvents = historicalEvents.peek();
+
+ mReason = new StringBuilder();
+
+ if (firstTapEvents == null) {
+ mReason.append("Only one gesture recorded");
+ return true;
+ }
+
+ return !isDoubleTap(firstTapEvents, secondTapEvents, mReason);
+ }
+
+ /** Returns true if the two supplied lists of {@link MotionEvent}s look like a double-tap. */
+ public boolean isDoubleTap(List<MotionEvent> firstEvents, List<MotionEvent> secondEvents,
+ StringBuilder reason) {
+
+ if (!mSingleTapClassifier.isTap(firstEvents)) {
+ reason.append("First gesture is not a tap. ").append(mSingleTapClassifier.getReason());
+ return false;
+ }
+
+ if (!mSingleTapClassifier.isTap(secondEvents)) {
+ reason.append("Second gesture is not a tap. ").append(mSingleTapClassifier.getReason());
+ return false;
+ }
+
+ MotionEvent firstFinalEvent = firstEvents.get(firstEvents.size() - 1);
+ MotionEvent secondFinalEvent = secondEvents.get(secondEvents.size() - 1);
+
+ long dt = secondFinalEvent.getEventTime() - firstFinalEvent.getEventTime();
+
+ if (dt > mDoubleTapTimeMs) {
+ reason.append("Time between taps too large: ").append(dt).append("ms");
+ return false;
+ }
+
+ if (Math.abs(firstFinalEvent.getX() - secondFinalEvent.getX()) >= mDoubleTapSlop) {
+ reason.append("Delta X between taps too large:")
+ .append(Math.abs(firstFinalEvent.getX() - secondFinalEvent.getX()))
+ .append(" vs ")
+ .append(mDoubleTapSlop);
+ return false;
+ }
+
+ if (Math.abs(firstFinalEvent.getY() - secondFinalEvent.getY()) >= mDoubleTapSlop) {
+ reason.append("Delta Y between taps too large:")
+ .append(Math.abs(firstFinalEvent.getY() - secondFinalEvent.getY()))
+ .append(" vs ")
+ .append(mDoubleTapSlop);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ String getReason() {
+ return mReason.toString();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
index 85e95a6..ed417b3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
@@ -22,6 +22,7 @@
import com.android.systemui.util.sensors.ProximitySensor;
import java.util.List;
+import java.util.Queue;
/**
* Base class for rules that determine False touches.
@@ -30,13 +31,17 @@
private final FalsingDataProvider mDataProvider;
FalsingClassifier(FalsingDataProvider dataProvider) {
- this.mDataProvider = dataProvider;
+ mDataProvider = dataProvider;
}
List<MotionEvent> getRecentMotionEvents() {
return mDataProvider.getRecentMotionEvents();
}
+ Queue<? extends List<MotionEvent>> getHistoricalEvents() {
+ return mDataProvider.getHistoricalMotionEvents();
+ }
+
MotionEvent getFirstMotionEvent() {
return mDataProvider.getFirstRecentMotionEvent();
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
index 8d06748..4681f97 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
@@ -23,9 +23,13 @@
import com.android.systemui.classifier.Classifier;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.time.SystemClock;
import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Queue;
import javax.inject.Inject;
@@ -35,18 +39,21 @@
public class FalsingDataProvider {
private static final long MOTION_EVENT_AGE_MS = 1000;
+ private static final long EXTENDED_MOTION_EVENT_AGE_MS = 30 * 1000;
private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI);
private final int mWidthPixels;
private final int mHeightPixels;
private final BatteryController mBatteryController;
+ private final SystemClock mSystemClock;
private final float mXdpi;
private final float mYdpi;
private @Classifier.InteractionType int mInteractionType;
- private final TimeLimitedMotionEventBuffer mRecentMotionEvents =
- new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+ private final Deque<TimeLimitedMotionEventBuffer> mExtendedMotionEvents = new LinkedList<>();
+ private TimeLimitedMotionEventBuffer mRecentMotionEvents =
+ new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
private boolean mDirty = true;
private float mAngle = 0;
@@ -55,12 +62,14 @@
private MotionEvent mLastMotionEvent;
@Inject
- public FalsingDataProvider(DisplayMetrics displayMetrics, BatteryController batteryController) {
+ public FalsingDataProvider(DisplayMetrics displayMetrics, BatteryController batteryController,
+ SystemClock systemClock) {
mXdpi = displayMetrics.xdpi;
mYdpi = displayMetrics.ydpi;
mWidthPixels = displayMetrics.widthPixels;
mHeightPixels = displayMetrics.heightPixels;
mBatteryController = batteryController;
+ mSystemClock = systemClock;
FalsingClassifier.logInfo("xdpi, ydpi: " + getXdpi() + ", " + getYdpi());
FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels());
@@ -81,7 +90,10 @@
}
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mRecentMotionEvents.clear();
+ if (!mRecentMotionEvents.isEmpty()) {
+ mExtendedMotionEvents.addFirst(mRecentMotionEvents);
+ mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+ }
}
mRecentMotionEvents.addAll(motionEvents);
@@ -112,6 +124,16 @@
return mRecentMotionEvents;
}
+ /** Returns recent gestures, exclusive of the most recent gesture. Newer gestures come first. */
+ Queue<? extends List<MotionEvent>> getHistoricalMotionEvents() {
+ long nowMs = mSystemClock.uptimeMillis();
+
+ mExtendedMotionEvents.removeIf(
+ motionEvents -> motionEvents.isFullyExpired(nowMs - EXTENDED_MOTION_EVENT_AGE_MS));
+
+ return mExtendedMotionEvents;
+ }
+
/**
* interactionType is defined by {@link com.android.systemui.classifier.Classifier}.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/SingleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/SingleTapClassifier.java
new file mode 100644
index 0000000..47708f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/SingleTapClassifier.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.systemui.classifier.brightline;
+
+import android.view.MotionEvent;
+
+import java.util.List;
+
+/**
+ * Falsing classifier that accepts or rejects a single gesture as a tap.
+ */
+public class SingleTapClassifier extends FalsingClassifier {
+ private final float mTouchSlop;
+ private String mReason;
+
+ SingleTapClassifier(FalsingDataProvider dataProvider, float touchSlop) {
+ super(dataProvider);
+ mTouchSlop = touchSlop;
+ }
+
+ @Override
+ boolean isFalseTouch() {
+ return !isTap(getRecentMotionEvents());
+ }
+
+ /** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */
+ public boolean isTap(List<MotionEvent> motionEvents) {
+ float downX = motionEvents.get(0).getX();
+ float downY = motionEvents.get(0).getY();
+
+ for (MotionEvent event : motionEvents) {
+ if (Math.abs(event.getX() - downX) >= mTouchSlop) {
+ mReason = "dX too big for a tap: "
+ + Math.abs(event.getX() - downX)
+ + "vs "
+ + mTouchSlop;
+ return false;
+ } else if (Math.abs(event.getY() - downY) >= mTouchSlop) {
+ mReason = "dY too big for a tap: "
+ + Math.abs(event.getY() - downY)
+ + "vs "
+ + mTouchSlop;
+ return false;
+ }
+ }
+ mReason = "";
+ return true;
+ }
+
+ @Override
+ String getReason() {
+ return mReason;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBuffer.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBuffer.java
index 9a83b5b..92aa7c5 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBuffer.java
@@ -34,12 +34,24 @@
public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
private final LinkedList<MotionEvent> mMotionEvents;
- private long mMaxAgeMs;
+ private final long mMaxAgeMs;
TimeLimitedMotionEventBuffer(long maxAgeMs) {
super();
- this.mMaxAgeMs = maxAgeMs;
- this.mMotionEvents = new LinkedList<>();
+ mMaxAgeMs = maxAgeMs;
+ mMotionEvents = new LinkedList<>();
+ }
+
+ /**
+ * Returns true if the most recent event in the buffer is past the expiration time.
+ *
+ * This method does not mutate the underlying data. This method does imply that, if the supplied
+ * expiration time is old enough and a new {@link MotionEvent} gets added to the buffer, all
+ * prior events would be removed.
+ */
+ public boolean isFullyExpired(long expirationMs) {
+ return mMotionEvents.isEmpty()
+ || mMotionEvents.getLast().getEventTime() <= expirationMs;
}
private void ejectOldEvents() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index d3d24be..96f2072 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -478,7 +478,7 @@
val pendingIntent = PendingIntent.getActivity(context,
componentName.hashCode(),
intent,
- 0)
+ PendingIntent.FLAG_IMMUTABLE)
val control = Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
.setTitle(controlInfo.controlTitle)
.setSubtitle(controlInfo.controlSubtitle)
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 58e49f8..6888d1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -134,7 +134,7 @@
DOZING_UPDATE_SENSOR_TAP(441),
@UiEvent(doc = "Dozing updated because on display auth was triggered from AOD.")
- DOZING_UPDATE_AUTH_TRIGGERED(442);
+ DOZING_UPDATE_AUTH_TRIGGERED(657);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 37bcb16..bd3b899 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -329,7 +329,7 @@
Method injectMethod = rootComponent.getClass()
.getMethod("inject", getClass());
injectMethod.invoke(rootComponent, this);
- Log.w("TAG", "mMediaManager is now: " + mMediaManager);
+ Log.w(TAG, "mMediaManager is now: " + mMediaManager);
} catch (NoSuchMethodException ex) {
Log.e(TAG, "Failed to find inject method for KeyguardSliceProvider", ex);
} catch (IllegalAccessException | InvocationTargetException ex) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4c68312..aea0dd0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -34,10 +34,12 @@
import android.app.StatusBarManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricSourceType;
import android.media.AudioAttributes;
@@ -88,6 +90,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardService;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
@@ -779,7 +782,7 @@
// Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
// is disabled.
- if (mContext.getResources().getBoolean(R.bool.config_enableKeyguardService)) {
+ if (isKeyguardServiceEnabled()) {
setShowingLocked(!shouldWaitForProvisioning()
&& !mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */);
@@ -957,6 +960,15 @@
mUpdateMonitor.dispatchFinishedGoingToSleep(why);
}
+ private boolean isKeyguardServiceEnabled() {
+ try {
+ return mContext.getPackageManager().getServiceInfo(
+ new ComponentName(mContext, KeyguardService.class), 0).isEnabled();
+ } catch (NameNotFoundException e) {
+ return true;
+ }
+ }
+
private long getLockTimeout(int userId) {
// if the screen turned off because of timeout or the user hit the power button
// and we don't need to lock immediately, set an alarm
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index a27e9ac..8c66ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -16,6 +16,8 @@
package com.android.systemui.power;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -334,10 +336,14 @@
}
private PendingIntent pendingBroadcast(String action) {
- return PendingIntent.getBroadcastAsUser(mContext, 0,
- new Intent(action).setPackage(mContext.getPackageName())
- .setFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- 0, UserHandle.CURRENT);
+ return PendingIntent.getBroadcastAsUser(
+ mContext,
+ 0 /* request code */,
+ new Intent(action)
+ .setPackage(mContext.getPackageName())
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+ FLAG_IMMUTABLE /* flags */,
+ UserHandle.CURRENT);
}
private static Intent settings(String action) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index 6ac1e70..4f577f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -25,8 +25,7 @@
import com.android.systemui.qs.TileLayout.exactly
class DoubleLineTileLayout(
- context: Context,
- private val uiEventLogger: UiEventLogger
+ context: Context
) : ViewGroup(context), QSPanel.QSTileLayout {
companion object {
@@ -84,7 +83,7 @@
return false
}
- override fun setListening(listening: Boolean) {
+ override fun setListening(listening: Boolean, uiEventLogger: UiEventLogger) {
if (_listening == listening) return
_listening = listening
for (record in mRecords) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 8c7d459..addbd5f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -142,7 +142,7 @@
}
@Override
- public void setListening(boolean listening) {
+ public void setListening(boolean listening, UiEventLogger uiEventLogger) {
if (mListening == listening) return;
mListening = listening;
updateListening();
@@ -150,7 +150,7 @@
private void updateListening() {
for (TilePage tilePage : mPages) {
- tilePage.setListening(tilePage.getParent() == null ? false : mListening);
+ tilePage.setListening(tilePage.getParent() != null && mListening);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index f4571d7..16e9590 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import android.content.Context;
import android.content.res.Configuration;
@@ -62,7 +61,7 @@
private float mQsExpansion;
private QSCustomizer mQSCustomizer;
private View mDragHandle;
- private View mQSPanelContainer;
+ private NonInterceptingScrollView mQSPanelContainer;
private View mBackground;
@@ -127,18 +126,12 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the
// bottom and footer are inside the screen.
- Configuration config = getResources().getConfiguration();
- boolean navBelow = config.smallestScreenWidthDp >= 600
- || config.orientation != Configuration.ORIENTATION_LANDSCAPE;
MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
// The footer is pinned to the bottom of QSPanel (same bottoms), therefore we don't need to
// subtract its height. We do not care if the collapsed notifications fit in the screen.
int maxQs = getDisplayHeight() - layoutParams.topMargin - layoutParams.bottomMargin
- getPaddingBottom();
- if (navBelow) {
- maxQs -= getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
- }
int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
+ layoutParams.rightMargin;
@@ -184,7 +177,8 @@
mBackground.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
}
- void updateResources(QSPanelController qsPanelController) {
+ void updateResources(QSPanelController qsPanelController,
+ QuickStatusBarHeaderController quickStatusBarHeaderController) {
LayoutParams layoutParams = (LayoutParams) mQSPanelContainer.getLayoutParams();
layoutParams.topMargin = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.quick_qs_offset_height);
@@ -196,7 +190,7 @@
boolean marginsChanged = padding != mContentPadding;
mContentPadding = padding;
if (marginsChanged) {
- updatePaddingsAndMargins(qsPanelController);
+ updatePaddingsAndMargins(qsPanelController, quickStatusBarHeaderController);
}
}
@@ -217,12 +211,13 @@
public void updateExpansion(boolean animate) {
int height = calculateContainerHeight();
+ int scrollBottom = calculateContainerBottom();
setBottom(getTop() + height);
- mQSDetail.setBottom(getTop() + height);
+ mQSDetail.setBottom(getTop() + scrollBottom);
// Pin the drag handle to the bottom of the panel.
- mDragHandle.setTranslationY(height - mDragHandle.getHeight());
+ mDragHandle.setTranslationY(scrollBottom - mDragHandle.getHeight());
mBackground.setTop(mQSPanelContainer.getTop());
- updateBackgroundBottom(height, animate);
+ updateBackgroundBottom(scrollBottom, animate);
}
private void updateBackgroundBottom(int height, boolean animated) {
@@ -246,13 +241,23 @@
+ mHeader.getHeight();
}
+ int calculateContainerBottom() {
+ int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
+ return mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight()
+ : Math.round(mQsExpansion
+ * (heightOverride + mQSPanelContainer.getScrollRange()
+ - mQSPanelContainer.getScrollY() - mHeader.getHeight()))
+ + mHeader.getHeight();
+ }
+
public void setExpansion(float expansion) {
mQsExpansion = expansion;
mDragHandle.setAlpha(1.0f - expansion);
updateExpansion();
}
- private void updatePaddingsAndMargins(QSPanelController qsPanelController) {
+ private void updatePaddingsAndMargins(QSPanelController qsPanelController,
+ QuickStatusBarHeaderController quickStatusBarHeaderController) {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view == mQSCustomizer) {
@@ -271,7 +276,7 @@
} else if (view == mHeader) {
// The header contains the QQS panel which needs to have special padding, to
// visually align them.
- mHeader.setContentMargins(mContentPadding, mContentPadding);
+ quickStatusBarHeaderController.setContentMargins(mContentPadding, mContentPadding);
} else {
view.setPaddingRelative(
mContentPadding,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 27d3221..3638395 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -35,7 +35,7 @@
new ConfigurationController.ConfigurationListener() {
@Override
public void onConfigChanged(Configuration newConfig) {
- mView.updateResources(mQsPanelController);
+ mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
}
};
@@ -60,7 +60,7 @@
@Override
protected void onViewAttached() {
- mView.updateResources(mQsPanelController);
+ mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
mQsPanelController.setMediaVisibilityChangedListener((visible) -> {
if (mQsPanelController.isShown()) {
mView.onMediaVisibilityChanged(true);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 043f5f1..dbdd04a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -300,7 +300,7 @@
? View.VISIBLE
: View.INVISIBLE);
mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
- || (mQsExpanded && !mStackScrollerOverscrolling));
+ || (mQsExpanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
mFooter.setVisibility(
!mQsDisabled && (mQsExpanded || !keyguardShowing || mHeaderAnimating
|| mShowCollapsedOnKeyguard)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 337f31e..87a8da0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -16,8 +16,6 @@
package com.android.systemui.qs;
-import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
import static com.android.systemui.util.Utils.useQsMediaPlayer;
import android.annotation.NonNull;
@@ -36,29 +34,20 @@
import android.view.ViewGroup;
import android.widget.LinearLayout;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.RemeasuringLinearLayout;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.brightness.BrightnessSlider;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.animation.DisappearParameters;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
-import javax.inject.Inject;
-import javax.inject.Named;
-
/** View that represents the quick settings tile panel (when expanded/pulled down). **/
public class QSPanel extends LinearLayout implements Tunable {
@@ -68,7 +57,6 @@
private static final String TAG = "QSPanel";
protected final Context mContext;
- private final MediaHost mMediaHost;
/**
* The index where the content starts that needs to be moved between parents
@@ -81,7 +69,6 @@
protected BrightnessSlider mToggleSliderController;
private final H mHandler = new H();
- private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
/** Whether or not the QS media player feature is enabled. */
protected boolean mUsingMediaPlayer;
private int mVisualMarginStart;
@@ -91,8 +78,6 @@
protected boolean mListening;
private QSDetail.Callback mCallback;
- private final QSLogger mQSLogger;
- protected final UiEventLogger mUiEventLogger;
protected QSTileHost mHost;
private final List<OnConfigurationChangedListener> mOnConfigurationChangedListeners =
new ArrayList<>();
@@ -108,7 +93,6 @@
@Nullable
private ViewGroup mHeaderContainer;
private PageIndicator mFooterPageIndicator;
- private boolean mGridContentVisible = true;
private int mContentMarginStart;
private int mContentMarginEnd;
private int mVisualTilePadding;
@@ -129,23 +113,12 @@
private int mFooterMarginStartHorizontal;
private Consumer<Boolean> mMediaVisibilityChangedListener;
-
- @Inject
- public QSPanel(
- @Named(VIEW_CONTEXT) Context context,
- AttributeSet attrs,
- QSLogger qsLogger,
- @Named(QS_PANEL) MediaHost mediaHost,
- UiEventLogger uiEventLogger
- ) {
+ public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
mUsingMediaPlayer = useQsMediaPlayer(context);
mMediaTotalBottomMargin = getResources().getDimensionPixelSize(
R.dimen.quick_settings_bottom_margin_media);
- mMediaHost = mediaHost;
mContext = context;
- mQSLogger = qsLogger;
- mUiEventLogger = uiEventLogger;
setOrientation(VERTICAL);
@@ -175,7 +148,6 @@
lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1);
addView(mHorizontalLinearLayout, lp);
}
- mQSLogger.logAllTilesChangeListening(mListening, getDumpableTag(), "");
}
protected void onMediaVisibilityChanged(Boolean visible) {
@@ -213,36 +185,6 @@
return createRegularTileLayout();
}
- /**
- * Update the way the media disappears based on if we're using the horizontal layout
- */
- void updateMediaDisappearParameters() {
- if (!mUsingMediaPlayer) {
- return;
- }
- DisappearParameters parameters = mMediaHost.getDisappearParameters();
- if (mUsingHorizontalLayout) {
- // Only height remaining
- parameters.getDisappearSize().set(0.0f, 0.4f);
- // Disappearing on the right side on the bottom
- parameters.getGonePivot().set(1.0f, 1.0f);
- // translating a bit horizontal
- parameters.getContentTranslationFraction().set(0.25f, 1.0f);
- parameters.setDisappearEnd(0.6f);
- } else {
- // Only width remaining
- parameters.getDisappearSize().set(1.0f, 0.0f);
- // Disappearing on the bottom
- parameters.getGonePivot().set(0.0f, 1.0f);
- // translating a bit vertical
- parameters.getContentTranslationFraction().set(0.0f, 1.05f);
- parameters.setDisappearEnd(0.95f);
- }
- parameters.setFadeStartPosition(0.95f);
- parameters.setDisappearStart(0.0f);
- mMediaHost.setDisappearParameters(parameters);
- }
-
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mTileLayout instanceof PagedTileLayout) {
@@ -284,14 +226,6 @@
setMeasuredDimension(getMeasuredWidth(), height);
}
- @Override
- protected void onDetachedFromWindow() {
- if (mTileLayout != null) {
- mTileLayout.setListening(false);
- }
- super.onDetachedFromWindow();
- }
-
protected String getDumpableTag() {
return TAG;
}
@@ -403,65 +337,6 @@
mDivider = findViewById(R.id.divider);
}
- boolean switchTileLayout(boolean force, List<QSPanelControllerBase.TileRecord> records) {
- /** Whether or not the QuickQSPanel currently contains a media player. */
- boolean horizontal = shouldUseHorizontalLayout();
- if (mDivider != null) {
- if (!horizontal && mUsingMediaPlayer && mMediaHost.getVisible()) {
- mDivider.setVisibility(View.VISIBLE);
- } else {
- mDivider.setVisibility(View.GONE);
- }
- }
- if (horizontal != mUsingHorizontalLayout || force) {
- mUsingHorizontalLayout = horizontal;
- View visibleView = horizontal ? mHorizontalLinearLayout : (View) mRegularTileLayout;
- View hiddenView = horizontal ? (View) mRegularTileLayout : mHorizontalLinearLayout;
- ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
- QSTileLayout newLayout = horizontal ? mHorizontalTileLayout : mRegularTileLayout;
- if (hiddenView != null &&
- (mRegularTileLayout != mHorizontalTileLayout ||
- hiddenView != mRegularTileLayout)) {
- // Only hide the view if the horizontal and the regular view are different,
- // otherwise its reattached.
- hiddenView.setVisibility(View.GONE);
- }
- visibleView.setVisibility(View.VISIBLE);
- switchAllContentToParent(newParent, newLayout);
- reAttachMediaHost();
- if (mTileLayout != null) {
- mTileLayout.setListening(false);
- for (QSPanelControllerBase.TileRecord record : records) {
- mTileLayout.removeTile(record);
- record.tile.removeCallback(record.callback);
- }
- }
- mTileLayout = newLayout;
- if (needsDynamicRowsAndColumns()) {
- newLayout.setMinRows(horizontal ? 2 : 1);
- // Let's use 3 columns to match the current layout
- newLayout.setMaxColumns(horizontal ? 3 : TileLayout.NO_MAX_COLUMNS);
- }
- updateTileLayoutMargins();
- updateFooterMargin();
- updateDividerMargin();
- updateMediaDisappearParameters();
- updateMediaHostContentMargins();
- updateHorizontalLinearLayoutMargins();
- updatePadding();
- return true;
- }
- return false;
- }
-
- /**
- * Sets the listening state of the current layout to the state of the view. Used after
- * switching layouts.
- */
- public void reSetLayoutListening() {
- mTileLayout.setListening(mListening);
- }
-
private void updateHorizontalLinearLayoutMargins() {
if (mHorizontalLinearLayout != null && !displayMediaMarginsOnMedia()) {
LayoutParams lp = (LayoutParams) mHorizontalLinearLayout.getLayoutParams();
@@ -524,27 +399,19 @@
}
}
- private boolean shouldUseHorizontalLayout() {
- return mUsingMediaPlayer && mMediaHost.getVisible()
- && getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
- }
-
- protected void reAttachMediaHost() {
+ /** Call when orientation has changed and MediaHost needs to be adjusted. */
+ private void reAttachMediaHost(ViewGroup hostView, boolean horizontal) {
if (!mUsingMediaPlayer) {
return;
}
- boolean horizontal = shouldUseHorizontalLayout();
- ViewGroup host = mMediaHost.getHostView();
-
ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this;
- ViewGroup currentParent = (ViewGroup) host.getParent();
+ ViewGroup currentParent = (ViewGroup) hostView.getParent();
if (currentParent != newParent) {
if (currentParent != null) {
- currentParent.removeView(host);
+ currentParent.removeView(hostView);
}
- newParent.addView(host);
- LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams();
+ newParent.addView(hostView);
+ LinearLayout.LayoutParams layoutParams = (LayoutParams) hostView.getLayoutParams();
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT;
layoutParams.weight = horizontal ? 1.2f : 0;
@@ -558,7 +425,6 @@
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
- mQSLogger.logPanelExpanded(expanded, getDumpableTag());
mExpanded = expanded;
if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
@@ -576,16 +442,10 @@
}
/** */
- public void setListening(boolean listening, String cachedSpecs) {
- if (mListening == listening) return;
+ public void setListening(boolean listening) {
mListening = listening;
- if (mTileLayout != null) {
- mQSLogger.logAllTilesChangeListening(listening, getDumpableTag(), cachedSpecs);
- mTileLayout.setListening(listening);
- }
}
-
public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
int xInWindow = locationInWindow[0];
int yInWindow = locationInWindow[1];
@@ -728,14 +588,6 @@
fireScanStateChanged(scanState);
}
- void setGridContentVisibility(boolean visible) {
- int newVis = visible ? VISIBLE : INVISIBLE;
- setVisibility(newVis);
- if (mGridContentVisible != visible) {
- mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis);
- }
- mGridContentVisible = visible;
- }
private void fireShowingDetail(DetailAdapter detail, int x, int y) {
if (mCallback != null) {
mCallback.onShowingDetail(detail, x, y);
@@ -763,14 +615,15 @@
return mDivider;
}
- public void setContentMargins(int startMargin, int endMargin) {
+ /** */
+ public void setContentMargins(int startMargin, int endMargin, ViewGroup mediaHostView) {
// Only some views actually want this content padding, others want to go all the way
// to the edge like the brightness slider
mContentMarginStart = startMargin;
mContentMarginEnd = endMargin;
updateTileLayoutMargins(mContentMarginStart - mVisualTilePadding,
mContentMarginEnd - mVisualTilePadding);
- updateMediaHostContentMargins();
+ updateMediaHostContentMargins(mediaHostView);
updateFooterMargin();
updateDividerMargin();
}
@@ -826,13 +679,13 @@
/**
* Update the margins of the media hosts
*/
- protected void updateMediaHostContentMargins() {
+ protected void updateMediaHostContentMargins(ViewGroup mediaHostView) {
if (mUsingMediaPlayer) {
int marginStart = mContentMarginStart;
if (mUsingHorizontalLayout) {
marginStart = 0;
}
- updateMargins(mMediaHost.getHostView(), marginStart, mContentMarginEnd);
+ updateMargins(mediaHostView, marginStart, mContentMarginEnd);
}
}
@@ -871,6 +724,45 @@
mSecurityFooter = view;
}
+ void setUsingHorizontalLayout(boolean horizontal, ViewGroup mediaHostView, boolean force,
+ UiEventLogger uiEventLogger) {
+ if (horizontal != mUsingHorizontalLayout || force) {
+ mUsingHorizontalLayout = horizontal;
+ View visibleView = horizontal ? mHorizontalLinearLayout : (View) mRegularTileLayout;
+ View hiddenView = horizontal ? (View) mRegularTileLayout : mHorizontalLinearLayout;
+ ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
+ QSPanel.QSTileLayout newLayout = horizontal
+ ? mHorizontalTileLayout : mRegularTileLayout;
+ if (hiddenView != null
+ && (mRegularTileLayout != mHorizontalTileLayout
+ || hiddenView != mRegularTileLayout)) {
+ // Only hide the view if the horizontal and the regular view are different,
+ // otherwise its reattached.
+ hiddenView.setVisibility(View.GONE);
+ }
+ visibleView.setVisibility(View.VISIBLE);
+ switchAllContentToParent(newParent, newLayout);
+ reAttachMediaHost(mediaHostView, horizontal);
+ mTileLayout = newLayout;
+ newLayout.setListening(mListening, uiEventLogger);
+ if (needsDynamicRowsAndColumns()) {
+ newLayout.setMinRows(horizontal ? 2 : 1);
+ // Let's use 3 columns to match the current layout
+ newLayout.setMaxColumns(horizontal ? 3 : TileLayout.NO_MAX_COLUMNS);
+ }
+ updateMargins(mediaHostView);
+ }
+ }
+
+ private void updateMargins(ViewGroup mediaHostView) {
+ updateTileLayoutMargins();
+ updateFooterMargin();
+ updateDividerMargin();
+ updateMediaHostContentMargins(mediaHostView);
+ updateHorizontalLinearLayoutMargins();
+ updatePadding();
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
@@ -912,7 +804,7 @@
boolean updateResources();
/** */
- void setListening(boolean listening);
+ void setListening(boolean listening, UiEventLogger uiEventLogger);
/**
* Set the minimum number of rows to show
@@ -924,7 +816,7 @@
}
/**
- * Set the max number of collums to show
+ * Set the max number of columns to show
*
* @param maxColumns the maximum
*
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index eaa3ed0..8ee284b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
import android.annotation.NonNull;
import android.content.res.Configuration;
@@ -27,6 +28,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
@@ -34,6 +36,7 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessSlider;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -57,6 +60,9 @@
private final BrightnessSlider.Factory mBrightnessSliderFactory;
private final BrightnessSlider mBrightnessSlider;
+ private BrightnessMirrorController mBrightnessMirrorController;
+ private boolean mGridContentVisible = true;
+
private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
new QSPanel.OnConfigurationChangedListener() {
@Override
@@ -69,7 +75,6 @@
updateBrightnessMirror();
}
};
- private BrightnessMirrorController mBrightnessMirrorController;
private final BrightnessMirrorController.BrightnessMirrorListener mBrightnessMirrorListener =
mirror -> updateBrightnessMirror();
@@ -77,13 +82,14 @@
@Inject
QSPanelController(QSPanel view, QSSecurityFooter qsSecurityFooter, TunerService tunerService,
QSTileHost qstileHost, QSCustomizerController qsCustomizerController,
+ @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QS_PANEL) MediaHost mediaHost,
QSTileRevealController.Factory qsTileRevealControllerFactory,
DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
- BrightnessController.Factory brightnessControllerFactory,
+ QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSlider.Factory brightnessSliderFactory) {
- super(view, qstileHost, qsCustomizerController, mediaHost, metricsLogger, uiEventLogger,
- dumpManager);
+ super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
+ metricsLogger, uiEventLogger, qsLogger, dumpManager);
mQsSecurityFooter = qsSecurityFooter;
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
@@ -111,7 +117,7 @@
protected void onViewAttached() {
super.onViewAttached();
- mView.updateMediaDisappearParameters();
+ updateMediaDisappearParameters();
mTunerService.addTunable(mView, QS_SHOW_BRIGHTNESS);
mView.updateResources();
@@ -142,11 +148,6 @@
super.onViewDetached();
}
- /** TODO(b/168904199): Remove this method once view is controllerized. */
- QSPanel getView() {
- return mView;
- }
-
/**
* Set the header container of quick settings.
*/
@@ -246,7 +247,12 @@
/** */
public void setGridContentVisibility(boolean visible) {
- mView.setGridContentVisibility(visible);
+ int newVis = visible ? View.VISIBLE : View.INVISIBLE;
+ setVisibility(newVis);
+ if (mGridContentVisible != visible) {
+ mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis);
+ }
+ mGridContentVisible = visible;
}
public boolean isLayoutRtl() {
@@ -277,7 +283,7 @@
/** */
public void setContentMargins(int startMargin, int endMargin) {
- mView.setContentMargins(startMargin, endMargin);
+ mView.setContentMargins(startMargin, endMargin, mMediaHost.getHostView());
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 2c4c8c2..4418a74 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -17,10 +17,12 @@
package com.android.systemui.qs;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
import android.content.ComponentName;
import android.content.res.Configuration;
import android.metrics.LogMaker;
+import android.view.View;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
@@ -31,7 +33,9 @@
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.util.ViewController;
+import com.android.systemui.util.animation.DisappearParameters;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -39,6 +43,8 @@
import java.util.Collection;
import java.util.stream.Collectors;
+import javax.inject.Named;
+
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
@@ -51,9 +57,11 @@
implements Dumpable{
protected final QSTileHost mHost;
private final QSCustomizerController mQsCustomizerController;
+ private final boolean mUsingMediaPlayer;
protected final MediaHost mMediaHost;
- private final MetricsLogger mMetricsLogger;
+ protected final MetricsLogger mMetricsLogger;
private final UiEventLogger mUiEventLogger;
+ private final QSLogger mQSLogger;
private final DumpManager mDumpManager;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
@@ -81,19 +89,30 @@
return null;
};
+ private boolean mUsingHorizontalLayout;
+
protected QSPanelControllerBase(T view, QSTileHost host,
- QSCustomizerController qsCustomizerController, MediaHost mediaHost,
- MetricsLogger metricsLogger, UiEventLogger uiEventLogger, DumpManager dumpManager) {
+ QSCustomizerController qsCustomizerController,
+ @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, MediaHost mediaHost,
+ MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
+ DumpManager dumpManager) {
super(view);
mHost = host;
mQsCustomizerController = qsCustomizerController;
+ mUsingMediaPlayer = usingMediaPlayer;
mMediaHost = mediaHost;
mMetricsLogger = metricsLogger;
mUiEventLogger = uiEventLogger;
+ mQSLogger = qsLogger;
mDumpManager = dumpManager;
}
@Override
+ protected void onInit() {
+ mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), "");
+ }
+
+ @Override
protected void onViewAttached() {
mQsTileRevealController = createTileRevealController();
if (mQsTileRevealController != null) {
@@ -115,6 +134,8 @@
mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
mHost.removeCallback(mQSHostCallback);
+ mView.getTileLayout().setListening(false, mUiEventLogger);
+
mMediaHost.removeVisibilityChangeListener(mMediaHostVisibilityListener);
for (TileRecord record : mRecords) {
@@ -205,6 +226,11 @@
/** */
public void setExpanded(boolean expanded) {
+ if (mView.isExpanded() == expanded) {
+ return;
+ }
+ mQSLogger.logPanelExpanded(expanded, mView.getDumpableTag());
+
mView.setExpanded(expanded);
mMetricsLogger.visibility(MetricsEvent.QS_PANEL, expanded);
if (!expanded) {
@@ -238,17 +264,77 @@
void setListening(boolean listening) {
- mView.setListening(listening, mCachedSpecs);
+ mView.setListening(listening);
+
+ if (mView.getTileLayout() != null) {
+ mQSLogger.logAllTilesChangeListening(listening, mView.getDumpableTag(), mCachedSpecs);
+ mView.getTileLayout().setListening(listening, mUiEventLogger);
+ }
}
boolean switchTileLayout(boolean force) {
- if (mView.switchTileLayout(force, mRecords)) {
+ /** Whether or not the QuickQSPanel currently contains a media player. */
+ boolean horizontal = shouldUseHorizontalLayout();
+ if (mView.getDivider() != null) {
+ if (!horizontal && mUsingMediaPlayer && mMediaHost.getVisible()) {
+ mView.getDivider().setVisibility(View.VISIBLE);
+ } else {
+ mView.getDivider().setVisibility(View.GONE);
+ }
+ }
+ if (horizontal != mUsingHorizontalLayout || force) {
+ mUsingHorizontalLayout = horizontal;
+ for (QSPanelControllerBase.TileRecord record : mRecords) {
+ mView.removeTile(record);
+ record.tile.removeCallback(record.callback);
+ }
+ mView.setUsingHorizontalLayout(mUsingHorizontalLayout, mMediaHost.getHostView(), force,
+ mUiEventLogger);
+ updateMediaDisappearParameters();
+
setTiles();
+
return true;
}
return false;
}
+ /**
+ * Update the way the media disappears based on if we're using the horizontal layout
+ */
+ void updateMediaDisappearParameters() {
+ if (!mUsingMediaPlayer) {
+ return;
+ }
+ DisappearParameters parameters = mMediaHost.getDisappearParameters();
+ if (mUsingHorizontalLayout) {
+ // Only height remaining
+ parameters.getDisappearSize().set(0.0f, 0.4f);
+ // Disappearing on the right side on the bottom
+ parameters.getGonePivot().set(1.0f, 1.0f);
+ // translating a bit horizontal
+ parameters.getContentTranslationFraction().set(0.25f, 1.0f);
+ parameters.setDisappearEnd(0.6f);
+ } else {
+ // Only width remaining
+ parameters.getDisappearSize().set(1.0f, 0.0f);
+ // Disappearing on the bottom
+ parameters.getGonePivot().set(0.0f, 1.0f);
+ // translating a bit vertical
+ parameters.getContentTranslationFraction().set(0.0f, 1.05f);
+ parameters.setDisappearEnd(0.95f);
+ }
+ parameters.setFadeStartPosition(0.95f);
+ parameters.setDisappearStart(0.0f);
+ mMediaHost.setDisappearParameters(parameters);
+ }
+
+ boolean shouldUseHorizontalLayout() {
+ return mUsingMediaPlayer && mMediaHost.getVisible()
+ && getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
private void logTiles() {
for (int i = 0; i < mRecords.size(); i++) {
QSTile tile = mRecords.get(i).tile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 84c2ac24..c89f8e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -16,9 +16,6 @@
package com.android.systemui.qs;
-import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -29,14 +26,9 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
-import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.qs.logging.QSLogger;
-
-import javax.inject.Inject;
-import javax.inject.Named;
/**
* Version of QSPanel that only shows N Quick Tiles in the QS Header.
@@ -51,15 +43,8 @@
private boolean mDisabledByPolicy;
private int mMaxTiles;
-
- @Inject
- public QuickQSPanel(
- @Named(VIEW_CONTEXT) Context context,
- AttributeSet attrs,
- QSLogger qsLogger,
- @Named(QUICK_QS_PANEL) MediaHost mediaHost,
- UiEventLogger uiEventLogger) {
- super(context, attrs, qsLogger, mediaHost, uiEventLogger);
+ public QuickQSPanel(Context context, AttributeSet attrs) {
+ super(context, attrs);
mMaxTiles = Math.min(DEFAULT_MAX_TILES,
getResources().getInteger(R.integer.quick_qs_panel_max_columns));
applyBottomMargin((View) mRegularTileLayout);
@@ -79,12 +64,12 @@
@Override
public TileLayout createRegularTileLayout() {
- return new QuickQSPanel.HeaderTileLayout(mContext, mUiEventLogger);
+ return new QuickQSPanel.HeaderTileLayout(mContext);
}
@Override
protected QSTileLayout createHorizontalTileLayout() {
- return new DoubleLineTileLayout(mContext, mUiEventLogger);
+ return new DoubleLineTileLayout(mContext);
}
@Override
@@ -198,13 +183,10 @@
private static class HeaderTileLayout extends TileLayout {
- private final UiEventLogger mUiEventLogger;
-
private Rect mClippingBounds = new Rect();
- public HeaderTileLayout(Context context, UiEventLogger uiEventLogger) {
+ HeaderTileLayout(Context context) {
super(context);
- mUiEventLogger = uiEventLogger;
setClipChildren(false);
setClipToPadding(false);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
@@ -332,14 +314,14 @@
}
@Override
- public void setListening(boolean listening) {
+ public void setListening(boolean listening, UiEventLogger uiEventLogger) {
boolean startedListening = !mListening && listening;
- super.setListening(listening);
+ super.setListening(listening, uiEventLogger);
if (startedListening) {
// getNumVisibleTiles() <= mRecords.size()
for (int i = 0; i < getNumVisibleTiles(); i++) {
QSTile tile = mRecords.get(i).tile;
- mUiEventLogger.logWithInstanceId(QSEvent.QQS_TILE_VISIBLE, 0,
+ uiEventLogger.logWithInstanceId(QSEvent.QQS_TILE_VISIBLE, 0,
tile.getMetricsSpec(), tile.getInstanceId());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 174a81f..cca0e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
@@ -27,6 +28,7 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.qs.logging.QSLogger;
import java.util.ArrayList;
import java.util.List;
@@ -38,8 +40,6 @@
@QSScope
public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> {
- private List<QSTile> mAllTiles = new ArrayList<>();
-
private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
newConfig -> {
int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
@@ -51,11 +51,12 @@
@Inject
QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
QSCustomizerController qsCustomizerController,
+ @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QUICK_QS_PANEL) MediaHost mediaHost,
- MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
+ MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager) {
- super(view, qsTileHost, qsCustomizerController, mediaHost, metricsLogger, uiEventLogger,
- dumpManager);
+ super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
+ uiEventLogger, qsLogger, dumpManager);
}
@Override
@@ -89,14 +90,19 @@
@Override
public void setTiles() {
- mAllTiles.clear();
+ List<QSTile> tiles = new ArrayList();
for (QSTile tile : mHost.getTiles()) {
- mAllTiles.add(tile);
- if (mAllTiles.size() == QuickQSPanel.DEFAULT_MAX_TILES) {
+ tiles.add(tile);
+ if (tiles.size() == mView.getNumQuickTiles()) {
break;
}
}
- super.setTiles(mAllTiles.subList(0, mView.getNumQuickTiles()), true);
+ super.setTiles(tiles, true);
+ }
+
+ /** */
+ public void setContentMargins(int marginStart, int marginEnd) {
+ mView.setContentMargins(marginStart, marginEnd, mMediaHost.getHostView());
}
public int getNumQuickTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 09894e5..b85ad81 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -51,8 +51,6 @@
import com.android.systemui.DualToneHandler;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.qs.QSDetail.Callback;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
@@ -313,10 +311,11 @@
.build();
}
- public void setExpanded(boolean expanded) {
+ /** */
+ public void setExpanded(boolean expanded, QuickQSPanelController quickQSPanelController) {
if (mExpanded == expanded) return;
mExpanded = expanded;
- mHeaderQsPanel.setExpanded(expanded);
+ quickQSPanelController.setExpanded(expanded);
updateEverything();
}
@@ -466,14 +465,15 @@
}
/** */
- public void setContentMargins(int marginStart, int marginEnd) {
+ public void setContentMargins(int marginStart, int marginEnd,
+ QuickQSPanelController quickQSPanelController) {
mContentMarginStart = marginStart;
mContentMarginEnd = marginEnd;
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view == mHeaderQsPanel) {
// QS panel doesn't lays out some of its content full width
- mHeaderQsPanel.setContentMargins(marginStart, marginEnd);
+ quickQSPanelController.setContentMargins(marginStart, marginEnd);
} else {
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
lp.setMarginStart(marginStart);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 5ee9df4..c5b76fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -379,6 +379,11 @@
mZenModeController.getConsolidatedPolicy());
}
+ public void setContentMargins(int contentPaddingStart, int contentPaddingEnd) {
+ mView.setContentMargins(contentPaddingStart, contentPaddingEnd, mHeaderQsPanelController);
+ }
+
+
private static class ClockDemoModeReceiver implements DemoMode {
private Clock mClockView;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 4ab7afd..348dca5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -9,6 +9,7 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
@@ -59,8 +60,12 @@
return getTop();
}
- @Override
public void setListening(boolean listening) {
+ setListening(listening, null);
+ }
+
+ @Override
+ public void setListening(boolean listening, UiEventLogger uiEventLogger) {
if (mListening == listening) return;
mListening = listening;
for (TileRecord record : mRecords) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index f3bf306..2363aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -16,6 +16,9 @@
package com.android.systemui.qs.dagger;
+import static com.android.systemui.util.Utils.useQsMediaPlayer;
+
+import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
@@ -44,6 +47,7 @@
@Module
public interface QSFragmentModule {
String QS_SECURITY_FOOTER_VIEW = "qs_security_footer";
+ String QS_USING_MEDIA_PLAYER = "qs_using_media_player";
/** */
@Provides
@@ -108,4 +112,12 @@
static View providesQSSecurityFooterView(LayoutInflater layoutInflater, QSPanel qsPanel) {
return layoutInflater.inflate(R.layout.quick_settings_footer, qsPanel, false);
}
+
+ /** */
+ @Provides
+ @Named(QS_USING_MEDIA_PLAYER)
+ static boolean providesQSUsingMediaPlayer(Context context) {
+ return useQsMediaPlayer(context);
+ }
+
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 216b83f..84818ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -41,6 +41,7 @@
public class NotificationHeaderUtil {
private static final TextViewComparator sTextViewComparator = new TextViewComparator();
+ private static final TextViewComparator sAppNameComparator = new AppNameComparator();
private static final VisibilityApplicator sVisibilityApplicator = new VisibilityApplicator();
private static final VisibilityApplicator sAppNameApplicator = new AppNameApplicator();
private static final DataExtractor sIconExtractor = new DataExtractor() {
@@ -119,7 +120,7 @@
mRow,
com.android.internal.R.id.app_name_text,
null,
- sTextViewComparator,
+ sAppNameComparator,
sAppNameApplicator));
mComparators.add(HeaderProcessor.forTextView(mRow,
com.android.internal.R.id.header_text));
@@ -389,4 +390,17 @@
super.apply(parent, view, apply, reset);
}
}
+
+ private static class AppNameComparator extends TextViewComparator {
+ @Override
+ public boolean compare(View parent, View child, Object parentData, Object childData) {
+ if (isEmpty(child)) {
+ // In headerless notifications the AppName view exists but is usually GONE (and not
+ // populated). We need to treat this case as equal to the header in order to
+ // deduplicate the view.
+ return true;
+ }
+ return super.compare(parent, child, parentData, childData);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
index 41ce51c..0b79387 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -23,32 +23,44 @@
import com.android.systemui.Gefingerpoken;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.phone.DoubleTapHelper;
+import com.android.systemui.statusbar.phone.NotificationTapHelper;
+import com.android.systemui.util.ViewController;
import javax.inject.Inject;
/**
* Controller for {@link ActivatableNotificationView}
*/
-public class ActivatableNotificationViewController {
- private final ActivatableNotificationView mView;
+public class ActivatableNotificationViewController
+ extends ViewController<ActivatableNotificationView> {
private final ExpandableOutlineViewController mExpandableOutlineViewController;
private final AccessibilityManager mAccessibilityManager;
private final FalsingManager mFalsingManager;
- private DoubleTapHelper mDoubleTapHelper;
- private boolean mNeedsDimming;
+ private final NotificationTapHelper mNotificationTapHelper;
+ private final TouchHandler mTouchHandler = new TouchHandler();
- private TouchHandler mTouchHandler = new TouchHandler();
+ private boolean mNeedsDimming;
@Inject
public ActivatableNotificationViewController(ActivatableNotificationView view,
+ NotificationTapHelper.Factory notificationTapHelpFactory,
ExpandableOutlineViewController expandableOutlineViewController,
AccessibilityManager accessibilityManager, FalsingManager falsingManager) {
- mView = view;
+ super(view);
mExpandableOutlineViewController = expandableOutlineViewController;
mAccessibilityManager = accessibilityManager;
mFalsingManager = falsingManager;
+ mNotificationTapHelper = notificationTapHelpFactory.create(
+ (active) -> {
+ if (active) {
+ mView.makeActive();
+ mFalsingManager.onNotificationActive();
+ } else {
+ mView.makeInactive(true /* animate */);
+ }
+ }, mView::performClick, mView::handleSlideBack);
+
mView.setOnActivatedListener(new ActivatableNotificationView.OnActivatedListener() {
@Override
public void onActivated(ActivatableNotificationView view) {
@@ -64,25 +76,25 @@
/**
* Initialize the controller, setting up handlers and other behavior.
*/
- public void init() {
+ @Override
+ public void onInit() {
mExpandableOutlineViewController.init();
- mDoubleTapHelper = new DoubleTapHelper(mView, (active) -> {
- if (active) {
- mView.makeActive();
- mFalsingManager.onNotificationActive();
- } else {
- mView.makeInactive(true /* animate */);
- }
- }, mView::performClick, mView::handleSlideBack,
- mFalsingManager::onNotificationDoubleTap);
mView.setOnTouchListener(mTouchHandler);
mView.setTouchHandler(mTouchHandler);
- mView.setOnDimmedListener(dimmed -> {
- mNeedsDimming = dimmed;
- });
+ mView.setOnDimmedListener(dimmed -> mNeedsDimming = dimmed);
mView.setAccessibilityManager(mAccessibilityManager);
}
+ @Override
+ protected void onViewAttached() {
+
+ }
+
+ @Override
+ protected void onViewDetached() {
+
+ }
+
class TouchHandler implements Gefingerpoken, View.OnTouchListener {
private boolean mBlockNextTouch;
@@ -103,7 +115,7 @@
// let's ensure we have a ripple
return false;
}
- result = mDoubleTapHelper.onTouchEvent(ev, mView.getActualHeight());
+ result = mNotificationTapHelper.onTouchEvent(ev, mView.getActualHeight());
} else {
return false;
}
@@ -117,7 +129,7 @@
&& !mAccessibilityManager.isTouchExplorationEnabled()) {
if (!mView.isActive()) {
return true;
- } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) {
+ } else if (mFalsingManager.isFalseDoubleTap()) {
mBlockNextTouch = true;
mView.makeInactive(true /* animate */);
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 3287745..74e6c00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1369,16 +1369,8 @@
bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener());
bubbleButton.setVisibility(VISIBLE);
actionContainer.setVisibility(VISIBLE);
-
- int paddingEnd = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.bubble_visible_padding_end);
- actionContainerLayout.setPaddingRelative(0, 0, paddingEnd, 0);
} else {
bubbleButton.setVisibility(GONE);
-
- int paddingEnd = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.bubble_gone_padding_end);
- actionContainerLayout.setPaddingRelative(0, 0, paddingEnd, 0);
}
}
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 447fa43..1034b1f 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
@@ -295,7 +295,7 @@
mMenuContainer = new FrameLayout(mContext);
}
final boolean newFlowHideShelf = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.SHOW_NEW_NOTIF_DISMISS, 0) == 1;
+ Settings.Global.SHOW_NEW_NOTIF_DISMISS, 1 /* on by default */) == 1;
if (newFlowHideShelf) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index d228ce1..05db67d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -56,6 +56,7 @@
private CachingIconView mIcon;
private NotificationExpandButton mExpandButton;
+ private View mAltExpandTarget;
protected NotificationHeaderView mNotificationHeader;
protected NotificationTopLineView mNotificationTopLine;
private TextView mHeaderText;
@@ -106,6 +107,7 @@
mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text);
mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
+ mAltExpandTarget = mView.findViewById(com.android.internal.R.id.alternate_expand_target);
mRightIcon = mView.findViewById(com.android.internal.R.id.right_icon);
mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
@@ -260,6 +262,9 @@
public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
mExpandButton.setOnClickListener(expandable ? onClickListener : null);
+ if (mAltExpandTarget != null) {
+ mAltExpandTarget.setOnClickListener(expandable ? onClickListener : null);
+ }
if (mNotificationHeader != null) {
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 8050fea..885048d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -41,7 +41,6 @@
private static final float MAX_PULSE_HEIGHT = 100000f;
private final SectionProvider mSectionProvider;
- private ArrayList<View> mDraggedViews = new ArrayList<>();
private int mScrollY;
private int mAnchorViewIndex;
private int mAnchorViewY;
@@ -161,19 +160,6 @@
mAnchorViewY = anchorViewY;
}
- /** Call when dragging begins. */
- public void onBeginDrag(View view) {
- mDraggedViews.add(view);
- }
-
- public void onDragFinished(View view) {
- mDraggedViews.remove(view);
- }
-
- public ArrayList<View> getDraggedViews() {
- return mDraggedViews;
- }
-
/**
* @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are
* translucent and everything is scaled back a bit.
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 fbcfef3..742e517 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
@@ -353,31 +353,24 @@
private boolean mContinuousShadowUpdate;
private boolean mContinuousBackgroundUpdate;
private ViewTreeObserver.OnPreDrawListener mShadowUpdater
- = new ViewTreeObserver.OnPreDrawListener() {
-
- @Override
- public boolean onPreDraw() {
- updateViewShadows();
- return true;
- }
- };
+ = () -> {
+ updateViewShadows();
+ return true;
+ };
private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
updateBackground();
return true;
};
- private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
- @Override
- public int compare(ExpandableView view, ExpandableView otherView) {
- float endY = view.getTranslationY() + view.getActualHeight();
- float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
- if (endY < otherEndY) {
- return -1;
- } else if (endY > otherEndY) {
- return 1;
- } else {
- // The two notifications end at the same location
- return 0;
- }
+ private Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
+ float endY = view.getTranslationY() + view.getActualHeight();
+ float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
+ if (endY < otherEndY) {
+ return -1;
+ } else if (endY > otherEndY) {
+ return 1;
+ } else {
+ // The two notifications end at the same location
+ return 0;
}
};
private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
@@ -415,8 +408,6 @@
*/
private float mBackgroundXFactor = 1f;
- private boolean mSwipingInProgress;
-
private boolean mUsingLightTheme;
private boolean mQsExpanded;
private boolean mForwardScrollable;
@@ -834,9 +825,9 @@
if (!mShouldDrawNotificationBackground) {
return;
}
- final boolean clearUndershelf = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.SHOW_NEW_NOTIF_DISMISS, 0 /* show background by default */) == 1;
- if (clearUndershelf) {
+ final boolean newFlowHideShelf = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.SHOW_NEW_NOTIF_DISMISS, 1 /* on by default */) == 1;
+ if (newFlowHideShelf) {
mBackgroundPaint.setColor(Color.TRANSPARENT);
invalidate();
return;
@@ -2554,12 +2545,13 @@
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
ExpandableView child = (ExpandableView) getChildAt(i);
- if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView)
- && child != mShelf) {
+ if (child.getVisibility() != View.GONE
+ && !(child instanceof StackScrollerDecorView)
+ && child != mShelf
+ && mSwipeHelper.getSwipedView() != child) {
children.add(child);
}
}
-
return children;
}
@@ -3577,7 +3569,10 @@
@Override
@ShadeViewRefactor(RefactorComponent.INPUT)
public boolean onGenericMotionEvent(MotionEvent event) {
- if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification
+ if (!isScrollingEnabled()
+ || !mIsExpanded
+ || mSwipeHelper.isSwiping()
+ || mExpandingNotification
|| mDisallowScrollingInThisMotion) {
return false;
}
@@ -4077,14 +4072,6 @@
return false;
}
- @ShadeViewRefactor(RefactorComponent.INPUT)
- void setSwipingInProgress(boolean swiping) {
- mSwipingInProgress = swiping;
- if (swiping) {
- requestDisallowInterceptTouchEvent(true);
- }
- }
-
@Override
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void onWindowFocusChanged(boolean hasWindowFocus) {
@@ -4127,9 +4114,8 @@
mStatusBar.resetUserExpandedStates();
clearTemporaryViews();
clearUserLockedViews();
- ArrayList<View> draggedViews = mAmbientState.getDraggedViews();
- if (draggedViews.size() > 0) {
- draggedViews.clear();
+ if (mSwipeHelper.isSwiping()) {
+ mSwipeHelper.resetSwipeState();
updateContinuousShadowDrawing();
}
}
@@ -5196,15 +5182,11 @@
ExpandableView child = (ExpandableView) getTransientView(i);
child.dump(fd, pw, args);
}
- ArrayList<View> draggedViews = mAmbientState.getDraggedViews();
- int draggedCount = draggedViews.size();
- pw.println(" Dragged Views: " + draggedCount);
- for (int i = 0; i < draggedCount; i++) {
- View view = draggedViews.get(i);
- if (view instanceof ExpandableView) {
- ExpandableView expandableView = (ExpandableView) view;
- expandableView.dump(fd, pw, args);
- }
+ View swipedView = mSwipeHelper.getSwipedView();
+ pw.println(" Swiped view: " + swipedView);
+ if (swipedView instanceof ExpandableView) {
+ ExpandableView expandableView = (ExpandableView) swipedView;
+ expandableView.dump(fd, pw, args);
}
}
@@ -5522,12 +5504,16 @@
mSwipedOutViews.add(v);
}
- void addDraggedView(View view) {
- mAmbientState.onBeginDrag(view);
+ void onSwipeBegin() {
+ requestDisallowInterceptTouchEvent(true);
+ updateFirstAndLastBackgroundViews();
+ updateContinuousShadowDrawing();
+ updateContinuousBackgroundDrawing();
+ requestChildrenUpdate();
}
- void removeDraggedView(View view) {
- mAmbientState.onDragFinished(view);
+ void onSwipeEnd() {
+ updateFirstAndLastBackgroundViews();
}
void setTopHeadsUpEntry(NotificationEntry topEntry) {
@@ -5539,10 +5525,6 @@
mAmbientState.setHasAlertEntries(numHeadsUp > 0);
}
- boolean getSwipingInProgress() {
- return mSwipingInProgress;
- }
-
public boolean getIsExpanded() {
return mIsExpanded;
}
@@ -5583,10 +5565,6 @@
mTouchHandler = touchHandler;
}
- boolean isSwipingInProgress() {
- return mSwipingInProgress;
- }
-
boolean getCheckSnoozeLeaveBehind() {
return mCheckForLeavebehind;
}
@@ -5661,9 +5639,17 @@
mSectionsManager.updateSectionBoundaries(reason);
}
+ boolean isSilkDismissEnabled() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.SHOW_NEW_NOTIF_DISMISS, 1 /* enabled by default */) == 1;
+ }
+
void updateContinuousBackgroundDrawing() {
+ if (isSilkDismissEnabled()) {
+ return;
+ }
boolean continuousBackground = !mAmbientState.isFullyAwake()
- && !mAmbientState.getDraggedViews().isEmpty();
+ && mSwipeHelper.isSwiping();
if (continuousBackground != mContinuousBackgroundUpdate) {
mContinuousBackgroundUpdate = continuousBackground;
if (continuousBackground) {
@@ -5677,7 +5663,7 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
void updateContinuousShadowDrawing() {
boolean continuousShadowUpdate = mAnimationRunning
- || !mAmbientState.getDraggedViews().isEmpty();
+ || mSwipeHelper.isSwiping();
if (continuousShadowUpdate != mContinuousShadowUpdate) {
if (continuousShadowUpdate) {
getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7cee365..006d8da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -361,7 +361,6 @@
@Override
public void onDragCancelled(View v) {
- mView.setSwipingInProgress(false);
mFalsingManager.onNotificationStopDismissing();
}
@@ -392,14 +391,10 @@
*/
public void handleChildViewDismissed(View view) {
- mView.setSwipingInProgress(false);
if (mView.getDismissAllInProgress()) {
return;
}
-
- mView.removeDraggedView(view);
- mView.updateContinuousShadowDrawing();
-
+ mView.onSwipeEnd();
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
if (row.isHeadsUp()) {
@@ -454,18 +449,12 @@
@Override
public void onBeginDrag(View v) {
mFalsingManager.onNotificationStartDismissing();
- mView.setSwipingInProgress(true);
- mView.addDraggedView(v);
- mView.updateContinuousShadowDrawing();
- mView.updateContinuousBackgroundDrawing();
- mView.requestChildrenUpdate();
+ mView.onSwipeBegin();
}
@Override
public void onChildSnappedBack(View animView, float targetLeft) {
- mView.addDraggedView(animView);
- mView.updateContinuousShadowDrawing();
- mView.updateContinuousBackgroundDrawing();
+ mView.onSwipeEnd();
if (animView instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
if (row.isPinned() && !canChildBeDismissed(row)
@@ -1536,12 +1525,12 @@
NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
boolean expandWantsIt = false;
- boolean swipingInProgress = mView.isSwipingInProgress();
- if (!swipingInProgress && !mView.getOnlyScrollingInThisMotion() && guts == null) {
+ if (!mSwipeHelper.isSwiping()
+ && !mView.getOnlyScrollingInThisMotion() && guts == null) {
expandWantsIt = mView.getExpandHelper().onInterceptTouchEvent(ev);
}
boolean scrollWantsIt = false;
- if (!swipingInProgress && !mView.isExpandingNotification()) {
+ if (!mSwipeHelper.isSwiping() && !mView.isExpandingNotification()) {
scrollWantsIt = mView.onInterceptTouchEventScroll(ev);
}
boolean swipeWantsIt = false;
@@ -1582,10 +1571,9 @@
|| ev.getActionMasked() == MotionEvent.ACTION_UP;
mView.handleEmptySpaceClick(ev);
boolean expandWantsIt = false;
- boolean swipingInProgress = mView.getSwipingInProgress();
boolean onlyScrollingInThisMotion = mView.getOnlyScrollingInThisMotion();
boolean expandingNotification = mView.isExpandingNotification();
- if (mView.getIsExpanded() && !swipingInProgress && !onlyScrollingInThisMotion
+ if (mView.getIsExpanded() && !mSwipeHelper.isSwiping() && !onlyScrollingInThisMotion
&& guts == null) {
ExpandHelper expandHelper = mView.getExpandHelper();
if (isCancelOrUp) {
@@ -1600,7 +1588,7 @@
}
}
boolean scrollerWantsIt = false;
- if (mView.isExpanded() && !swipingInProgress && !expandingNotification
+ if (mView.isExpanded() && !mSwipeHelper.isSwiping() && !expandingNotification
&& !mView.getDisallowScrollingInThisMotion()) {
scrollerWantsIt = mView.onScrollTouch(ev);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
deleted file mode 100644
index 78ea5c0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.systemui.R;
-
-/**
- * Detects a double tap.
- */
-public class DoubleTapHelper {
-
- private static final long DOUBLETAP_TIMEOUT_MS = 1200;
-
- private final View mView;
- private final ActivationListener mActivationListener;
- private final DoubleTapListener mDoubleTapListener;
- private final SlideBackListener mSlideBackListener;
- private final DoubleTapLogListener mDoubleTapLogListener;
-
- private float mTouchSlop;
- private float mDoubleTapSlop;
-
- private boolean mActivated;
-
- private float mDownX;
- private float mDownY;
- private boolean mTrackTouch;
-
- private float mActivationX;
- private float mActivationY;
- private Runnable mTapTimeoutRunnable = this::makeInactive;
-
- public DoubleTapHelper(View view, ActivationListener activationListener,
- DoubleTapListener doubleTapListener, SlideBackListener slideBackListener,
- DoubleTapLogListener doubleTapLogListener) {
- mTouchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
- mDoubleTapSlop = view.getResources().getDimension(R.dimen.double_tap_slop);
- mView = view;
-
- mActivationListener = activationListener;
- mDoubleTapListener = doubleTapListener;
- mSlideBackListener = slideBackListener;
- mDoubleTapLogListener = doubleTapLogListener;
- }
-
- public boolean onTouchEvent(MotionEvent event) {
- return onTouchEvent(event, Integer.MAX_VALUE);
- }
-
- public boolean onTouchEvent(MotionEvent event, int maxTouchableHeight) {
- int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mDownX = event.getX();
- mDownY = event.getY();
- mTrackTouch = true;
- if (mDownY > maxTouchableHeight) {
- mTrackTouch = false;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- if (!isWithinTouchSlop(event)) {
- makeInactive();
- mTrackTouch = false;
- }
- break;
- case MotionEvent.ACTION_UP:
- if (isWithinTouchSlop(event)) {
- if (mSlideBackListener != null && mSlideBackListener.onSlideBack()) {
- return true;
- }
- if (!mActivated) {
- makeActive();
- mView.postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
- mActivationX = event.getX();
- mActivationY = event.getY();
- } else {
- boolean withinDoubleTapSlop = isWithinDoubleTapSlop(event);
- if (mDoubleTapLogListener != null) {
- mDoubleTapLogListener.onDoubleTapLog(withinDoubleTapSlop,
- event.getX() - mActivationX,
- event.getY() - mActivationY);
- }
- if (withinDoubleTapSlop) {
- makeInactive();
- if (!mDoubleTapListener.onDoubleTap()) {
- return false;
- }
- } else {
- makeInactive();
- mTrackTouch = false;
- }
- }
- } else {
- makeInactive();
- mTrackTouch = false;
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- makeInactive();
- mTrackTouch = false;
- break;
- default:
- break;
- }
- return mTrackTouch;
- }
-
- private void makeActive() {
- if (!mActivated) {
- mActivated = true;
- mActivationListener.onActiveChanged(true);
- }
- }
-
- private void makeInactive() {
- if (mActivated) {
- mActivated = false;
- mActivationListener.onActiveChanged(false);
- mView.removeCallbacks(mTapTimeoutRunnable);
- }
- }
-
- private boolean isWithinTouchSlop(MotionEvent event) {
- return Math.abs(event.getX() - mDownX) < mTouchSlop
- && Math.abs(event.getY() - mDownY) < mTouchSlop;
- }
-
- public boolean isWithinDoubleTapSlop(MotionEvent event) {
- if (!mActivated) {
- // If we're not activated there's no double tap slop to satisfy.
- return true;
- }
-
- return Math.abs(event.getX() - mActivationX) < mDoubleTapSlop
- && Math.abs(event.getY() - mActivationY) < mDoubleTapSlop;
- }
-
- @FunctionalInterface
- public interface ActivationListener {
- void onActiveChanged(boolean active);
- }
-
- @FunctionalInterface
- public interface DoubleTapListener {
- boolean onDoubleTap();
- }
-
- @FunctionalInterface
- public interface SlideBackListener {
- boolean onSlideBack();
- }
-
- @FunctionalInterface
- public interface DoubleTapLogListener {
- void onDoubleTapLog(boolean accepted, float dx, float dy);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationTapHelper.java
new file mode 100644
index 0000000..50c8e2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationTapHelper.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.view.MotionEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import javax.inject.Inject;
+
+/**
+ * Detects single and double taps on notifications.
+ */
+public class NotificationTapHelper {
+
+ public static final long DOUBLE_TAP_TIMEOUT_MS = 1200;
+
+ private final ActivationListener mActivationListener;
+ private final DoubleTapListener mDoubleTapListener;
+ private final FalsingManager mFalsingManager;
+ private final DelayableExecutor mExecutor;
+ private final SlideBackListener mSlideBackListener;
+
+ private boolean mTrackTouch;
+ private Runnable mTimeoutCancel;
+
+ private NotificationTapHelper(FalsingManager falsingManager, DelayableExecutor executor,
+ ActivationListener activationListener, DoubleTapListener doubleTapListener,
+ SlideBackListener slideBackListener) {
+ mFalsingManager = falsingManager;
+ mExecutor = executor;
+ mActivationListener = activationListener;
+ mDoubleTapListener = doubleTapListener;
+ mSlideBackListener = slideBackListener;
+ }
+
+ @VisibleForTesting
+ boolean onTouchEvent(MotionEvent event) {
+ return onTouchEvent(event, Integer.MAX_VALUE);
+ }
+
+ /** Call to have the helper process a touch event. */
+ public boolean onTouchEvent(MotionEvent event, int maxTouchableHeight) {
+ int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mTrackTouch = event.getY() <= maxTouchableHeight;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mTrackTouch && mFalsingManager.isFalseTap(false)) {
+ makeInactive();
+ mTrackTouch = false;
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ makeInactive();
+ mTrackTouch = false;
+ break;
+ case MotionEvent.ACTION_UP:
+ mTrackTouch = false;
+
+ // 1) See if we have confidence that we can activate after a single tap.
+ // 2) Else, see if it looks like a tap at all and check for a double-tap.
+ if (!mFalsingManager.isFalseTap(true)) {
+ makeInactive();
+ return mDoubleTapListener.onDoubleTap();
+ } else if (!mFalsingManager.isFalseTap(false)) {
+ if (mSlideBackListener != null && mSlideBackListener.onSlideBack()) {
+ return true;
+ }
+ if (mTimeoutCancel == null) {
+ // first tap
+ makeActive();
+ return true;
+ } else {
+ // second tap
+ makeInactive();
+ if (!mFalsingManager.isFalseDoubleTap()) {
+ return mDoubleTapListener.onDoubleTap();
+ }
+ }
+ } else {
+ makeInactive();
+ }
+ break;
+ default:
+ break;
+ }
+ return mTrackTouch;
+ }
+
+ private void makeActive() {
+ mTimeoutCancel = mExecutor.executeDelayed(this::makeInactive, DOUBLE_TAP_TIMEOUT_MS);
+ mActivationListener.onActiveChanged(true);
+ }
+
+ private void makeInactive() {
+ mActivationListener.onActiveChanged(false);
+ if (mTimeoutCancel != null) {
+ mTimeoutCancel.run();
+ mTimeoutCancel = null;
+ }
+ }
+
+ /** */
+ @FunctionalInterface
+ public interface ActivationListener {
+ /** */
+ void onActiveChanged(boolean active);
+ }
+
+ /** */
+ @FunctionalInterface
+ public interface DoubleTapListener {
+ /** */
+ boolean onDoubleTap();
+ }
+
+ /** */
+ @FunctionalInterface
+ public interface SlideBackListener {
+ /** */
+ boolean onSlideBack();
+ }
+
+ /**
+ * Injectable factory that creates a {@link NotificationTapHelper}.
+ */
+ public static class Factory {
+ private final FalsingManager mFalsingManager;
+ private final DelayableExecutor mDelayableExecutor;
+
+ @Inject
+ public Factory(FalsingManager falsingManager, @Main DelayableExecutor delayableExecutor) {
+ mFalsingManager = falsingManager;
+ mDelayableExecutor = delayableExecutor;
+ }
+
+ /** Create a {@link NotificationTapHelper} */
+ public NotificationTapHelper create(ActivationListener activationListener,
+ DoubleTapListener doubleTapListener, SlideBackListener slideBackListener) {
+ return new NotificationTapHelper(mFalsingManager, mDelayableExecutor,
+ activationListener, doubleTapListener, slideBackListener);
+ }
+ }
+}
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 5d84245..e6ac7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -147,7 +147,6 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.charging.WirelessChargingAnimation;
-import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.DemoMode;
@@ -1250,10 +1249,6 @@
message.write(SystemProperties.get("ro.serialno"));
message.write("\n");
- PrintWriter falsingPw = new PrintWriter(message);
- FalsingLog.dump(falsingPw);
- falsingPw.flush();
-
startActivityDismissingKeyguard(Intent.createChooser(new Intent(Intent.ACTION_SEND)
.setType("*/*")
.putExtra(Intent.EXTRA_SUBJECT, "Rejected touch report")
@@ -2676,9 +2671,6 @@
mLightBarController.dump(fd, pw, args);
}
- mFalsingManager.dump(pw);
- FalsingLog.dump(pw);
-
pw.println("SharedPreferences:");
for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
pw.print(" "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f9dfd7a..b912614 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -26,13 +26,17 @@
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.os.SystemClock;
+import android.util.TypedValue;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
+import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
@@ -160,6 +164,8 @@
private boolean mPulsing;
private boolean mGesturalNav;
private boolean mIsDocked;
+ private boolean mIsPortraitMode;
+ private int mScreenWidthDp;
protected boolean mFirstUpdate = true;
protected boolean mLastShowing;
@@ -174,6 +180,7 @@
private boolean mLastPulsing;
private int mLastBiometricMode;
private boolean mLastLockVisible;
+ private boolean mLastLockOrientationIsPortrait;
private OnDismissAction mAfterKeyguardGoneAction;
private Runnable mKeyguardGoneCancelAction;
@@ -263,6 +270,9 @@
mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
mStatusBarStateController.addCallback(this);
mConfigurationController.addCallback(this);
+ mIsPortraitMode = mContext.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_PORTRAIT;
+ mScreenWidthDp = mContext.getResources().getConfiguration().screenWidthDp;
mGesturalNav = QuickStepContract.isGesturalMode(
mNavigationModeController.addListener(this));
if (mDockManager != null) {
@@ -272,6 +282,18 @@
}
@Override
+ public void onDensityOrFontScaleChanged() {
+ hideBouncer(true /* destroyView */);
+ }
+
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ mIsPortraitMode = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT;
+ mScreenWidthDp = newConfig.screenWidthDp;
+ updateLockIcon();
+ }
+
+ @Override
public void onPanelExpansionChanged(float expansion, boolean tracking) {
// We don't want to translate the bounce when:
// • Keyguard is occluded, because we're in a FLAG_SHOW_WHEN_LOCKED activity and need to
@@ -317,14 +339,32 @@
if (mLockIconContainer == null) {
return;
}
+
boolean keyguardWithoutQs = mStatusBarStateController.getState() == StatusBarState.KEYGUARD
&& !mNotificationPanelViewController.isQsExpanded();
boolean lockVisible = (mBouncer.isShowing() || keyguardWithoutQs)
&& !mBouncer.isAnimatingAway() && !mKeyguardStateController.isKeyguardFadingAway();
+ boolean orientationChange =
+ lockVisible && (mLastLockOrientationIsPortrait != mIsPortraitMode);
- if (mLastLockVisible != lockVisible) {
+ if (mLastLockVisible != lockVisible || orientationChange) {
mLastLockVisible = lockVisible;
+ mLastLockOrientationIsPortrait = mIsPortraitMode;
if (lockVisible) {
+ FrameLayout.LayoutParams lp =
+ (FrameLayout.LayoutParams) mLockIconContainer.getLayoutParams();
+ if (mIsPortraitMode) {
+ lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ } else {
+ final int width = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ mScreenWidthDp,
+ mContext.getResources().getDisplayMetrics()) / 3;
+ mLockIconContainer.setMinimumWidth(width);
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
+ }
+ mLockIconContainer.setLayoutParams(lp);
+
CrossFadeHelper.fadeIn(mLockIconContainer,
AppearAnimationUtils.DEFAULT_APPEAR_DURATION /* duration */,
0 /* delay */);
@@ -685,11 +725,6 @@
}
@Override
- public void onDensityOrFontScaleChanged() {
- hideBouncer(true /* destroyView */);
- }
-
- @Override
public void onNavigationModeChanged(int mode) {
boolean gesturalNav = QuickStepContract.isGesturalMode(mode);
if (gesturalNav != mGesturalNav) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 4b4e1df..cdc5d87 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -24,8 +24,6 @@
import android.view.View;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QuickQSPanel;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import java.lang.reflect.InvocationTargetException;
@@ -93,16 +91,6 @@
* Creates the NotificationStackScrollLayout.
*/
NotificationStackScrollLayout createNotificationStackScrollLayout();
-
- /**
- * Creates the QSPanel.
- */
- QSPanel createQSPanel();
-
- /**
- * Creates the QuickQSPanel.
- */
- QuickQSPanel createQuickQSPanel();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index 777db95..5088a53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -211,6 +211,16 @@
}
@Test
+ public void testOnDialogAnimatedIn_sendsCancelReason_whenPendingDismiss() {
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
+ mAuthContainer.mContainerState = AuthContainerView.STATE_PENDING_DISMISS;
+ mAuthContainer.onDialogAnimatedIn();
+ verify(mCallback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+ eq(null) /* credentialAttestation */);
+ }
+
+ @Test
public void testLayoutParams_hasSecureWindowFlag() {
final IBinder windowToken = mock(IBinder.class);
final WindowManager.LayoutParams layoutParams =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
deleted file mode 100644
index c3c9ecc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
+++ /dev/null
@@ -1,128 +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.classifier;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_MANAGER_ENABLED;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.assertThat;
-
-import android.provider.DeviceConfig;
-import android.testing.AndroidTestingRunner;
-import android.util.DisplayMetrics;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.classifier.brightline.BrightLineFalsingManager;
-import com.android.systemui.classifier.brightline.FalsingDataProvider;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.dock.DockManagerFake;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.DeviceConfigProxyFake;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.sensors.ProximitySensor;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.utils.leaks.FakeBatteryController;
-import com.android.systemui.utils.leaks.LeakCheckedTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class FalsingManagerProxyTest extends LeakCheckedTest {
- @Mock(stubOnly = true)
- PluginManager mPluginManager;
- @Mock(stubOnly = true)
- ProximitySensor mProximitySensor;
- @Mock(stubOnly = true)
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock DumpManager mDumpManager;
- private FalsingManagerProxy mProxy;
- private DeviceConfigProxy mDeviceConfig;
- private FalsingDataProvider mFalsingDataProvider;
- private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
- private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
- private DockManager mDockManager = new DockManagerFake();
- private StatusBarStateController mStatusBarStateController =
- new StatusBarStateControllerImpl(new UiEventLoggerFake());
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mDeviceConfig = new DeviceConfigProxyFake();
- mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- BRIGHTLINE_FALSING_MANAGER_ENABLED, "false", false);
- mFalsingDataProvider = new FalsingDataProvider(
- new DisplayMetrics(), new FakeBatteryController(getLeakCheck()));
- }
-
- @After
- public void tearDown() {
- if (mProxy != null) {
- mProxy.cleanup();
- }
- }
-
- @Test
- public void test_brightLineFalsingManagerDisabled() {
- mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mExecutor,
- mProximitySensor, mDeviceConfig, mDockManager, mKeyguardUpdateMonitor,
- mDumpManager, mUiBgExecutor, mStatusBarStateController, mFalsingDataProvider);
- assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
- }
-
- @Test
- public void test_brightLineFalsingManagerEnabled() throws InterruptedException {
- mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
- mExecutor.runAllReady();
- mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mExecutor,
- mProximitySensor, mDeviceConfig, mDockManager, mKeyguardUpdateMonitor,
- mDumpManager, mUiBgExecutor, mStatusBarStateController, mFalsingDataProvider);
- assertThat(mProxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class));
- }
-
- @Test
- public void test_brightLineFalsingManagerToggled() throws InterruptedException {
- mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mExecutor,
- mProximitySensor, mDeviceConfig, mDockManager, mKeyguardUpdateMonitor,
- mDumpManager, mUiBgExecutor, mStatusBarStateController, mFalsingDataProvider);
- assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
-
- mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
- mExecutor.runAllReady();
- assertThat(mProxy.getInternalFalsingManager(),
- instanceOf(BrightLineFalsingManager.class));
-
- mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- BRIGHTLINE_FALSING_MANAGER_ENABLED, "false", false);
- mExecutor.runAllReady();
- assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java
index 061664b..30dddc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java
@@ -20,14 +20,18 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.content.res.Resources;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.DisplayMetrics;
+import android.view.ViewConfiguration;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.R;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.statusbar.StatusBarState;
@@ -37,6 +41,7 @@
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.utils.leaks.FakeBatteryController;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -56,6 +61,10 @@
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
private ProximitySensor mProximitySensor;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private ViewConfiguration mViewConfiguration;
private SysuiStatusBarStateController mStatusBarStateController;
private FalsingDataProvider mFalsingDataProvider;
private FakeBatteryController mFakeBatteryController;
@@ -71,14 +80,17 @@
dm.ydpi = 100;
dm.widthPixels = 100;
dm.heightPixels = 100;
- mFalsingDataProvider = new FalsingDataProvider(dm, mFakeBatteryController);
+ mFalsingDataProvider = new FalsingDataProvider(dm, mFakeBatteryController,
+ new FakeSystemClock());
DeviceConfigProxy deviceConfigProxy = new DeviceConfigProxyFake();
DockManager dockManager = new DockManagerFake();
mStatusBarStateController = new StatusBarStateControllerImpl(new UiEventLoggerFake());
mStatusBarStateController.setState(StatusBarState.KEYGUARD);
+ when(mResources.getDimension(R.dimen.double_tap_slop)).thenReturn(1f);
+ when(mViewConfiguration.getScaledTouchSlop()).thenReturn(1);
mFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
- mKeyguardUpdateMonitor, mProximitySensor, deviceConfigProxy, dockManager,
- mStatusBarStateController);
+ mKeyguardUpdateMonitor, mProximitySensor, deviceConfigProxy, mResources,
+ mViewConfiguration, dockManager, mStatusBarStateController);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
index a4d198a..69d39fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
@@ -21,6 +21,7 @@
import android.util.DisplayMetrics;
import android.view.MotionEvent;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.utils.leaks.FakeBatteryController;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -44,7 +45,8 @@
displayMetrics.widthPixels = 1000;
displayMetrics.heightPixels = 1000;
mFakeBatteryController = new FakeBatteryController(getLeakCheck());
- mDataProvider = new FalsingDataProvider(displayMetrics, mFakeBatteryController);
+ mDataProvider = new FalsingDataProvider(displayMetrics, mFakeBatteryController,
+ new FakeSystemClock());
mDataProvider.setInteractionType(UNLOCK);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DoubleTapClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DoubleTapClassifierTest.java
new file mode 100644
index 0000000..9f3a1e4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DoubleTapClassifierTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier.brightline;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DoubleTapClassifierTest extends ClassifierTest {
+
+ private static final int TOUCH_SLOP = 100;
+ private static final long DOUBLE_TAP_TIMEOUT_MS = 100;
+
+ private List<MotionEvent> mMotionEvents = new ArrayList<>();
+ private final Deque<List<MotionEvent>> mHistoricalMotionEvents = new LinkedList<>();
+
+ @Mock
+ private FalsingDataProvider mDataProvider;
+ @Mock
+ private SingleTapClassifier mSingleTapClassifier;
+ private DoubleTapClassifier mClassifier;
+
+ @Before
+ public void setup() {
+ super.setup();
+ MockitoAnnotations.initMocks(this);
+ mClassifier = new DoubleTapClassifier(mDataProvider, mSingleTapClassifier, TOUCH_SLOP,
+ DOUBLE_TAP_TIMEOUT_MS);
+ doReturn(mHistoricalMotionEvents).when(mDataProvider).getHistoricalMotionEvents();
+ }
+
+ @After
+ public void tearDown() {
+ for (MotionEvent motionEvent : mMotionEvents) {
+ motionEvent.recycle();
+ }
+
+ mMotionEvents.clear();
+ super.tearDown();
+ }
+
+
+ @Test
+ public void testSingleTap() {
+ when(mSingleTapClassifier.isTap(anyList())).thenReturn(true);
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, TOUCH_SLOP, 1);
+
+ boolean result = mClassifier.isFalseTouch();
+ assertThat("Single tap recognized as a valid double tap", result, is(true));
+ }
+
+ @Test
+ public void testDoubleTap() {
+ when(mSingleTapClassifier.isTap(anyList())).thenReturn(true);
+
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+ archiveMotionEvents();
+
+ addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, TOUCH_SLOP, TOUCH_SLOP);
+ addMotionEvent(2, 3, MotionEvent.ACTION_UP, TOUCH_SLOP, TOUCH_SLOP);
+
+ boolean result = mClassifier.isFalseTouch();
+ assertThat(mClassifier.getReason(), result, is(false));
+ }
+
+ @Test
+ public void testBadFirstTap() {
+ when(mSingleTapClassifier.isTap(anyList())).thenReturn(false, true);
+
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+ archiveMotionEvents();
+
+ addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(2, 3, MotionEvent.ACTION_UP, 1, 1);
+
+ boolean result = mClassifier.isFalseTouch();
+ assertThat("Bad first touch allowed", result, is(true));
+ }
+
+ @Test
+ public void testBadSecondTap() {
+ when(mSingleTapClassifier.isTap(anyList())).thenReturn(true, false);
+
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+ archiveMotionEvents();
+
+ addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(2, 3, MotionEvent.ACTION_UP, 1, 1);
+
+ boolean result = mClassifier.isFalseTouch();
+ assertThat("Bad second touch allowed", result, is(true));
+ }
+
+ @Test
+ public void testBadTouchSlop() {
+ when(mSingleTapClassifier.isTap(anyList())).thenReturn(true);
+
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+ archiveMotionEvents();
+
+ addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, TOUCH_SLOP + 1, TOUCH_SLOP);
+ addMotionEvent(2, 3, MotionEvent.ACTION_UP, TOUCH_SLOP, TOUCH_SLOP + 1);
+
+ boolean result = mClassifier.isFalseTouch();
+ assertThat("Sloppy second touch allowed", result, is(true));
+ }
+
+ @Test
+ public void testBadTouchSlow() {
+ when(mSingleTapClassifier.isTap(anyList())).thenReturn(true);
+
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+ archiveMotionEvents();
+
+ addMotionEvent(DOUBLE_TAP_TIMEOUT_MS + 1, DOUBLE_TAP_TIMEOUT_MS + 1,
+ MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(DOUBLE_TAP_TIMEOUT_MS + 1, DOUBLE_TAP_TIMEOUT_MS + 2,
+ MotionEvent.ACTION_UP, 1, 1);
+
+ boolean result = mClassifier.isFalseTouch();
+ assertThat("Slow second tap allowed", result, is(true));
+ }
+
+ private void addMotionEvent(long downMs, long eventMs, int action, int x, int y) {
+ MotionEvent ev = MotionEvent.obtain(downMs, eventMs, action, x, y, 0);
+ mMotionEvents.add(ev);
+ when(mDataProvider.getRecentMotionEvents()).thenReturn(mMotionEvents);
+ }
+
+ private void archiveMotionEvents() {
+ mHistoricalMotionEvents.addFirst(mMotionEvents);
+ doReturn(mHistoricalMotionEvents).when(mDataProvider).getHistoricalMotionEvents();
+ mMotionEvents = new ArrayList<>();
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java
index f13bc73..be38f4419 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java
@@ -26,6 +26,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.utils.leaks.FakeBatteryController;
import org.junit.After;
@@ -51,7 +52,8 @@
displayMetrics.ydpi = 100;
displayMetrics.widthPixels = 1000;
displayMetrics.heightPixels = 1000;
- mDataProvider = new FalsingDataProvider(displayMetrics, mFakeBatteryController);
+ mDataProvider = new FalsingDataProvider(displayMetrics, mFakeBatteryController,
+ new FakeSystemClock());
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/SingleTapClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/SingleTapClassifierTest.java
new file mode 100644
index 0000000..642b077
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/SingleTapClassifierTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.classifier.brightline;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class SingleTapClassifierTest extends ClassifierTest {
+
+ private static final int TOUCH_SLOP = 100;
+
+ private final List<MotionEvent> mMotionEvents = new ArrayList<>();
+
+ @Mock
+ private FalsingDataProvider mDataProvider;
+ private SingleTapClassifier mClassifier;
+
+ @Before
+ public void setup() {
+ super.setup();
+ MockitoAnnotations.initMocks(this);
+ mClassifier = new SingleTapClassifier(mDataProvider, TOUCH_SLOP);
+ }
+
+ @After
+ public void tearDown() {
+ for (MotionEvent motionEvent : mMotionEvents) {
+ motionEvent.recycle();
+ }
+
+ mMotionEvents.clear();
+ super.tearDown();
+ }
+
+
+ @Test
+ public void testSimpleTap_XSlop() {
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, TOUCH_SLOP, 1);
+
+ assertThat(mClassifier.isFalseTouch(), is(false));
+
+ mMotionEvents.clear();
+
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, -TOUCH_SLOP + 2, 1);
+
+ assertThat(mClassifier.isFalseTouch(), is(false));
+
+ }
+
+ @Test
+ public void testSimpleTap_YSlop() {
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, TOUCH_SLOP);
+
+ assertThat(mClassifier.isFalseTouch(), is(false));
+
+ mMotionEvents.clear();
+
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, -TOUCH_SLOP + 2);
+
+ assertThat(mClassifier.isFalseTouch(), is(false));
+ }
+
+
+ @Test
+ public void testFalseTap_XSlop() {
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, TOUCH_SLOP + 1, 1);
+
+ assertThat(mClassifier.isFalseTouch(), is(true));
+
+ mMotionEvents.clear();
+
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, -TOUCH_SLOP - 1, 1);
+
+ assertThat(mClassifier.isFalseTouch(), is(true));
+
+ }
+
+ @Test
+ public void testFalseTap_YSlop() {
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, TOUCH_SLOP + 1);
+
+ assertThat(mClassifier.isFalseTouch(), is(true));
+
+ mMotionEvents.clear();
+
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, -TOUCH_SLOP - 1);
+
+ assertThat(mClassifier.isFalseTouch(), is(true));
+ }
+
+ @Test
+ public void testLargeMovementFalses() {
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_MOVE, 1, TOUCH_SLOP + 1);
+ addMotionEvent(0, 2, MotionEvent.ACTION_UP, 1, 1);
+
+ assertThat(mClassifier.isFalseTouch(), is(true));
+ }
+
+ @Test
+ public void testDirectlySuppliedMotionEvents() {
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+ assertThat(mClassifier.isTap(mMotionEvents), is(true));
+
+ addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+ addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, TOUCH_SLOP + 1);
+
+ assertThat(mClassifier.isTap(mMotionEvents), is(false));
+
+ }
+
+ private void addMotionEvent(long downMs, long eventMs, int action, int x, int y) {
+ MotionEvent ev = MotionEvent.obtain(downMs, eventMs, action, x, y, 0);
+ mMotionEvents.add(ev);
+ when(mDataProvider.getRecentMotionEvents()).thenReturn(mMotionEvents);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBufferTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBufferTest.java
new file mode 100644
index 0000000..1dfffb2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBufferTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier.brightline;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class TimeLimitedMotionEventBufferTest extends SysuiTestCase {
+
+ private static final long MAX_AGE_MS = 100;
+
+ private TimeLimitedMotionEventBuffer mBuffer;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mBuffer = new TimeLimitedMotionEventBuffer(MAX_AGE_MS);
+ }
+
+ @After
+ public void tearDown() {
+ for (MotionEvent motionEvent : mBuffer) {
+ motionEvent.recycle();
+ }
+ mBuffer.clear();
+ }
+
+ @Test
+ public void testAllEventsRetained() {
+ MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ MotionEvent eventC = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ MotionEvent eventD = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 0, 0, 0);
+
+ mBuffer.add(eventA);
+ mBuffer.add(eventB);
+ mBuffer.add(eventC);
+ mBuffer.add(eventD);
+
+ assertThat(mBuffer.size(), is(4));
+ }
+
+ @Test
+ public void testOlderEventsRemoved() {
+ MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ MotionEvent eventC = MotionEvent.obtain(
+ 0, MAX_AGE_MS + 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ MotionEvent eventD = MotionEvent.obtain(
+ 0, MAX_AGE_MS + 2, MotionEvent.ACTION_UP, 0, 0, 0);
+
+ mBuffer.add(eventA);
+ mBuffer.add(eventB);
+ assertThat(mBuffer.size(), is(2));
+
+ mBuffer.add(eventC);
+ mBuffer.add(eventD);
+ assertThat(mBuffer.size(), is(2));
+
+ assertThat(mBuffer.get(0), is(eventC));
+ assertThat(mBuffer.get(1), is(eventD));
+ }
+
+ @Test
+ public void testFullyExpired() {
+ MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ MotionEvent eventC = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ MotionEvent eventD = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 0, 0, 0);
+
+ mBuffer.add(eventA);
+ mBuffer.add(eventB);
+ mBuffer.add(eventC);
+ mBuffer.add(eventD);
+
+ assertThat(mBuffer.isFullyExpired(2), is(false));
+ assertThat(mBuffer.isFullyExpired(6), is(true));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 0fe44ad..0dc268a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -42,7 +42,9 @@
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.customize.QSCustomizerController;
+import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.util.animation.DisappearParameters;
import org.junit.Before;
import org.junit.Test;
@@ -75,6 +77,8 @@
@Mock
private MetricsLogger mMetricsLogger;
private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake();
+ @Mock
+ private QSLogger mQSLogger;
private DumpManager mDumpManager = new DumpManager();
@Mock
QSTileImpl mQSTile;
@@ -89,9 +93,10 @@
private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
QSCustomizerController qsCustomizerController, MediaHost mediaHost,
- MetricsLogger metricsLogger, UiEventLogger uiEventLogger, DumpManager dumpManager) {
- super(view, host, qsCustomizerController, mediaHost,
- metricsLogger, uiEventLogger, dumpManager);
+ MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
+ DumpManager dumpManager) {
+ super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
+ qsLogger, dumpManager);
}
@Override
@@ -109,13 +114,17 @@
when(mQSPanel.openPanelEvent()).thenReturn(QSEvent.QS_PANEL_EXPANDED);
when(mQSPanel.closePanelEvent()).thenReturn(QSEvent.QS_PANEL_COLLAPSED);
when(mQSPanel.createRegularTileLayout()).thenReturn(mPagedTileLayout);
+ when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout);
+ when(mQSTile.getTileSpec()).thenReturn("dnd");
when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
when(mQSTileHost.createTileView(eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
when(mQSTileRevealControllerFactory.create(any(), any()))
.thenReturn(mQSTileRevealController);
+ when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
- mQSCustomizerController, mMediaHost, mMetricsLogger, mUiEventLogger, mDumpManager);
+ mQSCustomizerController, mMediaHost,
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
reset(mQSTileRevealController);
@@ -125,9 +134,9 @@
public void testSetRevealExpansion_preAttach() {
mController.onViewDetached();
- QSPanelControllerBase<QSPanel> controller = new QSPanelControllerBase<QSPanel>(
- mQSPanel, mQSTileHost, mQSCustomizerController, mMediaHost, mMetricsLogger,
- mUiEventLogger, mDumpManager) {
+ QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
+ mQSTileHost, mQSCustomizerController, mMediaHost,
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
@Override
protected QSTileRevealController createTileRevealController() {
return mQSTileRevealController;
@@ -159,14 +168,18 @@
@Test
public void testSetExpanded_Metrics() {
+ when(mQSPanel.isExpanded()).thenReturn(false);
mController.setExpanded(true);
verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(true));
+ verify(mQSLogger).logPanelExpanded(true, mQSPanel.getDumpableTag());
assertEquals(1, mUiEventLogger.numLogs());
assertEquals(QSEvent.QS_PANEL_EXPANDED.getId(), mUiEventLogger.eventId(0));
mUiEventLogger.getLogs().clear();
+ when(mQSPanel.isExpanded()).thenReturn(true);
mController.setExpanded(false);
verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(false));
+ verify(mQSLogger).logPanelExpanded(false, mQSPanel.getDumpableTag());
assertEquals(1, mUiEventLogger.numLogs());
assertEquals(QSEvent.QS_PANEL_COLLAPSED.getId(), mUiEventLogger.eventId(0));
mUiEventLogger.getLogs().clear();
@@ -195,4 +208,14 @@
assertEquals(expected, w.getBuffer().toString());
}
+ @Test
+ public void setListening() {
+ mController.setListening(true);
+ verify(mQSLogger).logAllTilesChangeListening(true, "QSPanel", "dnd");
+ verify(mPagedTileLayout).setListening(true, mUiEventLogger);
+
+ mController.setListening(false);
+ verify(mQSLogger).logAllTilesChangeListening(false, "QSPanel", "dnd");
+ verify(mPagedTileLayout).setListening(false, mUiEventLogger);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index 9090b9b..a6c2d08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -36,6 +36,7 @@
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.customize.QSCustomizerController;
+import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessSlider;
@@ -77,6 +78,8 @@
@Mock
private QSSecurityFooter mQSSecurityFooter;
@Mock
+ private QSLogger mQSLogger;
+ @Mock
private BrightnessController.Factory mBrightnessControllerFactory;
@Mock
private BrightnessController mBrightnessController;
@@ -91,7 +94,6 @@
@Mock
PagedTileLayout mPagedTileLayout;
-
private QSPanelController mController;
@Before
@@ -112,9 +114,9 @@
when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
mController = new QSPanelController(mQSPanel, mQSSecurityFooter, mTunerService,
- mQSTileHost, mQSCustomizerController, mMediaHost, mQSTileRevealControllerFactory,
- mDumpManager, mMetricsLogger, mUiEventLogger, mBrightnessControllerFactory,
- mToggleSliderViewControllerFactory);
+ mQSTileHost, mQSCustomizerController, true, mMediaHost,
+ mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
+ mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory);
mController.init();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index 450ffac..a726181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -17,13 +17,10 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.Context;
-import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -32,18 +29,11 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.util.animation.DisappearParameters;
-import com.android.systemui.util.animation.UniqueObjectHostView;
import org.junit.Before;
import org.junit.Test;
@@ -58,7 +48,6 @@
@SmallTest
public class QSPanelTest extends SysuiTestCase {
- private MetricsLogger mMetricsLogger;
private TestableLooper mTestableLooper;
private QSPanel mQsPanel;
@Mock
@@ -75,30 +64,23 @@
@Mock
private QSTileView mQSTileView;
@Mock
- private MediaHost mMediaHost;
- @Mock
private ActivityStarter mActivityStarter;
- private UiEventLoggerFake mUiEventLogger;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
- // Dependencies for QSSecurityFooter
- mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
- mDependency.injectMockDependency(SecurityController.class);
- mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
- mContext.addMockSystemService(Context.USER_SERVICE, mock(UserManager.class));
- when(mMediaHost.getHostView()).thenReturn(new UniqueObjectHostView(getContext()));
- when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
+// // Dependencies for QSSecurityFooter
+// mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
+// mDependency.injectMockDependency(SecurityController.class);
+// mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+// mContext.addMockSystemService(Context.USER_SERVICE, mock(UserManager.class));
mDndTileRecord.tile = dndTile;
mDndTileRecord.tileView = mQSTileView;
- mUiEventLogger = new UiEventLoggerFake();
mTestableLooper.runWithLooper(() -> {
- mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
- mQsPanel = new QSPanel(mContext, null, mQSLogger, mMediaHost, mUiEventLogger);
+ mQsPanel = new QSPanel(mContext, null);
mQsPanel.onFinishInflate();
// Provides a parent with non-zero size for QSPanel
mParentView = new FrameLayout(mContext);
@@ -113,15 +95,6 @@
}
@Test
- public void testSetExpanded_Metrics() {
- mQsPanel.setExpanded(true);
- verify(mQSLogger).logPanelExpanded(true, mQsPanel.getDumpableTag());
-
- mQsPanel.setExpanded(false);
- verify(mQSLogger).logPanelExpanded(false, mQsPanel.getDumpableTag());
- }
-
- @Test
public void testOpenDetailsWithExistingTile_NoException() {
mTestableLooper.processAllMessages();
mQsPanel.openDetails(dndTile);
@@ -131,15 +104,6 @@
}
@Test
- public void setListening() {
- mQsPanel.setListening(true, "dnd");
- verify(mQSLogger).logAllTilesChangeListening(true, mQsPanel.getDumpableTag(), "dnd");
-
- mQsPanel.setListening(false, "dnd");
- verify(mQSLogger).logAllTilesChangeListening(false, mQsPanel.getDumpableTag(), "dnd");
- }
-
- @Test
public void testOpenDetailsWithNullParameter_NoException() {
mTestableLooper.processAllMessages();
mQsPanel.openDetails(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
new file mode 100644
index 0000000..3f2b4da
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.MediaHost
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.customize.QSCustomizerController
+import com.android.systemui.qs.logging.QSLogger
+import org.junit.After
+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.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class QuickQSPanelControllerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var quickQSPanel: QuickQSPanel
+ @Mock
+ private lateinit var qsTileHost: QSTileHost
+ @Mock
+ private lateinit var qsCustomizerController: QSCustomizerController
+ @Mock
+ private lateinit var mediaHost: MediaHost
+ @Mock
+ private lateinit var metricsLogger: MetricsLogger
+ private val uiEventLogger = UiEventLoggerFake()
+ @Mock
+ private lateinit var qsLogger: QSLogger
+ private val dumpManager = DumpManager()
+ @Mock
+ private lateinit var tile: QSTile
+ @Mock
+ private lateinit var tileLayout: TileLayout
+
+ private lateinit var controller: QuickQSPanelController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(quickQSPanel.tileLayout).thenReturn(tileLayout)
+ `when`(quickQSPanel.dumpableTag).thenReturn("")
+
+ controller = QuickQSPanelController(
+ quickQSPanel,
+ qsTileHost,
+ qsCustomizerController,
+ false,
+ mediaHost,
+ metricsLogger,
+ uiEventLogger,
+ qsLogger,
+ dumpManager
+ )
+
+ controller.init()
+ }
+
+ @After
+ fun tearDown() {
+ controller.onViewDetached()
+ }
+
+ @Test
+ fun testTileSublistWithFewerTiles_noCrash() {
+ `when`(quickQSPanel.numQuickTiles).thenReturn(3)
+
+ `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile))
+
+ controller.setTiles()
+ }
+
+ @Test
+ fun testTileSublistWithTooManyTiles() {
+ val limit = 3
+ `when`(quickQSPanel.numQuickTiles).thenReturn(limit)
+ `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
+
+ controller.setTiles()
+
+ verify(quickQSPanel, times(limit)).addTile(any())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 6c7c20a..54cee84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -75,7 +75,7 @@
public void testSetListening_CallsSetListeningOnTile() {
QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
mTileLayout.addTile(tileRecord);
- mTileLayout.setListening(true);
+ mTileLayout.setListening(true, null);
verify(tileRecord.tile, times(1)).setListening(mTileLayout, true);
}
@@ -83,14 +83,14 @@
public void testSetListening_SameValueIsNoOp() {
QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
mTileLayout.addTile(tileRecord);
- mTileLayout.setListening(false);
+ mTileLayout.setListening(false, null);
verify(tileRecord.tile, times(1)).setListening(any(), anyBoolean());
}
@Test
public void testSetListening_ChangesValueForAddingFutureTiles() {
QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
- mTileLayout.setListening(true);
+ mTileLayout.setListening(true, null);
mTileLayout.addTile(tileRecord);
verify(tileRecord.tile, times(1)).setListening(mTileLayout, true);
}
@@ -98,7 +98,7 @@
@Test
public void testRemoveTile_CallsSetListeningFalseOnTile() {
QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
- mTileLayout.setListening(true);
+ mTileLayout.setListening(true, null);
mTileLayout.addTile(tileRecord);
mTileLayout.removeTile(tileRecord);
verify(tileRecord.tile, times(1)).setListening(mTileLayout, false);
@@ -108,7 +108,7 @@
public void testRemoveAllViews_CallsSetListeningFalseOnAllTiles() {
QSPanelControllerBase.TileRecord tileRecord1 = createTileRecord();
QSPanelControllerBase.TileRecord tileRecord2 = createTileRecord();
- mTileLayout.setListening(true);
+ mTileLayout.setListening(true, null);
mTileLayout.addTile(tileRecord1);
mTileLayout.addTile(tileRecord2);
mTileLayout.removeAllViews();
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 37e8218..c426c87 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
@@ -15,6 +15,7 @@
package com.android.systemui.statusbar.notification.row;
import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE;
+import static android.provider.Settings.Global.SHOW_NEW_NOTIF_DISMISS;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -96,6 +97,7 @@
@Test
public void testNoAppOpsInSlowSwipe() {
Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
+ Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
@@ -108,6 +110,7 @@
@Test
public void testNoSnoozeInSlowSwipe() {
Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
+ Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
@@ -120,6 +123,7 @@
@Test
public void testSnoozeInSlowSwipe() {
Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 1);
+ Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
@@ -130,6 +134,19 @@
}
@Test
+ public void testSlowSwipe_newDismiss() {
+ Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 1);
+ Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 1);
+
+ NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
+ row.createMenu(mRow, null);
+
+ ViewGroup container = (ViewGroup) row.getMenuView();
+ // Clear menu
+ assertEquals(0, container.getChildCount());
+ }
+
+ @Test
public void testIsSnappedAndOnSameSide() {
NotificationMenuRow row = Mockito.spy(
new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index e9e6b3e..4841b3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -231,6 +231,7 @@
when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
+ when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
mDisplayMetrics);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
index df1233a..4ed2746 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
@@ -16,17 +16,12 @@
package com.android.systemui.statusbar.phone;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
-import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
import android.view.MotionEvent;
import android.view.View;
@@ -36,48 +31,47 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
-public class DoubleTapHelperTest extends SysuiTestCase {
+public class NotificationTapHelperTest extends SysuiTestCase {
- private DoubleTapHelper mDoubleTapHelper;
- private int mTouchSlop;
- private int mDoubleTouchSlop;
+ private NotificationTapHelper mNotificationTapHelper;
+ private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
+ private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@Mock private View mView;
- @Mock private DoubleTapHelper.ActivationListener mActivationListener;
- @Mock private DoubleTapHelper.DoubleTapListener mDoubleTapListener;
- @Mock private DoubleTapHelper.SlideBackListener mSlideBackListener;
- @Mock private DoubleTapHelper.DoubleTapLogListener mDoubleTapLogListener;
+ @Mock private NotificationTapHelper.ActivationListener mActivationListener;
+ @Mock private NotificationTapHelper.DoubleTapListener mDoubleTapListener;
+ @Mock private NotificationTapHelper.SlideBackListener mSlideBackListener;
@Mock private Resources mResources;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- // The double tap slop has to be less than the regular slop, otherwise it has no effect.
- mDoubleTouchSlop = mTouchSlop - 1;
when(mView.getContext()).thenReturn(mContext);
when(mView.getResources()).thenReturn(mResources);
when(mResources.getDimension(R.dimen.double_tap_slop))
- .thenReturn((float) mDoubleTouchSlop);
+ .thenReturn((float) ViewConfiguration.get(mContext).getScaledTouchSlop() - 1);
- mDoubleTapHelper = new DoubleTapHelper(mView,
- mActivationListener,
- mDoubleTapListener,
- mSlideBackListener, mDoubleTapLogListener);
+ mFalsingManager.setFalseRobustTap(true); // Test double tapping most of the time.
+
+ mNotificationTapHelper = new NotificationTapHelper.Factory(mFalsingManager, mFakeExecutor)
+ .create(mActivationListener, mDoubleTapListener, mSlideBackListener);
}
@Test
public void testDoubleTap_success() {
- long downtimeA = SystemClock.uptimeMillis();
+ long downtimeA = 100;
long downtimeB = downtimeA + 100;
MotionEvent evDownA = MotionEvent.obtain(downtimeA,
@@ -105,16 +99,13 @@
1,
0);
- mDoubleTapHelper.onTouchEvent(evDownA);
- mDoubleTapHelper.onTouchEvent(evUpA);
+ mNotificationTapHelper.onTouchEvent(evDownA);
+ mNotificationTapHelper.onTouchEvent(evUpA);
verify(mActivationListener).onActiveChanged(true);
- verify(mView).postDelayed(any(Runnable.class), anyLong());
- verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
verify(mDoubleTapListener, never()).onDoubleTap();
- mDoubleTapHelper.onTouchEvent(evDownB);
- mDoubleTapHelper.onTouchEvent(evUpB);
- verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+ mNotificationTapHelper.onTouchEvent(evDownB);
+ mNotificationTapHelper.onTouchEvent(evUpB);
verify(mDoubleTapListener).onDoubleTap();
evDownA.recycle();
@@ -125,7 +116,7 @@
@Test
public void testSingleTap_timeout() {
- long downtimeA = SystemClock.uptimeMillis();
+ long downtimeA = 100;
MotionEvent evDownA = MotionEvent.obtain(downtimeA,
downtimeA,
@@ -140,21 +131,19 @@
1,
0);
- ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
- mDoubleTapHelper.onTouchEvent(evDownA);
- mDoubleTapHelper.onTouchEvent(evUpA);
+ mNotificationTapHelper.onTouchEvent(evDownA);
+ mNotificationTapHelper.onTouchEvent(evUpA);
verify(mActivationListener).onActiveChanged(true);
- verify(mView).postDelayed(runnableCaptor.capture(), anyLong());
- runnableCaptor.getValue().run();
- verify(mActivationListener).onActiveChanged(true);
+ drainExecutor();
+ verify(mActivationListener).onActiveChanged(false);
evDownA.recycle();
evUpA.recycle();
}
@Test
- public void testSingleTap_slop() {
- long downtimeA = SystemClock.uptimeMillis();
+ public void testSingleTap_falsed() {
+ long downtimeA = 100;
MotionEvent evDownA = MotionEvent.obtain(downtimeA,
downtimeA,
@@ -165,13 +154,13 @@
MotionEvent evUpA = MotionEvent.obtain(downtimeA,
downtimeA + 1,
MotionEvent.ACTION_UP,
- 1 + mTouchSlop,
+ 1,
1,
0);
- ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
- mDoubleTapHelper.onTouchEvent(evDownA);
- mDoubleTapHelper.onTouchEvent(evUpA);
+ mFalsingManager.setFalseTap(true);
+ mNotificationTapHelper.onTouchEvent(evDownA);
+ mNotificationTapHelper.onTouchEvent(evUpA);
verify(mActivationListener, never()).onActiveChanged(true);
verify(mDoubleTapListener, never()).onDoubleTap();
@@ -180,8 +169,8 @@
}
@Test
- public void testDoubleTap_slop() {
- long downtimeA = SystemClock.uptimeMillis();
+ public void testDoubleTap_falsed() {
+ long downtimeA = 100;
long downtimeB = downtimeA + 100;
MotionEvent evDownA = MotionEvent.obtain(downtimeA,
@@ -206,17 +195,17 @@
downtimeB + 1,
MotionEvent.ACTION_UP,
1,
- 1 + mDoubleTouchSlop,
+ 1,
0);
- mDoubleTapHelper.onTouchEvent(evDownA);
- mDoubleTapHelper.onTouchEvent(evUpA);
- verify(mActivationListener).onActiveChanged(true);
- verify(mView).postDelayed(any(Runnable.class), anyLong());
+ mFalsingManager.setFalseDoubleTap(true);
- mDoubleTapHelper.onTouchEvent(evDownB);
- mDoubleTapHelper.onTouchEvent(evUpB);
- verify(mDoubleTapLogListener).onDoubleTapLog(false, 0, mDoubleTouchSlop);
+ mNotificationTapHelper.onTouchEvent(evDownA);
+ mNotificationTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener).onActiveChanged(true);
+
+ mNotificationTapHelper.onTouchEvent(evDownB);
+ mNotificationTapHelper.onTouchEvent(evUpB);
verify(mActivationListener).onActiveChanged(false);
verify(mDoubleTapListener, never()).onDoubleTap();
@@ -228,8 +217,7 @@
@Test
public void testSlideBack() {
- long downtimeA = SystemClock.uptimeMillis();
- long downtimeB = downtimeA + 100;
+ long downtimeA = 100;
MotionEvent evDownA = MotionEvent.obtain(downtimeA,
downtimeA,
@@ -243,42 +231,24 @@
1,
1,
0);
- MotionEvent evDownB = MotionEvent.obtain(downtimeB,
- downtimeB,
- MotionEvent.ACTION_DOWN,
- 1,
- 1,
- 0);
- MotionEvent evUpB = MotionEvent.obtain(downtimeB,
- downtimeB + 1,
- MotionEvent.ACTION_UP,
- 1,
- 1,
- 0);
when(mSlideBackListener.onSlideBack()).thenReturn(true);
- mDoubleTapHelper.onTouchEvent(evDownA);
- mDoubleTapHelper.onTouchEvent(evUpA);
+ mNotificationTapHelper.onTouchEvent(evDownA);
+ mNotificationTapHelper.onTouchEvent(evUpA);
verify(mActivationListener, never()).onActiveChanged(true);
verify(mActivationListener, never()).onActiveChanged(false);
verify(mDoubleTapListener, never()).onDoubleTap();
- mDoubleTapHelper.onTouchEvent(evDownB);
- mDoubleTapHelper.onTouchEvent(evUpB);
- verify(mActivationListener, never()).onActiveChanged(true);
- verify(mActivationListener, never()).onActiveChanged(false);
- verify(mDoubleTapListener, never()).onDoubleTap();
+ verify(mSlideBackListener).onSlideBack();
evDownA.recycle();
evUpA.recycle();
- evDownB.recycle();
- evUpB.recycle();
}
@Test
public void testMoreThanTwoTaps() {
- long downtimeA = SystemClock.uptimeMillis();
+ long downtimeA = 100;
long downtimeB = downtimeA + 100;
long downtimeC = downtimeB + 100;
long downtimeD = downtimeC + 100;
@@ -332,33 +302,26 @@
1,
0);
- mDoubleTapHelper.onTouchEvent(evDownA);
- mDoubleTapHelper.onTouchEvent(evUpA);
+ mNotificationTapHelper.onTouchEvent(evDownA);
+ mNotificationTapHelper.onTouchEvent(evUpA);
verify(mActivationListener).onActiveChanged(true);
- verify(mView).postDelayed(any(Runnable.class), anyLong());
- verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
verify(mDoubleTapListener, never()).onDoubleTap();
- mDoubleTapHelper.onTouchEvent(evDownB);
- mDoubleTapHelper.onTouchEvent(evUpB);
- verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+ mNotificationTapHelper.onTouchEvent(evDownB);
+ mNotificationTapHelper.onTouchEvent(evUpB);
verify(mDoubleTapListener).onDoubleTap();
reset(mView);
reset(mActivationListener);
- reset(mDoubleTapLogListener);
reset(mDoubleTapListener);
- mDoubleTapHelper.onTouchEvent(evDownC);
- mDoubleTapHelper.onTouchEvent(evUpC);
+ mNotificationTapHelper.onTouchEvent(evDownC);
+ mNotificationTapHelper.onTouchEvent(evUpC);
verify(mActivationListener).onActiveChanged(true);
- verify(mView).postDelayed(any(Runnable.class), anyLong());
- verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
verify(mDoubleTapListener, never()).onDoubleTap();
- mDoubleTapHelper.onTouchEvent(evDownD);
- mDoubleTapHelper.onTouchEvent(evUpD);
- verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+ mNotificationTapHelper.onTouchEvent(evDownD);
+ mNotificationTapHelper.onTouchEvent(evUpD);
verify(mDoubleTapListener).onDoubleTap();
evDownA.recycle();
@@ -366,4 +329,9 @@
evDownB.recycle();
evUpB.recycle();
}
+
+ private void drainExecutor() {
+ mFakeExecutor.advanceClockToLast();
+ mFakeExecutor.runAllReady();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 9832d31..83030ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -33,6 +33,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
+import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
@@ -103,6 +104,7 @@
when(mLockIconContainer.getParent()).thenReturn(mock(ViewGroup.class));
when(mLockIconContainer.animate()).thenReturn(mock(ViewPropertyAnimator.class,
RETURNS_DEEP_STUBS));
+ when(mLockIconContainer.getLayoutParams()).thenReturn(mock(FrameLayout.LayoutParams.class));
when(mKeyguardBouncerFactory.create(
any(ViewGroup.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
index ecfb357..d8271e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
@@ -28,17 +28,16 @@
* backwards. uptimeMillis, elapsedRealtime, and currentThreadTimeMillis are all kept in sync.
*
* Unless otherwise specified, uptimeMillis and elapsedRealtime will advance the same amount with
- * every call to {@link #advanceTime(long)}. Thread time always lags by 50% of the uptime
+ * every call to {@link #advanceTime}. Thread time always lags by 50% of the uptime
* advancement to simulate time loss due to scheduling.
*/
public class FakeSystemClock implements SystemClock {
private long mUptimeMillis = 10000;
private long mElapsedRealtime = 10000;
private long mCurrentThreadTimeMillis = 10000;
-
private long mCurrentTimeMillis = 1555555500000L;
-
private final List<ClockTickListener> mListeners = new ArrayList<>();
+
@Override
public long uptimeMillis() {
return mUptimeMillis;
@@ -72,21 +71,38 @@
mCurrentTimeMillis = millis;
}
- public void advanceTime(long uptime) {
- advanceTime(uptime, 0);
+ /**
+ * Advances the time tracked by the fake clock and notifies any listeners that the time has
+ * changed (for example, an attached {@link FakeExecutor} may fire its pending runnables).
+ *
+ * All tracked times increment by [millis], with the exception of currentThreadTimeMillis,
+ * which advances by [millis] * 0.5
+ */
+ public void advanceTime(long millis) {
+ advanceTime(millis, 0);
}
- public void advanceTime(long uptime, long sleepTime) {
- if (uptime < 0 || sleepTime < 0) {
+ /**
+ * Advances the time tracked by the fake clock and notifies any listeners that the time has
+ * changed (for example, an attached {@link FakeExecutor} may fire its pending runnables).
+ *
+ * The tracked times change as follows:
+ * - uptimeMillis increments by [awakeMillis]
+ * - currentThreadTimeMillis increments by [awakeMillis] * 0.5
+ * - elapsedRealtime increments by [awakeMillis] + [sleepMillis]
+ * - currentTimeMillis increments by [awakeMillis] + [sleepMillis]
+ */
+ public void advanceTime(long awakeMillis, long sleepMillis) {
+ if (awakeMillis < 0 || sleepMillis < 0) {
throw new IllegalArgumentException("Time cannot go backwards");
}
- if (uptime > 0 || sleepTime > 0) {
- mUptimeMillis += uptime;
- mElapsedRealtime += uptime + sleepTime;
- mCurrentTimeMillis += uptime + sleepTime;
+ if (awakeMillis > 0 || sleepMillis > 0) {
+ mUptimeMillis += awakeMillis;
+ mElapsedRealtime += awakeMillis + sleepMillis;
+ mCurrentTimeMillis += awakeMillis + sleepMillis;
- mCurrentThreadTimeMillis += Math.ceil(uptime * 0.5);
+ mCurrentThreadTimeMillis += Math.ceil(awakeMillis * 0.5);
for (ClockTickListener listener : mListeners) {
listener.onClockTick();
@@ -105,6 +121,4 @@
public interface ClockTickListener {
void onClockTick();
}
-
- private static final long START_TIME = 10000;
}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 15bd4dc..ae024ff 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -332,5 +332,9 @@
// Notify the user that the admin suspended personal apps on the device.
// Package: android
NOTE_PERSONAL_APPS_SUSPENDED = 1003;
+
+ // Notify the user that window magnification is available.
+ // package: android
+ NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE = 1004;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 857ac6a..be7643e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -37,6 +37,7 @@
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
+import com.android.server.accessibility.magnification.WindowMagnificationPromptController;
import com.android.server.policy.WindowManagerPolicy;
import java.util.ArrayList;
@@ -561,8 +562,9 @@
detectControlGestures, triggerable, displayId);
} else {
magnificationGestureHandler = new FullScreenMagnificationGestureHandler(displayContext,
- mAms.getFullScreenMagnificationController(), mAms::onMagnificationScaleChanged,
- detectControlGestures, triggerable, displayId);
+ mAms.getFullScreenMagnificationController(),
+ mAms::onMagnificationScaleChanged, detectControlGestures, triggerable,
+ new WindowMagnificationPromptController(displayContext, mUserId), displayId);
}
return magnificationGestureHandler;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index d50e9d7..efb9d87 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -137,6 +137,7 @@
@VisibleForTesting final ViewportDraggingState mViewportDraggingState;
private final ScreenStateReceiver mScreenStateReceiver;
+ private final WindowMagnificationPromptController mPromptController;
/**
* {@code true} if this detector should detect and respond to triple-tap
@@ -178,6 +179,7 @@
ScaleChangedListener listener,
boolean detectTripleTap,
boolean detectShortcutTrigger,
+ @NonNull WindowMagnificationPromptController promptController,
int displayId) {
super(listener);
if (DEBUG_ALL) {
@@ -186,6 +188,7 @@
+ ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
}
mFullScreenMagnificationController = fullScreenMagnificationController;
+ mPromptController = promptController;
mDisplayId = displayId;
mDelegatingState = new DelegatingState();
@@ -195,7 +198,6 @@
mDetectTripleTap = detectTripleTap;
mDetectShortcutTrigger = detectShortcutTrigger;
-
if (mDetectShortcutTrigger) {
mScreenStateReceiver = new ScreenStateReceiver(context, this);
mScreenStateReceiver.register();
@@ -264,6 +266,7 @@
if (mScreenStateReceiver != null) {
mScreenStateReceiver.unregister();
}
+ mPromptController.onDestroy();
// Check if need to reset when MagnificationGestureHandler is the last magnifying service.
mFullScreenMagnificationController.resetAllIfNeeded(
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -278,6 +281,7 @@
if (wasMagnifying) {
clearAndTransitionToStateDetecting();
} else {
+ mPromptController.showNotificationIfNeeded();
mDetectingState.toggleShortcutTriggered();
}
}
@@ -950,6 +954,7 @@
if (mFullScreenMagnificationController.isMagnifying(mDisplayId)) {
zoomOff();
} else {
+ mPromptController.showNotificationIfNeeded();
zoomOn(up.getX(), up.getY());
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java
new file mode 100644
index 0000000..7212207
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.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 com.android.server.accessibility.magnification;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
+import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE;
+
+import android.Manifest;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.app.ActivityOptions;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.notification.SystemNotificationChannels;
+
+/**
+ * A class to show notification to prompt the user that this feature is available.
+ */
+public class WindowMagnificationPromptController {
+
+ private static final Uri MAGNIFICATION_WINDOW_MODE_PROMPT_URI = Settings.Secure.getUriFor(
+ ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT);
+ @VisibleForTesting
+ static final String ACTION_DISMISS =
+ "com.android.server.accessibility.magnification.action.DISMISS";
+ @VisibleForTesting
+ static final String ACTION_TURN_ON_IN_SETTINGS =
+ "com.android.server.accessibility.magnification.action.TURN_ON_IN_SETTINGS";
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+ private final ContentObserver mContentObserver;
+ private final int mUserId;
+ @VisibleForTesting
+ BroadcastReceiver mNotificationActionReceiver;
+
+ private boolean mNeedToShowNotification;
+
+ @MainThread
+ public WindowMagnificationPromptController(@NonNull Context context, int userId) {
+ mContext = context;
+ mNotificationManager = context.getSystemService(NotificationManager.class);
+ mUserId = userId;
+ mContentObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ onPromptSettingsValueChanged();
+ }
+ };
+ context.getContentResolver().registerContentObserver(MAGNIFICATION_WINDOW_MODE_PROMPT_URI,
+ false, mContentObserver, mUserId);
+ mNeedToShowNotification = isWindowMagnificationPromptEnabled();
+ }
+
+ @VisibleForTesting
+ protected void onPromptSettingsValueChanged() {
+ final boolean needToShowNotification = isWindowMagnificationPromptEnabled();
+ if (mNeedToShowNotification == needToShowNotification) {
+ return;
+ }
+ mNeedToShowNotification = needToShowNotification;
+ if (!mNeedToShowNotification) {
+ unregisterReceiverIfNeeded();
+ mNotificationManager.cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+ }
+ }
+
+ /**
+ * Shows the prompt notification that could bring users to magnification settings if necessary.
+ */
+ @MainThread
+ void showNotificationIfNeeded() {
+ if (!mNeedToShowNotification) return;
+
+ final Notification.Builder notificationBuilder = new Notification.Builder(mContext,
+ SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION);
+ notificationBuilder.setSmallIcon(R.drawable.ic_settings_24dp)
+ .setContentTitle(mContext.getString(R.string.window_magnification_prompt_title))
+ .setContentText(mContext.getString(R.string.window_magnification_prompt_content))
+ .setLargeIcon(Icon.createWithResource(mContext,
+ R.drawable.ic_accessibility_magnification))
+ .setTicker(mContext.getString(R.string.window_magnification_prompt_title))
+ .setOnlyAlertOnce(true)
+ .setDeleteIntent(createPendingIntent(ACTION_DISMISS))
+ .setContentIntent(createPendingIntent(ACTION_TURN_ON_IN_SETTINGS))
+ .setActions(buildTurnOnAction(), buildDismissAction());
+ mNotificationManager.notify(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE,
+ notificationBuilder.build());
+ registerReceiverIfNeeded();
+ }
+
+ /**
+ * Called when this object is not used anymore to release resources if necessary.
+ */
+ @VisibleForTesting
+ @MainThread
+ public void onDestroy() {
+ dismissNotification();
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ }
+
+ private boolean isWindowMagnificationPromptEnabled() {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, 0, mUserId) == 1;
+ }
+
+ private Notification.Action buildTurnOnAction() {
+ return new Notification.Action.Builder(null,
+ mContext.getString(R.string.turn_on_magnification_settings_action),
+ createPendingIntent(ACTION_TURN_ON_IN_SETTINGS)).build();
+ }
+
+ private Notification.Action buildDismissAction() {
+ return new Notification.Action.Builder(null, mContext.getString(R.string.dismiss_action),
+ createPendingIntent(ACTION_DISMISS)).build();
+ }
+
+ private PendingIntent createPendingIntent(String action) {
+ final Intent intent = new Intent(action);
+ intent.setPackage(mContext.getPackageName());
+ return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+ }
+
+ private void registerReceiverIfNeeded() {
+ if (mNotificationActionReceiver != null) {
+ return;
+ }
+ mNotificationActionReceiver = new NotificationActionReceiver();
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_DISMISS);
+ intentFilter.addAction(ACTION_TURN_ON_IN_SETTINGS);
+ mContext.registerReceiver(mNotificationActionReceiver, intentFilter,
+ Manifest.permission.MANAGE_ACCESSIBILITY, null);
+ }
+
+ private void launchMagnificationSettings() {
+ final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.putExtra(Intent.EXTRA_COMPONENT_NAME,
+ MAGNIFICATION_COMPONENT_NAME.flattenToShortString());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(
+ mContext.getDisplayId()).toBundle();
+ mContext.startActivityAsUser(intent, bundle, UserHandle.of(mUserId));
+ mContext.getSystemService(StatusBarManager.class).collapsePanels();
+ }
+
+ private void dismissNotification() {
+ unregisterReceiverIfNeeded();
+ mNotificationManager.cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+ }
+
+ private void unregisterReceiverIfNeeded() {
+ if (mNotificationActionReceiver == null) {
+ return;
+ }
+ mContext.unregisterReceiver(mNotificationActionReceiver);
+ mNotificationActionReceiver = null;
+ }
+
+ private class NotificationActionReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (TextUtils.isEmpty(action)) return;
+
+ mNeedToShowNotification = false;
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, 0, mUserId);
+
+ if (ACTION_TURN_ON_IN_SETTINGS.equals(action)) {
+ launchMagnificationSettings();
+ dismissNotification();
+ } else if (ACTION_DISMISS.equals(action)) {
+ dismissNotification();
+ }
+ }
+ }
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 4bde31f..069a5ea 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -61,6 +61,7 @@
java_library_static {
name: "services.core.unboosted",
+ defaults: ["platform_service_defaults"],
srcs: [
":services.core.protologsrc",
":dumpstate_aidl",
@@ -74,6 +75,7 @@
":platform-compat-config",
":display-device-config",
":cec-config",
+ ":device-state-config",
"java/com/android/server/EventLogTags.logtags",
"java/com/android/server/am/EventLogTags.logtags",
"java/com/android/server/wm/EventLogTags.logtags",
@@ -146,7 +148,6 @@
java_library {
name: "services.core",
- defaults: ["platform_service_defaults"],
static_libs: ["services.core.priorityboosted"],
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 9455051..bb4bbd5 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -437,6 +437,10 @@
info.legacy.legacy.batteryChargeCounter);
Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryCurrent",
info.legacy.legacy.batteryCurrent);
+ Trace.traceCounter(Trace.TRACE_TAG_POWER, "PlugType",
+ plugType(info.legacy.legacy));
+ Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryStatus",
+ info.legacy.legacy.batteryStatus);
synchronized (mLock) {
if (!mUpdatesStopped) {
@@ -471,6 +475,18 @@
dst.batteryTechnology = src.batteryTechnology;
}
+ private static int plugType(HealthInfo healthInfo) {
+ if (healthInfo.chargerAcOnline) {
+ return BatteryManager.BATTERY_PLUGGED_AC;
+ } else if (healthInfo.chargerUsbOnline) {
+ return BatteryManager.BATTERY_PLUGGED_USB;
+ } else if (healthInfo.chargerWirelessOnline) {
+ return BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ } else {
+ return BATTERY_PLUGGED_NONE;
+ }
+ }
+
private void processValuesLocked(boolean force) {
boolean logOutlier = false;
long dischargeDuration = 0;
@@ -478,15 +494,7 @@
mBatteryLevelCritical =
mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
&& mHealthInfo.batteryLevel <= mCriticalBatteryLevel;
- if (mHealthInfo.chargerAcOnline) {
- mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
- } else if (mHealthInfo.chargerUsbOnline) {
- mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
- } else if (mHealthInfo.chargerWirelessOnline) {
- mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
- } else {
- mPlugType = BATTERY_PLUGGED_NONE;
- }
+ mPlugType = plugType(mHealthInfo);
if (DEBUG) {
Slog.d(TAG, "Processing new values: "
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d586f00..ff0434f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -137,7 +137,6 @@
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
-import android.os.BasicShellCommandHandler;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -190,6 +189,7 @@
import com.android.internal.util.LocationPermissionChecker;
import com.android.internal.util.MessageUtils;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.BasicShellCommandHandler;
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import com.android.server.am.BatteryStatsService;
@@ -5854,7 +5854,6 @@
@GuardedBy("mBlockedAppUids")
private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
- // Note: if mDefaultRequest is changed, NetworkMonitor needs to be updated.
@NonNull
private final NetworkRequest mDefaultRequest;
// The NetworkAgentInfo currently satisfying the default request, if any.
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index d4e912b..8ed23f9 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -44,6 +44,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -145,16 +148,44 @@
private long mLastPowerDown;
private int mPowerButtonConsecutiveTaps;
private int mPowerButtonSlowConsecutiveTaps;
+ private final UiEventLogger mUiEventLogger;
+ @VisibleForTesting
+ public enum GestureLauncherEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The user lifted the device just the right way to launch the camera.")
+ GESTURE_CAMERA_LIFT(658),
+
+ @UiEvent(doc = "The user wiggled the device just the right way to launch the camera.")
+ GESTURE_CAMERA_WIGGLE(659),
+
+ @UiEvent(doc = "The user double-tapped power quickly enough to launch the camera.")
+ GESTURE_CAMERA_DOUBLE_TAP_POWER(660),
+
+ @UiEvent(doc = "The user multi-tapped power quickly enough to signal an emergency.")
+ GESTURE_PANIC_TAP_POWER(661);
+
+ private final int mId;
+
+ GestureLauncherEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
public GestureLauncherService(Context context) {
- this(context, new MetricsLogger());
+ this(context, new MetricsLogger(), new UiEventLoggerImpl());
}
@VisibleForTesting
- GestureLauncherService(Context context, MetricsLogger metricsLogger) {
+ GestureLauncherService(Context context, MetricsLogger metricsLogger,
+ UiEventLogger uiEventLogger) {
super(context);
mContext = context;
mMetricsLogger = metricsLogger;
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -460,11 +491,12 @@
if (launchCamera) {
mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
(int) powerTapInterval);
+ mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
}
} else if (launchEmergencyGesture) {
Slog.i(TAG, "Emergency gesture detected, launching.");
launchEmergencyGesture = handleEmergencyGesture();
- // TODO(b/160006048): Add logging
+ mUiEventLogger.log(GestureLauncherEvent.GESTURE_PANIC_TAP_POWER);
}
mMetricsLogger.histogram("power_consecutive_short_tap_count",
mPowerButtonSlowConsecutiveTaps);
@@ -587,6 +619,7 @@
if (handleCameraGesture(true /* useWakelock */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)) {
mMetricsLogger.action(MetricsEvent.ACTION_WIGGLE_CAMERA_GESTURE);
+ mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_WIGGLE);
trackCameraLaunchEvent(event);
}
return;
@@ -671,6 +704,7 @@
if (handleCameraGesture(true /* useWakelock */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_CAMERA_LIFT_TRIGGER);
+ mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_LIFT);
}
} else {
if (DBG_CAMERA_LIFT) Slog.d(TAG, "Ignoring lift event");
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 5556e48..9d60b84 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -21,6 +21,7 @@
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.os.Build;
import android.os.Environment;
import android.os.SystemClock;
import android.os.Trace;
@@ -31,6 +32,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.ClassLoaderFactory;
import com.android.internal.util.Preconditions;
import com.android.server.SystemService.TargetUser;
import com.android.server.am.EventLogTags;
@@ -121,7 +123,10 @@
if (pathClassLoader == null) {
// NB: the parent class loader should always be the system server class loader.
// Changing it has implications that require discussion with the mainline team.
- pathClassLoader = new PathClassLoader(path, this.getClass().getClassLoader());
+ pathClassLoader = (PathClassLoader) ClassLoaderFactory.createClassLoader(
+ path, null /* librarySearchPath */, null /* libraryPermittedPath */,
+ this.getClass().getClassLoader(), Build.VERSION.SDK_INT,
+ true /* isNamespaceShared */, null /* classLoaderName */);
mLoadedPaths.put(path, pathClassLoader);
}
final Class<SystemService> serviceClass = loadClassFromLoader(className, pathClassLoader);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index ba52aee..eb55512 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -80,7 +80,6 @@
import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Pair;
@@ -102,13 +101,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
/**
* Since phone process can be restarted, this class provides a centralized place
@@ -148,14 +144,14 @@
int callerUid;
int callerPid;
- Set<Integer> eventList;
+ long events;
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
int phoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
- boolean matchPhoneStateListenerEvent(int event) {
- return (callback != null) && (this.eventList.contains(event));
+ boolean matchPhoneStateListenerEvent(long events) {
+ return (callback != null) && ((events & this.events) != 0);
}
boolean matchOnSubscriptionsChangedListener() {
@@ -183,7 +179,7 @@
+ onSubscriptionsChangedListenerCallback
+ " onOpportunisticSubscriptionsChangedListenererCallback="
+ onOpportunisticSubscriptionsChangedListenerCallback + " subId=" + subId
- + " phoneId=" + phoneId + " events=" + eventList + "}";
+ + " phoneId=" + phoneId + " events=" + Long.toHexString(events) + "}";
}
}
@@ -314,10 +310,6 @@
private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
- private boolean mIsDataEnabled = false;
-
- private int mDataEnabledReason;
-
/**
* Per-phone map of precise data connection state. The key of the map is the pair of transport
* type and APN setting. This is the cache to prevent redundant callbacks to the listeners.
@@ -327,62 +319,40 @@
private List<Map<Pair<Integer, ApnSetting>, PreciseDataConnectionState>>
mPreciseDataConnectionStates;
- private static final Set<Integer> REQUIRE_PRECISE_PHONE_STATE_PERMISSION;
- static {
- REQUIRE_PRECISE_PHONE_STATE_PERMISSION = new HashSet<Integer>();
- REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
- PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED);
- REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
- PhoneStateListener.EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED);
- REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
- PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED);
- REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
- PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED);
- REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
- PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED);
- REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
- PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED);
- REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(PhoneStateListener.EVENT_REGISTRATION_FAILURE);
- REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(PhoneStateListener.EVENT_BARRING_INFO_CHANGED);
- REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
- PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
- REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
- PhoneStateListener.EVENT_DATA_ENABLED_CHANGED);
- }
+ // Starting in Q, almost all cellular location requires FINE location enforcement.
+ // Prior to Q, cellular was available with COARSE location enforcement. Bits in this
+ // list will be checked for COARSE on apps targeting P or earlier and FINE on Q or later.
+ static final long ENFORCE_LOCATION_PERMISSION_MASK =
+ PhoneStateListener.LISTEN_CELL_LOCATION
+ | PhoneStateListener.LISTEN_CELL_INFO
+ | PhoneStateListener.LISTEN_REGISTRATION_FAILURE
+ | PhoneStateListener.LISTEN_BARRING_INFO;
- private boolean isLocationPermissionRequired(Set<Integer> events) {
- return events.contains(PhoneStateListener.EVENT_CELL_LOCATION_CHANGED)
- || events.contains(PhoneStateListener.EVENT_CELL_INFO_CHANGED)
- || events.contains(PhoneStateListener.EVENT_REGISTRATION_FAILURE)
- || events.contains(PhoneStateListener.EVENT_BARRING_INFO_CHANGED);
- }
+ static final long ENFORCE_PHONE_STATE_PERMISSION_MASK =
+ PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
+ | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
+ | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST
+ | PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED;
- private boolean isPhoneStatePermissionRequired(Set<Integer> events) {
- return events.contains(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)
- || events.contains(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)
- || events.contains(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED)
- || events.contains(PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED);
- }
+ static final long ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK =
+ PhoneStateListener.LISTEN_PRECISE_CALL_STATE
+ | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE
+ | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES
+ | PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED
+ | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES
+ | PhoneStateListener.LISTEN_REGISTRATION_FAILURE
+ | PhoneStateListener.LISTEN_BARRING_INFO
+ | PhoneStateListener.LISTEN_PHYSICAL_CHANNEL_CONFIGURATION;
- private boolean isPrecisePhoneStatePermissionRequired(Set<Integer> events) {
- for (Integer requireEvent : REQUIRE_PRECISE_PHONE_STATE_PERMISSION) {
- if (events.contains(requireEvent)) {
- return true;
- }
- }
- return false;
- }
+ static final long READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK =
+ PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL
+ | PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS;
- private boolean isActiveEmergencySessionPermissionRequired(Set<Integer> events) {
- return events.contains(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL)
- || events.contains(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS);
- }
-
- private boolean isPrivilegedPhoneStatePermissionRequired(Set<Integer> events) {
- return events.contains(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED)
- || events.contains(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED)
- || events.contains(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED);
- }
+ static final long READ_PRIVILEGED_PHONE_STATE_PERMISSION_MASK =
+ PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT
+ | PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED
+ | PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED
+ | PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE;
private static final int MSG_USER_SWITCHED = 1;
private static final int MSG_UPDATE_DEFAULT_SUB = 2;
@@ -700,7 +670,7 @@
r.callingFeatureId = callingFeatureId;
r.callerUid = Binder.getCallingUid();
r.callerPid = Binder.getCallingPid();
- r.eventList = new ArraySet<>();
+ r.events = 0;
if (DBG) {
log("listen oscl: Register r=" + r);
}
@@ -754,7 +724,7 @@
r.callingFeatureId = callingFeatureId;
r.callerUid = Binder.getCallingUid();
r.callerPid = Binder.getCallingPid();
- r.eventList = new ArraySet<>();
+ r.events = 0;
if (DBG) {
log("listen ooscl: Register r=" + r);
}
@@ -827,338 +797,331 @@
}
}
+ @Deprecated
@Override
- public void listenWithEventList(int subId, String callingPackage, String callingFeatureId,
- IPhoneStateListener callback, int[] events, boolean notifyNow) {
- Set<Integer> eventList = Arrays.stream(events).boxed().collect(Collectors.toSet());
- listen(callingPackage, callingFeatureId, callback, eventList, notifyNow, subId);
+ public void listen(String callingPackage, IPhoneStateListener callback, int events,
+ boolean notifyNow) {
+ listenWithFeature(callingPackage, null, callback, events, notifyNow);
+ }
+
+ @Override
+ public void listenWithFeature(String callingPackage, String callingFeatureId,
+ IPhoneStateListener callback, long events, boolean notifyNow) {
+ listenForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, callingPackage,
+ callingFeatureId, callback, events, notifyNow);
+ }
+
+ @Override
+ public void listenForSubscriber(int subId, String callingPackage, String callingFeatureId,
+ IPhoneStateListener callback, long events, boolean notifyNow) {
+ listen(callingPackage, callingFeatureId, callback, events, notifyNow, subId);
}
private void listen(String callingPackage, @Nullable String callingFeatureId,
- IPhoneStateListener callback, Set<Integer> events, boolean notifyNow, int subId) {
+ IPhoneStateListener callback, long events, boolean notifyNow, int subId) {
int callerUserId = UserHandle.getCallingUserId();
-
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
String str = "listen: E pkg=" + pii(callingPackage) + " uid=" + Binder.getCallingUid()
- + " events=" + events + " notifyNow=" + notifyNow
- + " subId=" + subId + " myUserId=" + UserHandle.myUserId()
- + " callerUserId=" + callerUserId;
+ + " events=0x" + Long.toHexString(events) + " notifyNow=" + notifyNow + " subId="
+ + subId + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId;
mListenLog.log(str);
if (VDBG) {
log(str);
}
- if (events.isEmpty()) {
- if (DBG) {
- log("listen: Unregister");
- }
- events.clear();
- remove(callback.asBinder());
- return;
- }
-
- // Checks permission and throws SecurityException for disallowed operations. For pre-M
- // apps whose runtime permission has been revoked, we return immediately to skip sending
- // events to the app without crashing it.
- if (!checkListenerPermission(events, subId, callingPackage, callingFeatureId,
- "listen")) {
- return;
- }
-
- int phoneId = getPhoneIdFromSubId(subId);
- synchronized (mRecords) {
- // register
- IBinder b = callback.asBinder();
- boolean doesLimitApply =
- Binder.getCallingUid() != Process.SYSTEM_UID
- && Binder.getCallingUid() != Process.PHONE_UID
- && Binder.getCallingUid() != Process.myUid();
- Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply);
-
- if (r == null) {
+ if (events != PhoneStateListener.LISTEN_NONE) {
+ // Checks permission and throws SecurityException for disallowed operations. For pre-M
+ // apps whose runtime permission has been revoked, we return immediately to skip sending
+ // events to the app without crashing it.
+ if (!checkListenerPermission(events, subId, callingPackage, callingFeatureId,
+ "listen")) {
return;
}
- r.context = mContext;
- r.callback = callback;
- r.callingPackage = callingPackage;
- r.callingFeatureId = callingFeatureId;
- r.callerUid = Binder.getCallingUid();
- r.callerPid = Binder.getCallingPid();
- // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
- // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- } else {//APP specify subID
- r.subId = subId;
- }
- r.phoneId = phoneId;
- r.eventList = events;
- if (DBG) {
- log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
- }
- if (notifyNow && validatePhoneId(phoneId)) {
- if (events.contains(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED)) {
- try {
- if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]);
- ServiceState rawSs = new ServiceState(mServiceState[phoneId]);
- if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
- r.callback.onServiceStateChanged(rawSs);
- } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) {
- r.callback.onServiceStateChanged(
- rawSs.createLocationInfoSanitizedCopy(false));
- } else {
- r.callback.onServiceStateChanged(
- rawSs.createLocationInfoSanitizedCopy(true));
+ int phoneId = getPhoneIdFromSubId(subId);
+ synchronized (mRecords) {
+ // register
+ IBinder b = callback.asBinder();
+ boolean doesLimitApply =
+ Binder.getCallingUid() != Process.SYSTEM_UID
+ && Binder.getCallingUid() != Process.PHONE_UID
+ && Binder.getCallingUid() != Process.myUid();
+ Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply);
+
+ if (r == null) {
+ return;
+ }
+
+ r.context = mContext;
+ r.callback = callback;
+ r.callingPackage = callingPackage;
+ r.callingFeatureId = callingFeatureId;
+ r.callerUid = Binder.getCallingUid();
+ r.callerPid = Binder.getCallingPid();
+ // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+ // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ } else {//APP specify subID
+ r.subId = subId;
+ }
+ r.phoneId = phoneId;
+ r.events = events;
+ if (DBG) {
+ log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
+ }
+ if (notifyNow && validatePhoneId(phoneId)) {
+ if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
+ try {
+ if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]);
+ ServiceState rawSs = new ServiceState(mServiceState[phoneId]);
+ if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+ r.callback.onServiceStateChanged(rawSs);
+ } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) {
+ r.callback.onServiceStateChanged(
+ rawSs.createLocationInfoSanitizedCopy(false));
+ } else {
+ r.callback.onServiceStateChanged(
+ rawSs.createLocationInfoSanitizedCopy(true));
+ }
+ } catch (RemoteException ex) {
+ remove(r.binder);
}
- } catch (RemoteException ex) {
- remove(r.binder);
}
- }
- if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED)) {
- try {
- if (mSignalStrength[phoneId] != null) {
- int gsmSignalStrength = mSignalStrength[phoneId]
- .getGsmSignalStrength();
- r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
- : gsmSignalStrength));
+ if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
+ try {
+ if (mSignalStrength[phoneId] != null) {
+ int gsmSignalStrength = mSignalStrength[phoneId]
+ .getGsmSignalStrength();
+ r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
+ : gsmSignalStrength));
+ }
+ } catch (RemoteException ex) {
+ remove(r.binder);
}
- } catch (RemoteException ex) {
- remove(r.binder);
}
- }
- if (events.contains(
- PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)) {
- try {
- r.callback.onMessageWaitingIndicatorChanged(
- mMessageWaiting[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- if (events.contains(
- PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)) {
- try {
- r.callback.onCallForwardingIndicatorChanged(
- mCallForwarding[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- if (validateEventAndUserLocked(
- r, PhoneStateListener.EVENT_CELL_LOCATION_CHANGED)) {
- try {
- if (DBG_LOC) log("listen: mCellIdentity = " + mCellIdentity[phoneId]);
- if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
- && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
- // null will be translated to empty CellLocation object in client.
- r.callback.onCellLocationChanged(mCellIdentity[phoneId]);
+ if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
+ try {
+ r.callback.onMessageWaitingIndicatorChanged(
+ mMessageWaiting[phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
}
- } catch (RemoteException ex) {
- remove(r.binder);
}
- }
- if (events.contains(PhoneStateListener.EVENT_CALL_STATE_CHANGED)) {
- try {
- r.callback.onCallStateChanged(mCallState[phoneId],
- getCallIncomingNumber(r, phoneId));
- } catch (RemoteException ex) {
- remove(r.binder);
+ if ((events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
+ try {
+ r.callback.onCallForwardingIndicatorChanged(
+ mCallForwarding[phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
- }
- if (events.contains(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED)) {
- try {
- r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
+ if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) {
+ try {
+ if (DBG_LOC) log("listen: mCellIdentity = " + mCellIdentity[phoneId]);
+ if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+ && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+ // null will be translated to empty CellLocation object in client.
+ r.callback.onCellLocationChanged(mCellIdentity[phoneId]);
+ }
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ if ((events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
+ try {
+ r.callback.onCallStateChanged(mCallState[phoneId],
+ getCallIncomingNumber(r, phoneId));
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ if ((events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
+ try {
+ r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
mDataConnectionNetworkType[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- if (events.contains(PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED)) {
- try {
- r.callback.onDataActivity(mDataActivity[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED)) {
- try {
- if (mSignalStrength[phoneId] != null) {
- r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
}
- } catch (RemoteException ex) {
- remove(r.binder);
}
- }
- if (events.contains(
- PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) {
- updateReportSignalStrengthDecision(r.subId);
- try {
- if (mSignalStrength[phoneId] != null) {
- r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+ if ((events & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) {
+ try {
+ r.callback.onDataActivity(mDataActivity[phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
}
- } catch (RemoteException ex) {
- remove(r.binder);
}
- }
- if (validateEventAndUserLocked(
- r, PhoneStateListener.EVENT_CELL_INFO_CHANGED)) {
- try {
- if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
- + mCellInfo.get(phoneId));
- if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
- && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
- r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
+ if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
+ try {
+ if (mSignalStrength[phoneId] != null) {
+ r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+ }
+ } catch (RemoteException ex) {
+ remove(r.binder);
}
- } catch (RemoteException ex) {
- remove(r.binder);
}
- }
- if (events.contains(PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED)) {
- try {
- r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- if (events.contains(PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED)) {
- try {
- r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId],
- mCallPreciseDisconnectCause[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- if (events.contains(PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED)) {
- try {
- r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(phoneId));
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- if (events.contains(
- PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED)) {
- try {
- for (PreciseDataConnectionState pdcs
- : mPreciseDataConnectionStates.get(phoneId).values()) {
- r.callback.onPreciseDataConnectionStateChanged(pdcs);
+ if ((events & PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
+ != 0) {
+ updateReportSignalStrengthDecision(r.subId);
+ try {
+ if (mSignalStrength[phoneId] != null) {
+ r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+ }
+ } catch (RemoteException ex) {
+ remove(r.binder);
}
- } catch (RemoteException ex) {
- remove(r.binder);
}
- }
- if (events.contains(PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED)) {
- try {
- r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- if (events.contains(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED)) {
- try {
- r.callback.onVoiceActivationStateChanged(
- mVoiceActivationState[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- if (events.contains(PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED)) {
- try {
- r.callback.onDataActivationStateChanged(mDataActivationState[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- if (events.contains(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED)) {
- try {
- r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- if (events.contains(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED)) {
- try {
- if (mTelephonyDisplayInfos[phoneId] != null) {
- r.callback.onDisplayInfoChanged(mTelephonyDisplayInfos[phoneId]);
+ if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
+ try {
+ if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
+ + mCellInfo.get(phoneId));
+ if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+ && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+ r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
+ }
+ } catch (RemoteException ex) {
+ remove(r.binder);
}
- } catch (RemoteException ex) {
- remove(r.binder);
}
- }
- if (events.contains(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED)) {
- try {
- r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
- } catch (RemoteException ex) {
- remove(r.binder);
+ if ((events & PhoneStateListener.LISTEN_PRECISE_CALL_STATE) != 0) {
+ try {
+ r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
- }
- if (events.contains(PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED)) {
- try {
- r.callback.onPhoneCapabilityChanged(mPhoneCapability);
- } catch (RemoteException ex) {
- remove(r.binder);
+ if ((events & PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES) != 0) {
+ try {
+ r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId],
+ mCallPreciseDisconnectCause[phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
- }
- if (events.contains(
- PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED)) {
- try {
- r.callback.onActiveDataSubIdChanged(mActiveDataSubId);
- } catch (RemoteException ex) {
- remove(r.binder);
+ if ((events & PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) != 0) {
+ try {
+ r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(phoneId));
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
- }
- if (events.contains(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED)) {
- try {
- r.callback.onRadioPowerStateChanged(mRadioPowerState);
- } catch (RemoteException ex) {
- remove(r.binder);
+ if ((events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) {
+ try {
+ for (PreciseDataConnectionState pdcs
+ : mPreciseDataConnectionStates.get(phoneId).values()) {
+ r.callback.onPreciseDataConnectionStateChanged(pdcs);
+ }
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
- }
- if (events.contains(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED)) {
- try {
- r.callback.onSrvccStateChanged(mSrvccState[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
+ if ((events & PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) != 0) {
+ try {
+ r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
- }
- if (events.contains(PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED)) {
- try {
- r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
+ if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) !=0) {
+ try {
+ r.callback.onVoiceActivationStateChanged(
+ mVoiceActivationState[phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
- }
- if (events.contains(PhoneStateListener.EVENT_BARRING_INFO_CHANGED)) {
- 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.BASE)
- ? barringInfo : biNoLocation);
- } catch (RemoteException ex) {
- remove(r.binder);
+ if ((events & PhoneStateListener.LISTEN_DATA_ACTIVATION_STATE) !=0) {
+ try {
+ r.callback.onDataActivationStateChanged(mDataActivationState[phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
- }
- if (events.contains(
- PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED)) {
- try {
- r.callback.onPhysicalChannelConfigChanged(
- mPhysicalChannelConfigs);
- } catch (RemoteException ex) {
- remove(r.binder);
+ if ((events & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) {
+ try {
+ r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
- }
- if (events.contains(
- PhoneStateListener.EVENT_DATA_ENABLED_CHANGED)) {
- try {
- r.callback.onDataEnabledChanged(mIsDataEnabled, mDataEnabledReason);
- } catch (RemoteException ex) {
- remove(r.binder);
+ if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
+ try {
+ if (mTelephonyDisplayInfos[phoneId] != null) {
+ r.callback.onDisplayInfoChanged(mTelephonyDisplayInfos[phoneId]);
+ }
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ if ((events & PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST) != 0) {
+ try {
+ r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ if ((events & PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE) != 0) {
+ try {
+ r.callback.onPhoneCapabilityChanged(mPhoneCapability);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ if ((events & PhoneStateListener
+ .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0) {
+ try {
+ r.callback.onActiveDataSubIdChanged(mActiveDataSubId);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ if ((events & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) {
+ try {
+ r.callback.onRadioPowerStateChanged(mRadioPowerState);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ if ((events & PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED) != 0) {
+ try {
+ r.callback.onSrvccStateChanged(mSrvccState[phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ if ((events & PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED) != 0) {
+ try {
+ r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+ } catch (RemoteException ex) {
+ 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.BASE)
+ ? barringInfo : biNoLocation);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ if ((events & PhoneStateListener.LISTEN_PHYSICAL_CHANNEL_CONFIGURATION) != 0) {
+ try {
+ r.callback.onPhysicalChannelConfigurationChanged(
+ mPhysicalChannelConfigs);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
}
}
+ } else {
+ if(DBG) log("listen: Unregister");
+ remove(callback.asBinder());
}
}
@@ -1170,7 +1133,7 @@
// If any of the system clients wants to always listen to signal strength,
// we need to set it on.
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) {
+ PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)) {
telephonyManager.createForSubscriptionId(subscriptionId)
.setAlwaysReportSignalStrength(true);
return;
@@ -1271,7 +1234,7 @@
// strength is removed from registry records, we need to check if
// the signal strength decision needs to update on its slot.
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) {
+ PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)) {
updateReportSignalStrengthDecision(r.subId);
}
return;
@@ -1291,8 +1254,8 @@
synchronized (mRecords) {
for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(PhoneStateListener.EVENT_CALL_STATE_CHANGED)
- && (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
+ if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_CALL_STATE) &&
+ (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
try {
// Ensure the listener has read call log permission; if they do not return
// an empty phone number.
@@ -1326,9 +1289,9 @@
mCallState[phoneId] = state;
mCallIncomingNumber[phoneId] = incomingNumber;
for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(PhoneStateListener.EVENT_CALL_STATE_CHANGED)
- && (r.subId == subId)
- && (r.subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
+ if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_CALL_STATE) &&
+ (r.subId == subId) &&
+ (r.subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
try {
String incomingNumberOrEmpty = getCallIncomingNumber(r, phoneId);
r.callback.onCallStateChanged(state, incomingNumberOrEmpty);
@@ -1368,9 +1331,8 @@
log("notifyServiceStateForSubscriber: r=" + r + " subId=" + subId
+ " phoneId=" + phoneId + " state=" + state);
}
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_SERVICE_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SERVICE_STATE) &&
+ idMatch(r.subId, subId, phoneId)) {
try {
ServiceState stateToSend;
@@ -1431,7 +1393,7 @@
try {
if ((activationType == SIM_ACTIVATION_TYPE_VOICE)
&& r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED)
+ PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE)
&& idMatch(r.subId, subId, phoneId)) {
if (DBG) {
log("notifyVoiceActivationStateForPhoneId: callback.onVASC r=" + r
@@ -1442,7 +1404,7 @@
}
if ((activationType == SIM_ACTIVATION_TYPE_DATA)
&& r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED)
+ PhoneStateListener.LISTEN_DATA_ACTIVATION_STATE)
&& idMatch(r.subId, subId, phoneId)) {
if (DBG) {
log("notifyDataActivationStateForPhoneId: callback.onDASC r=" + r
@@ -1481,11 +1443,9 @@
log("notifySignalStrengthForPhoneId: r=" + r + " subId=" + subId
+ " phoneId=" + phoneId + " ss=" + signalStrength);
}
- if ((r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED)
+ if ((r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SIGNAL_STRENGTHS)
|| r.matchPhoneStateListenerEvent(
- PhoneStateListener.
- EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED))
+ PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH))
&& idMatch(r.subId, subId, phoneId)) {
try {
if (DBG) {
@@ -1498,9 +1458,8 @@
mRemoveList.add(r.binder);
}
}
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SIGNAL_STRENGTH) &&
+ idMatch(r.subId, subId, phoneId)) {
try {
int gsmSignalStrength = signalStrength.getGsmSignalStrength();
int ss = (gsmSignalStrength == 99 ? -1 : gsmSignalStrength);
@@ -1546,8 +1505,8 @@
}
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) &&
+ idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onCarrierNetworkChange(active);
} catch (RemoteException ex) {
@@ -1577,10 +1536,9 @@
if (validatePhoneId(phoneId)) {
mCellInfo.set(phoneId, cellInfo);
for (Record r : mRecords) {
- if (validateEventAndUserLocked(
- r, PhoneStateListener.EVENT_CELL_INFO_CHANGED)
- && idMatch(r.subId, subId, phoneId)
- && (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+ if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) &&
+ idMatch(r.subId, subId, phoneId) &&
+ (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
&& checkFineLocationAccess(r, Build.VERSION_CODES.Q))) {
try {
if (DBG_LOC) {
@@ -1612,8 +1570,8 @@
mMessageWaiting[phoneId] = mwi;
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) &&
+ idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onMessageWaitingIndicatorChanged(mwi);
} catch (RemoteException ex) {
@@ -1639,8 +1597,8 @@
mUserMobileDataState[phoneId] = state;
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) &&
+ idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onUserMobileDataStateChanged(state);
} catch (RemoteException ex) {
@@ -1678,7 +1636,7 @@
mTelephonyDisplayInfos[phoneId] = telephonyDisplayInfo;
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED)
+ PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
&& idMatchWithoutDefaultPhoneCheck(r.subId, subId)) {
try {
r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
@@ -1710,8 +1668,8 @@
mCallForwarding[phoneId] = cfi;
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) &&
+ idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onCallForwardingIndicatorChanged(cfi);
} catch (RemoteException ex) {
@@ -1738,9 +1696,8 @@
mDataActivity[phoneId] = state;
for (Record r : mRecords) {
// Notify by correct subId.
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_DATA_ACTIVITY) &&
+ idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onDataActivity(state);
} catch (RemoteException ex) {
@@ -1787,7 +1744,7 @@
mLocalLog.log(str);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED)
+ PhoneStateListener.LISTEN_DATA_CONNECTION_STATE)
&& idMatch(r.subId, subId, phoneId)) {
try {
if (DBG) {
@@ -1812,7 +1769,7 @@
if (!Objects.equals(oldState, preciseState)) {
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED)
+ PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onPreciseDataConnectionStateChanged(preciseState);
@@ -1857,10 +1814,9 @@
if (validatePhoneId(phoneId) && !Objects.equals(cellIdentity, mCellIdentity[phoneId])) {
mCellIdentity[phoneId] = cellIdentity;
for (Record r : mRecords) {
- if (validateEventAndUserLocked(
- r, PhoneStateListener.EVENT_CELL_LOCATION_CHANGED)
- && idMatch(r.subId, subId, phoneId)
- && (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+ if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) &&
+ idMatch(r.subId, subId, phoneId) &&
+ (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
&& checkFineLocationAccess(r, Build.VERSION_CODES.Q))) {
try {
if (DBG_LOC) {
@@ -1911,8 +1867,7 @@
}
for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED)
+ if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_PRECISE_CALL_STATE)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
@@ -1921,7 +1876,7 @@
}
}
if (notifyCallAttributes && r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED)
+ PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
@@ -1970,7 +1925,7 @@
mImsReasonInfo.set(phoneId, imsReasonInfo);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED)
+ PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES)
&& idMatch(r.subId, subId, phoneId)) {
try {
if (DBG_LOC) {
@@ -2002,8 +1957,8 @@
mSrvccState[phoneId] = state;
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_SRVCC_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED) &&
+ idMatch(r.subId, subId, phoneId)) {
try {
if (DBG_LOC) {
log("notifySrvccStateChanged: mSrvccState=" + state + " r=" + r);
@@ -2031,7 +1986,7 @@
log("notifyOemHookRawEventForSubscriber: r=" + r + " subId=" + subId);
}
if ((r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_OEM_HOOK_RAW))
+ PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT))
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onOemHookRawEvent(rawData);
@@ -2059,7 +2014,7 @@
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED)) {
+ PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE)) {
try {
r.callback.onPhoneCapabilityChanged(capability);
} catch (RemoteException ex) {
@@ -2084,7 +2039,7 @@
synchronized (mRecords) {
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED)) {
+ PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE)) {
try {
r.callback.onActiveDataSubIdChanged(activeDataSubId);
} catch (RemoteException ex) {
@@ -2111,7 +2066,7 @@
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED)
+ PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onRadioPowerStateChanged(state);
@@ -2140,7 +2095,7 @@
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED)
+ PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
@@ -2172,7 +2127,7 @@
for (Record r : mRecords) {
// Send to all listeners regardless of subscription
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL)) {
+ PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL)) {
try {
r.callback.onOutgoingEmergencyCall(emergencyNumber, subId);
} catch (RemoteException ex) {
@@ -2196,7 +2151,7 @@
for (Record r : mRecords) {
// Send to all listeners regardless of subscription
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS)) {
+ PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS)) {
try {
r.callback.onOutgoingEmergencySms(emergencyNumber, subId);
} catch (RemoteException ex) {
@@ -2226,7 +2181,7 @@
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED)
+ PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
@@ -2257,7 +2212,7 @@
if (validatePhoneId(phoneId)) {
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_REGISTRATION_FAILURE)
+ PhoneStateListener.LISTEN_REGISTRATION_FAILURE)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onRegistrationFailed(
@@ -2300,7 +2255,7 @@
if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_BARRING_INFO_CHANGED)
+ PhoneStateListener.LISTEN_BARRING_INFO)
&& idMatch(r.subId, subId, phoneId)) {
try {
if (DBG_LOC) {
@@ -2327,14 +2282,14 @@
* @param subId the subId
* @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
*/
- public void notifyPhysicalChannelConfigForSubscriber(
+ public void notifyPhysicalChannelConfigurationForSubscriber(
int subId, List<PhysicalChannelConfig> configs) {
- if (!checkNotifyPermission("notifyPhysicalChannelConfig()")) {
+ if (!checkNotifyPermission("notifyPhysicalChannelConfiguration()")) {
return;
}
if (VDBG) {
- log("notifyPhysicalChannelConfig: subId=" + subId + " configs=" + configs);
+ log("notifyPhysicalChannelConfiguration: subId=" + subId + " configs=" + configs);
}
synchronized (mRecords) {
@@ -2343,15 +2298,15 @@
mPhysicalChannelConfigs.set(phoneId, configs.get(phoneId));
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED)
+ PhoneStateListener.LISTEN_PHYSICAL_CHANNEL_CONFIGURATION)
&& idMatch(r.subId, subId, phoneId)) {
try {
if (DBG_LOC) {
- log("notifyPhysicalChannelConfig: "
+ log("notifyPhysicalChannelConfiguration: "
+ "mPhysicalChannelConfigs="
+ configs + " r=" + r);
}
- r.callback.onPhysicalChannelConfigChanged(configs);
+ r.callback.onPhysicalChannelConfigurationChanged(configs);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2661,18 +2616,18 @@
== PackageManager.PERMISSION_GRANTED;
}
- private boolean checkListenerPermission(Set<Integer> events, int subId, String callingPackage,
- @Nullable String callingFeatureId, String message) {
+ private boolean checkListenerPermission(long events, int subId, String callingPackage,
+ @Nullable String callingFeatureId, String message) {
LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder =
new LocationAccessPolicy.LocationPermissionQuery.Builder()
- .setCallingPackage(callingPackage)
- .setMethod(message + " events: " + events)
- .setCallingPid(Binder.getCallingPid())
- .setCallingUid(Binder.getCallingUid());
+ .setCallingPackage(callingPackage)
+ .setMethod(message + " events: " + events)
+ .setCallingPid(Binder.getCallingPid())
+ .setCallingUid(Binder.getCallingUid());
boolean shouldCheckLocationPermissions = false;
- if (isLocationPermissionRequired(events)) {
+ if ((events & ENFORCE_LOCATION_PERMISSION_MASK) != 0) {
// Everything that requires fine location started in Q. So far...
locationQueryBuilder.setMinSdkVersionForFine(Build.VERSION_CODES.Q);
// If we're enforcing fine starting in Q, we also want to enforce coarse even for
@@ -2697,14 +2652,14 @@
}
}
- if (isPhoneStatePermissionRequired(events)) {
+ if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) {
if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
mContext, subId, callingPackage, callingFeatureId, message)) {
isPermissionCheckSuccessful = false;
}
}
- if (isPrecisePhoneStatePermissionRequired(events)) {
+ if ((events & ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK) != 0) {
// check if calling app has either permission READ_PRECISE_PHONE_STATE
// or with carrier privileges
try {
@@ -2715,20 +2670,21 @@
}
}
- if (isActiveEmergencySessionPermissionRequired(events)) {
+ if ((events & READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK) != 0) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION, null);
}
- if ((events.contains(PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED))) {
+ if ((events & PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) != 0) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH, null);
}
- if (isPrivilegedPhoneStatePermissionRequired(events)) {
+ if ((events & READ_PRIVILEGED_PHONE_STATE_PERMISSION_MASK) != 0) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
}
+
return isPermissionCheckSuccessful;
}
@@ -2736,25 +2692,25 @@
int size = mRemoveList.size();
if (VDBG) log("handleRemoveListLocked: mRemoveList.size()=" + size);
if (size > 0) {
- for (IBinder b : mRemoveList) {
+ for (IBinder b: mRemoveList) {
remove(b);
}
mRemoveList.clear();
}
}
- private boolean validateEventAndUserLocked(Record r, int event) {
+ private boolean validateEventsAndUserLocked(Record r, int events) {
int foregroundUser;
final long callingIdentity = Binder.clearCallingIdentity();
boolean valid = false;
try {
foregroundUser = ActivityManager.getCurrentUser();
valid = UserHandle.getUserId(r.callerUid) == foregroundUser
- && r.matchPhoneStateListenerEvent(event);
+ && r.matchPhoneStateListenerEvent(events);
if (DBG | DBG_LOC) {
- log("validateEventAndUserLocked: valid=" + valid
+ log("validateEventsAndUserLocked: valid=" + valid
+ " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser
- + " r.eventList=" + r.eventList + " event=" + event);
+ + " r.events=" + r.events + " events=" + events);
}
} finally {
Binder.restoreCallingIdentity(callingIdentity);
@@ -2866,14 +2822,9 @@
}
private void checkPossibleMissNotify(Record r, int phoneId) {
- Set<Integer> events = r.eventList;
+ long events = r.events;
- if (events == null || events.isEmpty()) {
- log("checkPossibleMissNotify: events = null.");
- return;
- }
-
- if ((events.contains(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED))) {
+ if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
try {
if (VDBG) log("checkPossibleMissNotify: onServiceStateChanged state=" +
mServiceState[phoneId]);
@@ -2892,9 +2843,8 @@
}
}
- if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED)
- || events.contains(
- PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) {
+ if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0
+ || (events & PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) != 0) {
try {
if (mSignalStrength[phoneId] != null) {
SignalStrength signalStrength = mSignalStrength[phoneId];
@@ -2909,7 +2859,7 @@
}
}
- if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED)) {
+ if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
try {
if (mSignalStrength[phoneId] != null) {
int gsmSignalStrength = mSignalStrength[phoneId]
@@ -2926,7 +2876,7 @@
}
}
- if (validateEventAndUserLocked(r, PhoneStateListener.EVENT_CELL_INFO_CHANGED)) {
+ if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
try {
if (DBG_LOC) {
log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = "
@@ -2941,7 +2891,7 @@
}
}
- if (events.contains(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED)) {
+ if ((events & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) {
try {
if (VDBG) {
log("checkPossibleMissNotify: onUserMobileDataStateChanged phoneId="
@@ -2953,7 +2903,7 @@
}
}
- if (events.contains(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED)) {
+ if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
try {
if (VDBG) {
log("checkPossibleMissNotify: onDisplayInfoChanged phoneId="
@@ -2967,7 +2917,7 @@
}
}
- if (events.contains(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)) {
+ if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
try {
if (VDBG) {
log("checkPossibleMissNotify: onMessageWaitingIndicatorChanged phoneId="
@@ -2980,7 +2930,7 @@
}
}
- if (events.contains(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)) {
+ if ((events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
try {
if (VDBG) {
log("checkPossibleMissNotify: onCallForwardingIndicatorChanged phoneId="
@@ -2993,7 +2943,7 @@
}
}
- if (validateEventAndUserLocked(r, PhoneStateListener.EVENT_CELL_LOCATION_CHANGED)) {
+ if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) {
try {
if (DBG_LOC) {
log("checkPossibleMissNotify: onCellLocationChanged mCellIdentity = "
@@ -3009,7 +2959,7 @@
}
}
- if (events.contains(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED)) {
+ if ((events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
try {
if (DBG) {
log("checkPossibleMissNotify: onDataConnectionStateChanged(mDataConnectionState"
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index f376473..165b6a1 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -165,9 +165,13 @@
// TODO: Clear VCN configuration, trigger teardown as necessary
}
- @VisibleForTesting(visibility = Visibility.PRIVATE)
- class VcnNetworkProvider extends NetworkProvider {
- VcnNetworkProvider(@NonNull Context context, @NonNull Looper looper) {
+ /**
+ * Network provider for VCN networks.
+ *
+ * @hide
+ */
+ public class VcnNetworkProvider extends NetworkProvider {
+ VcnNetworkProvider(Context context, Looper looper) {
super(context, looper, VcnNetworkProvider.class.getSimpleName());
}
diff --git a/services/core/java/com/android/server/adb/AdbShellCommand.java b/services/core/java/com/android/server/adb/AdbShellCommand.java
index 7691852..d7e95df 100644
--- a/services/core/java/com/android/server/adb/AdbShellCommand.java
+++ b/services/core/java/com/android/server/adb/AdbShellCommand.java
@@ -16,7 +16,7 @@
package com.android.server.adb;
-import android.os.BasicShellCommandHandler;
+import com.android.modules.utils.BasicShellCommandHandler;
import java.io.PrintWriter;
import java.util.Objects;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 01e77f8..d6f7299 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1630,6 +1630,8 @@
}
}
+ // TODO: remove as part of fixing b/173627642
+ @SuppressWarnings("AndroidFrameworkCompatChange")
private void postFgsNotificationLocked(ServiceRecord r) {
boolean showNow = !mAm.mConstants.mFlagFgsNotificationDeferralEnabled;
if (!showNow) {
diff --git a/services/core/java/com/android/server/am/ActiveUids.java b/services/core/java/com/android/server/am/ActiveUids.java
index c86c29f..27be53a 100644
--- a/services/core/java/com/android/server/am/ActiveUids.java
+++ b/services/core/java/com/android/server/am/ActiveUids.java
@@ -52,9 +52,8 @@
void clear() {
mActiveUids.clear();
- if (mPostChangesToAtm) {
- mService.mAtmInternal.onActiveUidsCleared();
- }
+ // It is only called for a temporal container with mPostChangesToAtm == false or test case.
+ // So there is no need to notify activity task manager.
}
UidRecord get(int uid) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c3ba150..ed2faf9 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -519,16 +519,24 @@
return data;
}
- public ParcelFileDescriptor getStatisticsStream() {
+ /**
+ * Returns parceled BatteryStats as a MemoryFile.
+ *
+ * @param forceUpdate If true, runs a sync to get fresh battery stats. Otherwise,
+ * returns the current values.
+ */
+ public ParcelFileDescriptor getStatisticsStream(boolean forceUpdate) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BATTERY_STATS, null);
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
Parcel out = Parcel.obtain();
- // Drain the handler queue to make sure we've handled all pending works, so we'll get
- // an accurate stats.
- awaitCompletion();
- syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
+ if (forceUpdate) {
+ // Drain the handler queue to make sure we've handled all pending works, so we'll get
+ // an accurate stats.
+ awaitCompletion();
+ syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
+ }
synchronized (mStats) {
mStats.writeToParcel(out, 0);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 09937e3..da47040 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -94,7 +94,6 @@
import android.app.RuntimeAppOpAccessMessage;
import android.app.SyncNotedAppOp;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -3018,28 +3017,6 @@
}
}
- private boolean isTrustedVoiceServiceProxy(String packageName, int code) {
- if (code != OP_RECORD_AUDIO) {
- return false;
- }
- final String voiceRecognitionComponent = Settings.Secure.getString(
- mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
- final String voiceInteractionComponent = Settings.Secure.getString(
- mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE);
-
- final String voiceRecognitionServicePackageName =
- getComponentPackageNameFromString(voiceRecognitionComponent);
- final String voiceInteractionServicePackageName =
- getComponentPackageNameFromString(voiceInteractionComponent);
- return Objects.equals(packageName, voiceRecognitionServicePackageName) && Objects.equals(
- voiceRecognitionServicePackageName, voiceInteractionServicePackageName);
- }
-
- private String getComponentPackageNameFromString(String from) {
- ComponentName componentName = from != null ? ComponentName.unflattenFromString(from) : null;
- return componentName != null ? componentName.getPackageName() : "";
- }
-
@Override
public int noteProxyOperation(int code, int proxiedUid, String proxiedPackageName,
String proxiedAttributionTag, int proxyUid, String proxyPackageName,
@@ -3057,10 +3034,12 @@
// This is a workaround for R QPR, new API change is not allowed. We only allow the current
// voice recognizer is also the voice interactor to noteproxy op.
- final boolean isTrustVoiceServiceProxy = isTrustedVoiceServiceProxy(proxyPackageName, code);
+ final boolean isTrustVoiceServiceProxy =
+ AppOpsManager.isTrustedVoiceServiceProxy(mContext, proxyPackageName, code);
+ final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
final boolean isProxyTrusted = mContext.checkPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
- == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy;
+ == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy || isSelfBlame;
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
@@ -3524,10 +3503,12 @@
// This is a workaround for R QPR, new API change is not allowed. We only allow the current
// voice recognizer is also the voice interactor to noteproxy op.
- final boolean isTrustVoiceServiceProxy = isTrustedVoiceServiceProxy(proxyPackageName, code);
+ final boolean isTrustVoiceServiceProxy =
+ AppOpsManager.isTrustedVoiceServiceProxy(mContext, proxyPackageName, code);
+ final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
final boolean isProxyTrusted = mContext.checkPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
- == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy;
+ == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy || isSelfBlame;
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 84de25c..72d3835 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -48,10 +48,10 @@
]
},
{
- "name": "CtsStatsdHostTestCases",
+ "name": "CtsStatsdAtomHostTestCases",
"options": [
{
- "include-filter": "android.cts.statsd.atom.UidAtomTests#testAppOps"
+ "include-filter": "android.cts.statsdatom.appops.AppOpsTests#testAppOps"
}
]
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlNativeHandleUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlNativeHandleUtils.java
new file mode 100644
index 0000000..77e6ceb
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlNativeHandleUtils.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import android.annotation.Nullable;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * A utility class for the AIDL implementation of NativeHandle - {@link
+ * android.hardware.common.NativeHandle}.
+ */
+public final class AidlNativeHandleUtils {
+
+ /**
+ * Converts a {@link android.os.NativeHandle} to a {@link android.hardware.common.NativeHandle}
+ * by duplicating the underlying file descriptors.
+ *
+ * Both the original and new handle must be closed after use.
+ *
+ * @param handle {@link android.os.NativeHandle}. Can be null.
+ * @return a {@link android.hardware.common.NativeHandle} representation of {@code handle}.
+ * Returns null if {@code handle} is null.
+ * @throws IOException if any of the underlying calls to {@code dup} fail.
+ */
+ public static @Nullable android.hardware.common.NativeHandle dup(
+ @Nullable android.os.NativeHandle handle) throws IOException {
+ if (handle == null) {
+ return null;
+ }
+ android.hardware.common.NativeHandle res = new android.hardware.common.NativeHandle();
+ final FileDescriptor[] fds = handle.getFileDescriptors();
+ res.ints = handle.getInts().clone();
+ res.fds = new ParcelFileDescriptor[fds.length];
+ for (int i = 0; i < fds.length; ++i) {
+ res.fds[i] = ParcelFileDescriptor.dup(fds[i]);
+ }
+ return res;
+ }
+
+ /**
+ * Closes the file descriptors contained within a {@link android.hardware.common.NativeHandle}.
+ * This is a no-op if the handle is null.
+ *
+ * This should only be used for handles that own their file descriptors, for example handles
+ * obtained using {@link #dup(android.os.NativeHandle)}.
+ *
+ * @param handle {@link android.hardware.common.NativeHandle}. Can be null.
+ * @throws IOException if any of the underlying calls to {@code close} fail.
+ */
+ public static void close(@Nullable android.hardware.common.NativeHandle handle)
+ throws IOException {
+ if (handle != null) {
+ for (ParcelFileDescriptor fd : handle.fds) {
+ if (fd != null) {
+ fd.close();
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index f950862..f09df1e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -39,6 +39,7 @@
import com.android.server.biometrics.sensors.EnrollClient;
import com.android.server.biometrics.sensors.face.FaceUtils;
+import java.io.IOException;
import java.util.ArrayList;
/**
@@ -51,6 +52,7 @@
@NonNull private final int[] mEnrollIgnoreList;
@NonNull private final int[] mEnrollIgnoreListVendor;
@Nullable private ICancellationSignal mCancellationSignal;
+ @Nullable private android.hardware.common.NativeHandle mPreviewSurface;
private final int mMaxTemplatesPerUser;
FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
@@ -66,6 +68,24 @@
mEnrollIgnoreListVendor = getContext().getResources()
.getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
mMaxTemplatesPerUser = maxTemplatesPerUser;
+ try {
+ // We must manually close the duplicate handle after it's no longer needed.
+ // The caller is responsible for closing the original handle.
+ mPreviewSurface = AidlNativeHandleUtils.dup(previewSurface);
+ } catch (IOException e) {
+ mPreviewSurface = null;
+ Slog.e(TAG, "Failed to dup previewSurface", e);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ try {
+ AidlNativeHandleUtils.close(mPreviewSurface);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to close mPreviewSurface", e);
+ }
+ super.destroy();
}
@Override
@@ -94,10 +114,9 @@
try {
// TODO(b/172593978): Pass features.
- // TODO(b/172593521): Pass mPreviewSurface as android.hardware.common.NativeHandle.
mCancellationSignal = getFreshDaemon().enroll(mSequentialId,
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken),
- null /* mPreviewSurface */);
+ mPreviewSurface);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting enroll", e);
onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 26f6264..36796b8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -205,7 +205,7 @@
private void createNewSessionWithoutHandler(@NonNull IFace daemon, int sensorId,
int userId) throws RemoteException {
- // Note that per IFingerprint createSession contract, this method will block until all
+ // Note that per IFace createSession contract, this method will block until all
// existing operations are canceled/finished. However, also note that this is fine, since
// this method "withoutHandler" means it should only ever be invoked from the worker thread,
// so callers will never be blocked.
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 3555bbe..c57ab50 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -71,7 +71,6 @@
import com.android.server.biometrics.sensors.face.LockoutHalImpl;
import com.android.server.biometrics.sensors.face.ServiceProvider;
import com.android.server.biometrics.sensors.face.UsageStats;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import org.json.JSONArray;
import org.json.JSONException;
@@ -259,8 +258,7 @@
if (!removed.isEmpty()) {
// Convert to old fingerprint-like behavior, where remove() receives
- // one removal
- // at a time. This way, remove can share some more common code.
+ // one removal at a time. This way, remove can share some more common code.
for (int i = 0; i < removed.size(); i++) {
final int id = removed.get(i);
final Face face = new Face("", id, deviceId);
@@ -290,21 +288,16 @@
final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
if (!faceIds.isEmpty()) {
- // Convert to old fingerprint-like behavior, where enumerate()
- // receives one
- // template at a time. This way, enumerate can share some more common
- // code.
+ // Convert to old fingerprint-like behavior, where enumerate() receives one
+ // template at a time. This way, enumerate can share some more common code.
for (int i = 0; i < faceIds.size(); i++) {
final Face face = new Face("", faceIds.get(i), deviceId);
enumerateConsumer.onEnumerationResult(face, faceIds.size() - i - 1);
}
} else {
- // For face, the HIDL contract is to receive an empty list when there
- // are no
- // templates enrolled. Send a null identifier since we don't consume
- // them
- // anywhere, and send remaining == 0 so this code can be shared with
- // Fingerprint@2.1
+ // For face, the HIDL contract is to receive an empty list when there are no
+ // templates enrolled. Send a null identifier since we don't consume them
+ // anywhere, and send remaining == 0 so this code can be shared with Face@1.1
enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
}
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 437ecd7..fd1181b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
@@ -89,7 +88,8 @@
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken));
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting enroll", e);
- onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
+ onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
+ 0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index c750b90..2a89a88 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -65,7 +65,7 @@
final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
.size();
if (enrolled >= limit) {
- Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
+ Slog.w(TAG, "Too many fingerprints registered, user: " + getTargetUserId());
return true;
}
return false;
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index a4ae9c8..2a81426 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -22,6 +22,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.hardware.CameraSessionStats;
+import android.hardware.CameraStreamStats;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceProxy;
import android.media.AudioManager;
@@ -36,11 +38,13 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserManager;
+import android.stats.camera.nano.CameraStreamProto;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.framework.protobuf.nano.MessageNano;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
@@ -97,7 +101,10 @@
@interface DeviceStateFlags {}
// Maximum entries to keep in usage history before dumping out
- private static final int MAX_USAGE_HISTORY = 100;
+ private static final int MAX_USAGE_HISTORY = 20;
+ // Number of stream statistics being dumped for each camera session
+ // Must be equal to number of CameraStreamProto in CameraActionEvent
+ private static final int MAX_STREAM_STATISTICS = 5;
private final Context mContext;
private final ServiceThread mHandlerThread;
@@ -123,7 +130,6 @@
private final ArrayMap<String, CameraUsageEvent> mActiveCameraUsage = new ArrayMap<>();
private final List<CameraUsageEvent> mCameraUsageHistory = new ArrayList<>();
- private final MetricsLogger mLogger = new MetricsLogger();
private static final String NFC_NOTIFICATION_PROP = "ro.camera.notify_nfc";
private static final String NFC_SERVICE_BINDER_NAME = "nfc";
private static final IBinder nfcInterfaceToken = new Binder();
@@ -137,27 +143,50 @@
* Structure to track camera usage
*/
private static class CameraUsageEvent {
+ public final String mCameraId;
public final int mCameraFacing;
public final String mClientName;
public final int mAPILevel;
+ public final boolean mIsNdk;
+ public final int mAction;
+ public final int mLatencyMs;
+ public final int mOperatingMode;
private boolean mCompleted;
+ public int mInternalReconfigure;
+ public long mRequestCount;
+ public long mResultErrorCount;
+ public boolean mDeviceError;
+ public List<CameraStreamStats> mStreamStats;
private long mDurationOrStartTimeMs; // Either start time, or duration once completed
- public CameraUsageEvent(int facing, String clientName, int apiLevel) {
+ CameraUsageEvent(String cameraId, int facing, String clientName, int apiLevel,
+ boolean isNdk, int action, int latencyMs, int operatingMode) {
+ mCameraId = cameraId;
mCameraFacing = facing;
mClientName = clientName;
mAPILevel = apiLevel;
mDurationOrStartTimeMs = SystemClock.elapsedRealtime();
mCompleted = false;
+ mIsNdk = isNdk;
+ mAction = action;
+ mLatencyMs = latencyMs;
+ mOperatingMode = operatingMode;
}
- public void markCompleted() {
+ public void markCompleted(int internalReconfigure, long requestCount,
+ long resultErrorCount, boolean deviceError,
+ List<CameraStreamStats> streamStats) {
if (mCompleted) {
return;
}
mCompleted = true;
mDurationOrStartTimeMs = SystemClock.elapsedRealtime() - mDurationOrStartTimeMs;
+ mInternalReconfigure = internalReconfigure;
+ mRequestCount = requestCount;
+ mResultErrorCount = resultErrorCount;
+ mDeviceError = deviceError;
+ mStreamStats = streamStats;
if (CameraServiceProxy.DEBUG) {
Slog.v(TAG, "A camera facing " + cameraFacingToString(mCameraFacing) +
" was in use by " + mClientName + " for " +
@@ -211,19 +240,22 @@
}
@Override
- public void notifyCameraState(String cameraId, int newCameraState, int facing,
- String clientName, int apiLevel) {
+ public void notifyCameraState(CameraSessionStats cameraState) {
if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " +
" camera service UID!");
return;
}
- String state = cameraStateToString(newCameraState);
- String facingStr = cameraFacingToString(facing);
- if (DEBUG) Slog.v(TAG, "Camera " + cameraId + " facing " + facingStr + " state now " +
- state + " for client " + clientName + " API Level " + apiLevel);
+ String state = cameraStateToString(cameraState.getNewCameraState());
+ String facingStr = cameraFacingToString(cameraState.getFacing());
+ if (DEBUG) {
+ Slog.v(TAG, "Camera " + cameraState.getCameraId()
+ + " facing " + facingStr + " state now " + state
+ + " for client " + cameraState.getClientName()
+ + " API Level " + cameraState.getApiLevel());
+ }
- updateActivityCount(cameraId, newCameraState, facing, clientName, apiLevel);
+ updateActivityCount(cameraState);
}
};
@@ -385,20 +417,80 @@
private void logCameraUsageEvent(CameraUsageEvent e) {
int facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__UNKNOWN;
switch(e.mCameraFacing) {
- case ICameraServiceProxy.CAMERA_FACING_BACK:
+ case CameraSessionStats.CAMERA_FACING_BACK:
facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__BACK;
break;
- case ICameraServiceProxy.CAMERA_FACING_FRONT:
+ case CameraSessionStats.CAMERA_FACING_FRONT:
facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__FRONT;
break;
- case ICameraServiceProxy.CAMERA_FACING_EXTERNAL:
+ case CameraSessionStats.CAMERA_FACING_EXTERNAL:
facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__EXTERNAL;
break;
default:
Slog.w(TAG, "Unknown camera facing: " + e.mCameraFacing);
}
+
+ int streamCount = 0;
+ if (e.mStreamStats != null) {
+ streamCount = e.mStreamStats.size();
+ }
+ if (CameraServiceProxy.DEBUG) {
+ Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction
+ + " clientName " + e.mClientName
+ + ", duration " + e.getDuration()
+ + ", APILevel " + e.mAPILevel
+ + ", cameraId " + e.mCameraId
+ + ", facing " + facing
+ + ", isNdk " + e.mIsNdk
+ + ", latencyMs " + e.mLatencyMs
+ + ", operatingMode " + e.mOperatingMode
+ + ", internalReconfigure " + e.mInternalReconfigure
+ + ", requestCount " + e.mRequestCount
+ + ", resultErrorCount " + e.mResultErrorCount
+ + ", deviceError " + e.mDeviceError
+ + ", streamCount is " + streamCount);
+ }
+ // Convert from CameraStreamStats to CameraStreamProto
+ CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS];
+ for (int i = 0; i < MAX_STREAM_STATISTICS; i++) {
+ streamProtos[i] = new CameraStreamProto();
+ if (i < streamCount) {
+ CameraStreamStats streamStats = e.mStreamStats.get(i);
+ streamProtos[i].width = streamStats.getWidth();
+ streamProtos[i].height = streamStats.getHeight();
+ streamProtos[i].format = streamStats.getFormat();
+ streamProtos[i].dataSpace = streamStats.getDataSpace();
+ streamProtos[i].usage = streamStats.getUsage();
+ streamProtos[i].requestCount = streamStats.getRequestCount();
+ streamProtos[i].errorCount = streamStats.getErrorCount();
+ streamProtos[i].firstCaptureLatencyMillis = streamStats.getStartLatencyMs();
+ streamProtos[i].maxHalBuffers = streamStats.getMaxHalBuffers();
+ streamProtos[i].maxAppBuffers = streamStats.getMaxAppBuffers();
+
+ if (CameraServiceProxy.DEBUG) {
+ Slog.v(TAG, "Stream " + i + ": width " + streamProtos[i].width
+ + ", height " + streamProtos[i].height
+ + ", format " + streamProtos[i].format
+ + ", dataSpace " + streamProtos[i].dataSpace
+ + ", usage " + streamProtos[i].usage
+ + ", requestCount " + streamProtos[i].requestCount
+ + ", errorCount " + streamProtos[i].errorCount
+ + ", firstCaptureLatencyMillis "
+ + streamProtos[i].firstCaptureLatencyMillis
+ + ", maxHalBuffers " + streamProtos[i].maxHalBuffers
+ + ", maxAppBuffers " + streamProtos[i].maxAppBuffers);
+ }
+ }
+ }
FrameworkStatsLog.write(FrameworkStatsLog.CAMERA_ACTION_EVENT, e.getDuration(),
- e.mAPILevel, e.mClientName, facing);
+ e.mAPILevel, e.mClientName, facing, e.mCameraId, e.mAction, e.mIsNdk,
+ e.mLatencyMs, e.mOperatingMode, e.mInternalReconfigure,
+ e.mRequestCount, e.mResultErrorCount, e.mDeviceError,
+ streamCount, MessageNano.toByteArray(streamProtos[0]),
+ MessageNano.toByteArray(streamProtos[1]),
+ MessageNano.toByteArray(streamProtos[2]),
+ MessageNano.toByteArray(streamProtos[3]),
+ MessageNano.toByteArray(streamProtos[4]));
}
}
@@ -410,35 +502,6 @@
synchronized(mLock) {
// Randomize order of events so that it's not meaningful
Collections.shuffle(mCameraUsageHistory);
- for (CameraUsageEvent e : mCameraUsageHistory) {
- if (DEBUG) {
- Slog.v(TAG, "Camera: " + e.mClientName + " used a camera facing " +
- cameraFacingToString(e.mCameraFacing) + " for " +
- e.getDuration() + " ms");
- }
- int subtype = 0;
- switch(e.mCameraFacing) {
- case ICameraServiceProxy.CAMERA_FACING_BACK:
- subtype = MetricsEvent.CAMERA_BACK_USED;
- break;
- case ICameraServiceProxy.CAMERA_FACING_FRONT:
- subtype = MetricsEvent.CAMERA_FRONT_USED;
- break;
- case ICameraServiceProxy.CAMERA_FACING_EXTERNAL:
- subtype = MetricsEvent.CAMERA_EXTERNAL_USED;
- break;
- default:
- continue;
- }
- LogMaker l = new LogMaker(MetricsEvent.ACTION_CAMERA_EVENT)
- .setType(MetricsEvent.TYPE_ACTION)
- .setSubtype(subtype)
- .setLatency(e.getDuration())
- .addTaggedData(MetricsEvent.FIELD_CAMERA_API_LEVEL, e.mAPILevel)
- .setPackageName(e.mClientName);
- mLogger.write(l);
- }
-
mLogWriterService.execute(new EventWriterTask(
new ArrayList<CameraUsageEvent>(mCameraUsageHistory)));
@@ -569,13 +632,25 @@
return true;
}
- private void updateActivityCount(String cameraId, int newCameraState, int facing,
- String clientName, int apiLevel) {
+ private void updateActivityCount(CameraSessionStats cameraState) {
+ String cameraId = cameraState.getCameraId();
+ int newCameraState = cameraState.getNewCameraState();
+ int facing = cameraState.getFacing();
+ String clientName = cameraState.getClientName();
+ int apiLevel = cameraState.getApiLevel();
+ boolean isNdk = cameraState.isNdk();
+ int sessionType = cameraState.getSessionType();
+ int internalReconfigureCount = cameraState.getInternalReconfigureCount();
+ int latencyMs = cameraState.getLatencyMs();
+ long requestCount = cameraState.getRequestCount();
+ long resultErrorCount = cameraState.getResultErrorCount();
+ boolean deviceError = cameraState.getDeviceErrorFlag();
+ List<CameraStreamStats> streamStats = cameraState.getStreamStats();
synchronized(mLock) {
// Update active camera list and notify NFC if necessary
boolean wasEmpty = mActiveCameraUsage.isEmpty();
switch (newCameraState) {
- case ICameraServiceProxy.CAMERA_STATE_OPEN:
+ case CameraSessionStats.CAMERA_STATE_OPEN:
// Notify the audio subsystem about the facing of the most-recently opened
// camera This can be used to select the best audio tuning in case video
// recording with that camera will happen. Since only open events are used, if
@@ -584,13 +659,18 @@
AudioManager audioManager = getContext().getSystemService(AudioManager.class);
if (audioManager != null) {
// Map external to front for audio tuning purposes
- String facingStr = (facing == ICameraServiceProxy.CAMERA_FACING_BACK) ?
+ String facingStr = (facing == CameraSessionStats.CAMERA_FACING_BACK) ?
"back" : "front";
String facingParameter = "cameraFacing=" + facingStr;
audioManager.setParameters(facingParameter);
}
+ CameraUsageEvent openEvent = new CameraUsageEvent(
+ cameraId, facing, clientName, apiLevel, isNdk,
+ FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__OPEN,
+ latencyMs, sessionType);
+ mCameraUsageHistory.add(openEvent);
break;
- case ICameraServiceProxy.CAMERA_STATE_ACTIVE:
+ case CameraSessionStats.CAMERA_STATE_ACTIVE:
// Check current active camera IDs to see if this package is already talking to
// some camera
boolean alreadyActivePackage = false;
@@ -609,40 +689,55 @@
}
// Update activity events
- CameraUsageEvent newEvent = new CameraUsageEvent(facing, clientName, apiLevel);
+ CameraUsageEvent newEvent = new CameraUsageEvent(
+ cameraId, facing, clientName, apiLevel, isNdk,
+ FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__SESSION,
+ latencyMs, sessionType);
CameraUsageEvent oldEvent = mActiveCameraUsage.put(cameraId, newEvent);
if (oldEvent != null) {
Slog.w(TAG, "Camera " + cameraId + " was already marked as active");
- oldEvent.markCompleted();
+ oldEvent.markCompleted(/*internalReconfigure*/0, /*requestCount*/0,
+ /*resultErrorCount*/0, /*deviceError*/false, streamStats);
mCameraUsageHistory.add(oldEvent);
}
break;
- case ICameraServiceProxy.CAMERA_STATE_IDLE:
- case ICameraServiceProxy.CAMERA_STATE_CLOSED:
+ case CameraSessionStats.CAMERA_STATE_IDLE:
+ case CameraSessionStats.CAMERA_STATE_CLOSED:
CameraUsageEvent doneEvent = mActiveCameraUsage.remove(cameraId);
- if (doneEvent == null) break;
+ if (doneEvent != null) {
- doneEvent.markCompleted();
- mCameraUsageHistory.add(doneEvent);
- if (mCameraUsageHistory.size() > MAX_USAGE_HISTORY) {
- dumpUsageEvents();
- }
+ doneEvent.markCompleted(internalReconfigureCount, requestCount,
+ resultErrorCount, deviceError, streamStats);
+ mCameraUsageHistory.add(doneEvent);
- // Check current active camera IDs to see if this package is still talking to
- // some camera
- boolean stillActivePackage = false;
- for (int i = 0; i < mActiveCameraUsage.size(); i++) {
- if (mActiveCameraUsage.valueAt(i).mClientName.equals(clientName)) {
- stillActivePackage = true;
- break;
+ // Check current active camera IDs to see if this package is still
+ // talking to some camera
+ boolean stillActivePackage = false;
+ for (int i = 0; i < mActiveCameraUsage.size(); i++) {
+ if (mActiveCameraUsage.valueAt(i).mClientName.equals(clientName)) {
+ stillActivePackage = true;
+ break;
+ }
+ }
+ // If not longer active, notify window manager about this package being done
+ // with camera
+ if (!stillActivePackage) {
+ WindowManagerInternal wmi =
+ LocalServices.getService(WindowManagerInternal.class);
+ wmi.removeNonHighRefreshRatePackage(clientName);
}
}
- // If not longer active, notify window manager about this package being done
- // with camera
- if (!stillActivePackage) {
- WindowManagerInternal wmi =
- LocalServices.getService(WindowManagerInternal.class);
- wmi.removeNonHighRefreshRatePackage(clientName);
+
+ if (newCameraState == CameraSessionStats.CAMERA_STATE_CLOSED) {
+ CameraUsageEvent closeEvent = new CameraUsageEvent(
+ cameraId, facing, clientName, apiLevel, isNdk,
+ FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__CLOSE,
+ latencyMs, sessionType);
+ mCameraUsageHistory.add(closeEvent);
+ }
+
+ if (mCameraUsageHistory.size() > MAX_USAGE_HISTORY) {
+ dumpUsageEvents();
}
break;
@@ -683,10 +778,10 @@
private static String cameraStateToString(int newCameraState) {
switch (newCameraState) {
- case ICameraServiceProxy.CAMERA_STATE_OPEN: return "CAMERA_STATE_OPEN";
- case ICameraServiceProxy.CAMERA_STATE_ACTIVE: return "CAMERA_STATE_ACTIVE";
- case ICameraServiceProxy.CAMERA_STATE_IDLE: return "CAMERA_STATE_IDLE";
- case ICameraServiceProxy.CAMERA_STATE_CLOSED: return "CAMERA_STATE_CLOSED";
+ case CameraSessionStats.CAMERA_STATE_OPEN: return "CAMERA_STATE_OPEN";
+ case CameraSessionStats.CAMERA_STATE_ACTIVE: return "CAMERA_STATE_ACTIVE";
+ case CameraSessionStats.CAMERA_STATE_IDLE: return "CAMERA_STATE_IDLE";
+ case CameraSessionStats.CAMERA_STATE_CLOSED: return "CAMERA_STATE_CLOSED";
default: break;
}
return "CAMERA_STATE_UNKNOWN";
@@ -694,9 +789,9 @@
private static String cameraFacingToString(int cameraFacing) {
switch (cameraFacing) {
- case ICameraServiceProxy.CAMERA_FACING_BACK: return "CAMERA_FACING_BACK";
- case ICameraServiceProxy.CAMERA_FACING_FRONT: return "CAMERA_FACING_FRONT";
- case ICameraServiceProxy.CAMERA_FACING_EXTERNAL: return "CAMERA_FACING_EXTERNAL";
+ case CameraSessionStats.CAMERA_FACING_BACK: return "CAMERA_FACING_BACK";
+ case CameraSessionStats.CAMERA_FACING_FRONT: return "CAMERA_FACING_FRONT";
+ case CameraSessionStats.CAMERA_FACING_EXTERNAL: return "CAMERA_FACING_EXTERNAL";
default: break;
}
return "CAMERA_FACING_UNKNOWN";
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 4d959d0..bdd315d 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -207,7 +207,8 @@
@VisibleForTesting protected String mPackage;
private int mOwnerUID;
private boolean mIsPackageTargetingAtLeastQ;
- private String mInterface;
+ @VisibleForTesting
+ protected String mInterface;
private Connection mConnection;
/** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 3172a04..d7dcbde 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -100,7 +100,7 @@
private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>();
public DeviceStateManagerService(@NonNull Context context) {
- this(context, new DeviceStatePolicyImpl());
+ this(context, new DeviceStatePolicyImpl(context));
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java
index f2413ed..2ba8758 100644
--- a/services/core/java/com/android/server/display/DisplayGroup.java
+++ b/services/core/java/com/android/server/display/DisplayGroup.java
@@ -22,10 +22,22 @@
/**
* Represents a collection of {@link LogicalDisplay}s which act in unison for certain behaviors and
* operations.
+ * @hide
*/
public class DisplayGroup {
- final List<LogicalDisplay> mDisplays = new ArrayList<>();
+ public static final int DEFAULT = 0;
+
+ private final List<LogicalDisplay> mDisplays = new ArrayList<>();
+ private final int mGroupId;
+
+ DisplayGroup(int groupId) {
+ mGroupId = groupId;
+ }
+
+ int getGroupId() {
+ return mGroupId;
+ }
void addDisplay(LogicalDisplay display) {
if (!mDisplays.contains(display)) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 004e481..4e60f1f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2536,6 +2536,13 @@
}
@Override
+ public int getDisplayGroupId(int displayId) {
+ synchronized (mSyncRoot) {
+ return mLogicalDisplayMapper.getDisplayGroupIdLocked(displayId);
+ }
+ }
+
+ @Override
public SurfaceControl.ScreenshotHardwareBuffer systemScreenshot(int displayId) {
return systemScreenshotInternal(displayId);
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 45c38b4..6b74170 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -94,6 +94,7 @@
private final SparseArray<LogicalDisplay> mLogicalDisplays =
new SparseArray<LogicalDisplay>();
private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
+ private int mNextNonDefaultGroupId = DisplayGroup.DEFAULT + 1;
/** A mapping from logical display id to display group. */
private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>();
@@ -178,6 +179,15 @@
}
}
+ public int getDisplayGroupIdLocked(int displayId) {
+ final DisplayGroup displayGroup = mDisplayGroups.get(displayId);
+ if (displayGroup != null) {
+ return displayGroup.getGroupId();
+ }
+
+ return -1;
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.println("LogicalDisplayMapper:");
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
@@ -309,7 +319,8 @@
final DisplayGroup displayGroup;
if (isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) {
- displayGroup = new DisplayGroup();
+ final int groupId = assignDisplayGroupIdLocked(isDefault);
+ displayGroup = new DisplayGroup(groupId);
} else {
displayGroup = mDisplayGroups.get(Display.DEFAULT_DISPLAY);
}
@@ -345,7 +356,8 @@
if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
// The display should have its own DisplayGroup.
if (defaultDisplayGroup.removeDisplay(display)) {
- final DisplayGroup displayGroup = new DisplayGroup();
+ final int groupId = assignDisplayGroupIdLocked(false);
+ final DisplayGroup displayGroup = new DisplayGroup(groupId);
displayGroup.addDisplay(display);
mDisplayGroups.append(display.getDisplayIdLocked(), displayGroup);
}
@@ -381,6 +393,10 @@
return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
}
+ private int assignDisplayGroupIdLocked(boolean isDefault) {
+ return isDefault ? DisplayGroup.DEFAULT : mNextNonDefaultGroupId++;
+ }
+
private int assignLayerStackLocked(int displayId) {
// Currently layer stacks and display ids are the same.
// This need not be the case.
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index d0cc8e71..42dcbeb 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -501,15 +501,6 @@
static final int DISABLED = 0;
static final int ENABLED = 1;
- @IntDef({
- VERSION_1_4,
- VERSION_2_0
- })
- @interface CecVersion {}
- static final int VERSION_1_3 = 0x04;
- static final int VERSION_1_4 = 0x05;
- static final int VERSION_2_0 = 0x06;
-
static final int ALL_DEVICE_TYPES_TV = 7;
static final int ALL_DEVICE_TYPES_RECORDER = 6;
static final int ALL_DEVICE_TYPES_TUNER = 5;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index e7f302c..fb71d95 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -18,6 +18,7 @@
import android.annotation.CallSuper;
import android.annotation.Nullable;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.input.InputManager;
@@ -560,7 +561,7 @@
protected abstract List<Integer> getDeviceFeatures();
protected boolean handleGiveFeatures(HdmiCecMessage message) {
- if (mService.getCecVersion() < Constants.VERSION_2_0) {
+ if (mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
return false;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 109f6a7..9ca1f91 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -128,25 +128,6 @@
String.valueOf(addr));
}
- @ServiceThreadOnly
- void queryDisplayStatus(IHdmiControlCallback callback) {
- assertRunOnServiceThread();
- List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
- if (!actions.isEmpty()) {
- Slog.i(TAG, "queryDisplayStatus already in progress");
- actions.get(0).addCallback(callback);
- return;
- }
- DevicePowerStatusAction action = DevicePowerStatusAction.create(this, Constants.ADDR_TV,
- callback);
- if (action == null) {
- Slog.w(TAG, "Cannot initiate queryDisplayStatus");
- invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
- return;
- }
- addAndStartAction(action);
- }
-
@Override
@ServiceThreadOnly
void onHotplug(int portId, boolean connected) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 62b7d8f..3b3ac74 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -70,6 +70,25 @@
super(service, deviceType);
}
+ @ServiceThreadOnly
+ void queryDisplayStatus(IHdmiControlCallback callback) {
+ assertRunOnServiceThread();
+ List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
+ if (!actions.isEmpty()) {
+ Slog.i(TAG, "queryDisplayStatus already in progress");
+ actions.get(0).addCallback(callback);
+ return;
+ }
+ DevicePowerStatusAction action = DevicePowerStatusAction.create(this, Constants.ADDR_TV,
+ callback);
+ if (action == null) {
+ Slog.w(TAG, "Cannot initiate queryDisplayStatus");
+ invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
+ return;
+ }
+ addAndStartAction(action);
+ }
+
@Override
@ServiceThreadOnly
void onHotplug(int portId, boolean connected) {
@@ -87,10 +106,14 @@
@ServiceThreadOnly
protected void sendStandby(int deviceId) {
assertRunOnServiceThread();
-
- // Send standby to TV only for now
- int targetAddress = Constants.ADDR_TV;
- mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
+ String sendStandbyOnSleep = mService.getHdmiCecConfig().getStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP);
+ if (sendStandbyOnSleep.equals(HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST)) {
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_BROADCAST));
+ return;
+ }
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
}
@ServiceThreadOnly
@@ -113,6 +136,44 @@
}
@ServiceThreadOnly
+ void toggleAndFollowTvPower() {
+ assertRunOnServiceThread();
+ // Wake up Android framework to take over CEC control from the microprocessor.
+ mService.wakeUp();
+ mService.queryDisplayStatus(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int status) {
+ if (status == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+ Slog.i(TAG, "TV power toggle: TV power status unknown");
+ sendUserControlPressedAndReleased(Constants.ADDR_TV,
+ HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
+ // Source device remains awake.
+ } else if (status == HdmiControlManager.POWER_STATUS_ON
+ || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
+ Slog.i(TAG, "TV power toggle: turning off TV");
+ sendStandby(0 /*unused */);
+ // Source device goes to standby, to follow the toggled TV power state.
+ mService.standby();
+ } else if (status == HdmiControlManager.POWER_STATUS_STANDBY
+ || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
+ Slog.i(TAG, "TV power toggle: turning on TV");
+ oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ if (result != HdmiControlManager.RESULT_SUCCESS) {
+ Slog.w(TAG, "Failed to complete One Touch Play. result=" + result);
+ sendUserControlPressedAndReleased(Constants.ADDR_TV,
+ HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
+ }
+ }
+ });
+ // Source device remains awake, to follow the toggled TV power state.
+ }
+ }
+ });
+ }
+
+ @ServiceThreadOnly
protected void onActiveSourceLost() {
// Nothing to do.
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 1a481b6..96303ce 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -16,10 +16,10 @@
package com.android.server.hdmi;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import com.android.server.hdmi.Constants.AudioCodec;
-import com.android.server.hdmi.Constants.CecVersion;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
@@ -696,9 +696,9 @@
return buildCommand(src, dest, Constants.MESSAGE_GIVE_FEATURES);
}
- static HdmiCecMessage buildReportFeatures(int src, @CecVersion int cecVersion,
- List<Integer> allDeviceTypes, int rcProfile, List<Integer> rcFeatures,
- List<Integer> deviceFeatures) {
+ static HdmiCecMessage buildReportFeatures(int src,
+ @HdmiControlManager.HdmiCecVersion int cecVersion, List<Integer> allDeviceTypes,
+ int rcProfile, List<Integer> rcFeatures, List<Integer> deviceFeatures) {
byte cecVersionByte = (byte) (cecVersion & 0xFF);
byte deviceTypes = 0;
for (Integer deviceType : allDeviceTypes) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index baed9cc..d2a2d72 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -201,7 +201,7 @@
addValidationInfo(
Constants.MESSAGE_REPORT_POWER_STATUS,
new OneByteRangeValidator(0x00, 0x03),
- DEST_DIRECT);
+ DEST_DIRECT | DEST_BROADCAST);
// Messages for the General Protocol.
addValidationInfo(Constants.MESSAGE_FEATURE_ABORT,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java b/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java
new file mode 100644
index 0000000..c4dadaa
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.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.hdmi;
+
+import static com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+import android.hardware.hdmi.HdmiControlManager;
+
+/**
+ * Storage of HDMI-CEC power status and controls possible cases where power status changes must also
+ * broadcast {@code <Report Power Status>} messages.
+ *
+ * All HDMI-CEC related power status changes should be done through this class.
+ */
+class HdmiCecPowerStatusController {
+
+ private final HdmiControlService mHdmiControlService;
+
+ private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+
+ HdmiCecPowerStatusController(HdmiControlService hdmiControlService) {
+ mHdmiControlService = hdmiControlService;
+ }
+
+ int getPowerStatus() {
+ return mPowerStatus;
+ }
+
+ boolean isPowerStatusOn() {
+ return mPowerStatus == HdmiControlManager.POWER_STATUS_ON;
+ }
+
+ boolean isPowerStatusStandby() {
+ return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
+ }
+
+ boolean isPowerStatusTransientToOn() {
+ return mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
+ }
+
+ boolean isPowerStatusTransientToStandby() {
+ return mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
+ }
+
+ @ServiceThreadOnly
+ void setPowerStatus(int powerStatus) {
+ setPowerStatus(powerStatus, true);
+ }
+
+ @ServiceThreadOnly
+ void setPowerStatus(int powerStatus, boolean sendPowerStatusUpdate) {
+ if (powerStatus == mPowerStatus) {
+ return;
+ }
+
+ mPowerStatus = powerStatus;
+ if (sendPowerStatusUpdate
+ && mHdmiControlService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+ sendReportPowerStatus(mPowerStatus);
+ }
+ }
+
+ private void sendReportPowerStatus(int powerStatus) {
+ for (HdmiCecLocalDevice localDevice : mHdmiControlService.getAllLocalDevices()) {
+ mHdmiControlService.sendCecCommand(
+ HdmiCecMessageBuilder.buildReportPowerStatus(localDevice.mAddress,
+ Constants.ADDR_BROADCAST, powerStatus));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index dcd6c02..01ec3b4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -350,8 +350,8 @@
private HdmiCecMessageValidator mMessageValidator;
- @ServiceThreadOnly
- private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+ private final HdmiCecPowerStatusController mPowerStatusController =
+ new HdmiCecPowerStatusController(this);
@ServiceThreadOnly
private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
@@ -530,7 +530,8 @@
mIoThread.start();
mIoLooper = mIoThread.getLooper();
}
- mPowerStatus = getInitialPowerStatus();
+
+ mPowerStatusController.setPowerStatus(getInitialPowerStatus());
mProhibitMode = false;
mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
mHdmiCecVolumeControlEnabled = readBooleanSetting(
@@ -673,10 +674,12 @@
* Updates the power status once the initialization of local devices is complete.
*/
private void updatePowerStatusOnInitializeCecComplete() {
- if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
- mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
- } else if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
- mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+ if (mPowerStatusController.isPowerStatusTransientToOn()) {
+ mHandler.post(() -> mPowerStatusController.setPowerStatus(
+ HdmiControlManager.POWER_STATUS_ON));
+ } else if (mPowerStatusController.isPowerStatusTransientToStandby()) {
+ mHandler.post(() -> mPowerStatusController.setPowerStatus(
+ HdmiControlManager.POWER_STATUS_STANDBY));
}
}
@@ -1686,7 +1689,7 @@
public void oneTouchPlay(final IHdmiControlCallback callback) {
enforceAccessPermission();
int pid = Binder.getCallingPid();
- Slog.d(TAG, "Proccess pid: " + pid + " is calling oneTouchPlay.");
+ Slog.d(TAG, "Process pid: " + pid + " is calling oneTouchPlay.");
runOnServiceThread(new Runnable() {
@Override
public void run() {
@@ -1696,6 +1699,19 @@
}
@Override
+ public void toggleAndFollowTvPower() {
+ enforceAccessPermission();
+ int pid = Binder.getCallingPid();
+ Slog.d(TAG, "Process pid: " + pid + " is calling toggleAndFollowTvPower.");
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ HdmiControlService.this.toggleAndFollowTvPower();
+ }
+ });
+ }
+
+ @Override
public void queryDisplayStatus(final IHdmiControlCallback callback) {
enforceAccessPermission();
runOnServiceThread(new Runnable() {
@@ -2191,7 +2207,7 @@
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println("mProhibitMode: " + mProhibitMode);
- pw.println("mPowerStatus: " + mPowerStatus);
+ pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
// System settings
pw.println("System_settings:");
@@ -2362,7 +2378,23 @@
}
@ServiceThreadOnly
- private void queryDisplayStatus(final IHdmiControlCallback callback) {
+ @VisibleForTesting
+ protected void toggleAndFollowTvPower() {
+ assertRunOnServiceThread();
+ HdmiCecLocalDeviceSource source = playback();
+ if (source == null) {
+ source = audioSystem();
+ }
+
+ if (source == null) {
+ Slog.w(TAG, "Local source device not available");
+ return;
+ }
+ source.toggleAndFollowTvPower();
+ }
+
+ @ServiceThreadOnly
+ protected void queryDisplayStatus(final IHdmiControlCallback callback) {
assertRunOnServiceThread();
if (!mAddressAllocated) {
mDisplayStatusCallback = callback;
@@ -2371,9 +2403,13 @@
return;
}
- HdmiCecLocalDevicePlayback source = playback();
+ HdmiCecLocalDeviceSource source = playback();
if (source == null) {
- Slog.w(TAG, "Local playback device not available");
+ source = audioSystem();
+ }
+
+ if (source == null) {
+ Slog.w(TAG, "Local source device not available");
invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
return;
}
@@ -2831,34 +2867,34 @@
@ServiceThreadOnly
int getPowerStatus() {
assertRunOnServiceThread();
- return mPowerStatus;
+ return mPowerStatusController.getPowerStatus();
}
@ServiceThreadOnly
@VisibleForTesting
void setPowerStatus(int powerStatus) {
assertRunOnServiceThread();
- mPowerStatus = powerStatus;
+ mPowerStatusController.setPowerStatus(powerStatus);
}
@ServiceThreadOnly
boolean isPowerOnOrTransient() {
assertRunOnServiceThread();
- return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
- || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
+ return mPowerStatusController.isPowerStatusOn()
+ || mPowerStatusController.isPowerStatusTransientToOn();
}
@ServiceThreadOnly
boolean isPowerStandbyOrTransient() {
assertRunOnServiceThread();
- return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
- || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
+ return mPowerStatusController.isPowerStatusStandby()
+ || mPowerStatusController.isPowerStatusTransientToStandby();
}
@ServiceThreadOnly
boolean isPowerStandby() {
assertRunOnServiceThread();
- return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
+ return mPowerStatusController.isPowerStatusStandby();
}
@ServiceThreadOnly
@@ -2895,7 +2931,8 @@
@ServiceThreadOnly
private void onWakeUp(@WakeReason final int wakeUpAction) {
assertRunOnServiceThread();
- mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
+ mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
+ false);
if (mCecController != null) {
if (mHdmiControlEnabled) {
int startReason = -1;
@@ -2926,14 +2963,15 @@
@VisibleForTesting
protected void onStandby(final int standbyAction) {
assertRunOnServiceThread();
- mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
+ mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY,
+ false);
invokeVendorCommandListenersOnControlStateChanged(false,
HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
if (!isStandbyMessageReceived() && !canGoToStandby()) {
- mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+ mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
for (HdmiCecLocalDevice device : devices) {
device.onStandby(mStandbyMessageReceived, standbyAction);
}
@@ -3013,10 +3051,10 @@
assertRunOnServiceThread();
Slog.v(TAG, "onStandbyCompleted");
- if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
+ if (!mPowerStatusController.isPowerStatusTransientToStandby()) {
return;
}
- mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+ mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
device.onStandby(mStandbyMessageReceived, standbyAction);
}
diff --git a/services/core/java/com/android/server/hdmi/cec_key_handling.md b/services/core/java/com/android/server/hdmi/cec_key_handling.md
index d150dd3..1b41a67 100644
--- a/services/core/java/com/android/server/hdmi/cec_key_handling.md
+++ b/services/core/java/com/android/server/hdmi/cec_key_handling.md
@@ -9,13 +9,13 @@
The general action for key handling is described in the table
-| Android Key | TV Panel | OTT | Soundbar |
-| ----------- | ----------------- | ------------------- | ------------------- |
-| general | Send to active source | handle on device | handle on device |
-| POWER | Toggle the device power state | Toggle the TV power state | Toggle the TV power state |
-| TV_POWER | Toggle the device power state | Toggle the TV power state | Toggle the TV power state |
-| HOME | Turn on TV, Set active Source to TV, go to home screen | OTP, and go to home screen | OTP, and go to home screen |
-| volume keys | Handle on device or send to soundbar | Send to TV or soundbar | Handle on device or send to TV |
+| Android Key | TV Panel | OTT | Soundbar |
+| ----------- | ----------------- | ------------------- | ------------------- |
+| general | Send to active source | handle on device | handle on device |
+| POWER | Toggle the device power state | Toggle the OTT power state, TV power state follows | Toggle the soundbar power state, TV power state follows|
+| TV_POWER | Toggle the device power state | Toggle the TV power state, OTT power state follows | Toggle the TV power state, soundbar power state follows|
+| HOME | Turn on TV, Set active Source to TV, go to home screen | OTP, and go to home screen | OTP, and go to home screen |
+| volume keys | Handle on device or send to soundbar | Send to TV or soundbar | Handle on device or send to TV |
Special cases and flags for each key are described below
diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java
index 8c1afab..7d3ccbf 100644
--- a/services/core/java/com/android/server/location/LocationShellCommand.java
+++ b/services/core/java/com/android/server/location/LocationShellCommand.java
@@ -16,9 +16,10 @@
package com.android.server.location;
-import android.os.BasicShellCommandHandler;
import android.os.UserHandle;
+import com.android.modules.utils.BasicShellCommandHandler;
+
import java.io.PrintWriter;
import java.util.Objects;
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index 4a85342..5a90fa7 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -322,12 +322,28 @@
@Override
protected boolean isActive(GeofenceRegistration registration) {
- CallerIdentity identity = registration.getIdentity();
- return registration.isPermitted()
- && (identity.isSystem() || mUserInfoHelper.isCurrentUserId(identity.getUserId()))
- && mSettingsHelper.isLocationEnabled(identity.getUserId())
- && !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
- identity.getPackageName());
+ return registration.isPermitted() && isActive(registration.getIdentity());
+ }
+
+ private boolean isActive(CallerIdentity identity) {
+ if (identity.isSystemServer()) {
+ if (!mSettingsHelper.isLocationEnabled(mUserInfoHelper.getCurrentUserId())) {
+ return false;
+ }
+ } else {
+ if (!mSettingsHelper.isLocationEnabled(identity.getUserId())) {
+ return false;
+ }
+ if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+ return false;
+ }
+ if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
+ identity.getPackageName())) {
+ return false;
+ }
+ }
+
+ return true;
}
@Override
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index e68f595..7e848e0 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -266,11 +266,30 @@
CallerIdentity identity = registration.getIdentity();
return registration.isPermitted()
&& (registration.isForeground() || isBackgroundRestrictionExempt(identity))
- && (identity.isSystem() || mUserInfoHelper.isCurrentUserId(identity.getUserId()))
- && mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER,
- identity.getUserId())
- && !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
- identity.getPackageName());
+ && isActive(identity);
+ }
+
+ private boolean isActive(CallerIdentity identity) {
+ if (identity.isSystemServer()) {
+ if (!mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER,
+ mUserInfoHelper.getCurrentUserId())) {
+ return false;
+ }
+ } else {
+ if (!mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER,
+ identity.getUserId())) {
+ return false;
+ }
+ if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+ return false;
+ }
+ if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
+ identity.getPackageName())) {
+ return false;
+ }
+ }
+
+ return true;
}
private boolean isBackgroundRestrictionExempt(CallerIdentity identity) {
diff --git a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
index 56dd92a..d4a8fbd 100644
--- a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
@@ -88,11 +88,6 @@
return mUserManager;
}
- /**
- * Returns an array of running user ids. This will include all running users, and will also
- * include any profiles of the running users. The caller must never mutate the returned
- * array.
- */
@Override
public int[] getRunningUserIds() {
IActivityManager activityManager = getActivityManager();
@@ -110,10 +105,6 @@
}
}
- /**
- * Returns true if the given user id is either the current user or a profile of the current
- * user.
- */
@Override
public boolean isCurrentUserId(@UserIdInt int userId) {
ActivityManagerInternal activityManagerInternal = getActivityManagerInternal();
@@ -130,6 +121,21 @@
}
@Override
+ public @UserIdInt int getCurrentUserId() {
+ ActivityManagerInternal activityManagerInternal = getActivityManagerInternal();
+ if (activityManagerInternal != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return activityManagerInternal.getCurrentUserId();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } else {
+ return UserHandle.USER_NULL;
+ }
+ }
+
+ @Override
protected int[] getProfileIds(@UserIdInt int userId) {
UserManager userManager = getUserManager();
diff --git a/services/core/java/com/android/server/location/injector/UserInfoHelper.java b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
index 3b7e3b4..0fcc1ec 100644
--- a/services/core/java/com/android/server/location/injector/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
@@ -132,6 +132,12 @@
*/
public abstract boolean isCurrentUserId(@UserIdInt int userId);
+ /**
+ * Returns the current user id. Where possible, prefer to use {@link #isCurrentUserId(int)}
+ * instead, as that method has more flexibility.
+ */
+ public abstract @UserIdInt int getCurrentUserId();
+
protected abstract int[] getProfileIds(@UserIdInt int userId);
/**
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index eca6070..c5d7f2c 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -1469,18 +1469,9 @@
public @Nullable Location getLastLocation(CallerIdentity identity,
@PermissionLevel int permissionLevel, boolean ignoreLocationSettings) {
- if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
- identity.getPackageName())) {
+ if (!isActive(ignoreLocationSettings, identity)) {
return null;
}
- if (!ignoreLocationSettings) {
- if (!isEnabled(identity.getUserId())) {
- return null;
- }
- if (!identity.isSystem() && !mUserHelper.isCurrentUserId(identity.getUserId())) {
- return null;
- }
- }
// lastly - note app ops
if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
@@ -1905,20 +1896,16 @@
Preconditions.checkState(Thread.holdsLock(mLock));
}
- CallerIdentity identity = registration.getIdentity();
-
if (!registration.isPermitted()) {
return false;
}
- if (!registration.getRequest().isLocationSettingsIgnored()) {
- if (!isEnabled(identity.getUserId())) {
- return false;
- }
- if (!identity.isSystem() && !mUserHelper.isCurrentUserId(identity.getUserId())) {
- return false;
- }
+ boolean locationSettingsIgnored = registration.getRequest().isLocationSettingsIgnored();
+ if (!isActive(locationSettingsIgnored, registration.getIdentity())) {
+ return false;
+ }
+ if (!locationSettingsIgnored) {
switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
case LOCATION_MODE_FOREGROUND_ONLY:
if (!registration.isForeground()) {
@@ -1944,8 +1931,32 @@
}
}
- return !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
- identity.getPackageName());
+ return true;
+ }
+
+ private boolean isActive(boolean locationSettingsIgnored, CallerIdentity identity) {
+ if (identity.isSystemServer()) {
+ if (!locationSettingsIgnored) {
+ if (!isEnabled(mUserHelper.getCurrentUserId())) {
+ return false;
+ }
+ }
+ } else {
+ if (!locationSettingsIgnored) {
+ if (!isEnabled(identity.getUserId())) {
+ return false;
+ }
+ if (!mUserHelper.isCurrentUserId(identity.getUserId())) {
+ return false;
+ }
+ }
+ if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
+ identity.getPackageName())) {
+ return false;
+ }
+ }
+
+ return true;
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index dfeb682..b1289dd 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7164,6 +7164,16 @@
return true;
}
+ // Suppressed since it's a non-interruptive update to a bubble-suppressed notification
+ final boolean isBubbleOrOverflowed = record.canBubble() && (record.isFlagBubbleRemoved()
+ || record.getNotification().isBubbleNotification());
+ if (record.isUpdate && !record.isInterruptive() && isBubbleOrOverflowed
+ && record.getNotification().getBubbleMetadata() != null) {
+ if (record.getNotification().getBubbleMetadata().isNotificationSuppressed()) {
+ return true;
+ }
+ }
+
return false;
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index de869079..c4a23f9 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -774,6 +774,9 @@
throw new IllegalArgumentException(
"To query by locus ID, package name must also be set");
}
+ if ((query.getQueryFlags() & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) {
+ ensureStrictAccessShortcutsPermission(callingPackage);
+ }
// TODO(b/29399275): Eclipse compiler requires explicit List<ShortcutInfo> cast below.
return new ParceledListSlice<>((List<ShortcutInfo>)
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 06f8ca3..8bc524c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -103,7 +103,6 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
-import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
@@ -246,7 +245,6 @@
import android.graphics.Bitmap;
import android.hardware.display.DisplayManager;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -339,7 +337,6 @@
import com.android.internal.os.Zygote;
import com.android.internal.telephony.CarrierAppUtils;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
@@ -3842,7 +3839,7 @@
// are all flushed. Not really needed, but keeps things nice and
// tidy.
t.traceBegin("GC");
- Runtime.getRuntime().gc();
+ VMRuntime.getRuntime().requestConcurrentGC();
t.traceEnd();
// The initial scanning above does many calls into installd while
@@ -11592,22 +11589,6 @@
}
pkgSetting.signatures.mSigningDetails = reconciledPkg.signingDetails;
- if (!pkg.getAdoptPermissions().isEmpty()) {
- // This package wants to adopt ownership of permissions from
- // another package.
- for (int i = pkg.getAdoptPermissions().size() - 1; i >= 0; i--) {
- final String origName = pkg.getAdoptPermissions().get(i);
- final PackageSetting orig = mSettings.getPackageLPr(origName);
- if (orig != null) {
- if (verifyPackageUpdateLPr(orig, pkg)) {
- Slog.i(TAG, "Adopting permissions from " + origName + " to "
- + pkg.getPackageName());
- mPermissionManager.transferPermissions(origName, pkg.getPackageName());
- }
- }
- }
- }
-
if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
final String codePathString = changedAbiCodePath.get(i);
@@ -12854,27 +12835,6 @@
reconciledPkg.prepareResult != null && reconciledPkg.prepareResult.replace;
mAppsFilter.addPackage(pkgSetting, isReplace);
- // Don't allow ephemeral applications to define new permissions groups.
- if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
- Slog.w(TAG, "Permission groups from package " + pkg.getPackageName()
- + " ignored: instant apps cannot define new permission groups.");
- } else {
- mPermissionManager.addAllPermissionGroups(pkg, chatty);
- }
-
- // If a permission has had its defining app changed, or it has had its protection
- // upgraded, we need to revoke apps that hold it
- final List<String> permissionsWithChangedDefinition;
- // Don't allow ephemeral applications to define new permissions.
- if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
- permissionsWithChangedDefinition = null;
- Slog.w(TAG, "Permissions from package " + pkg.getPackageName()
- + " ignored: instant apps cannot define new permissions.");
- } else {
- permissionsWithChangedDefinition =
- mPermissionManager.addAllPermissions(pkg, chatty);
- }
-
int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
StringBuilder r = null;
int i;
@@ -12901,31 +12861,7 @@
}
}
- boolean hasOldPkg = oldPkg != null;
- boolean hasPermissionDefinitionChanges =
- !CollectionUtils.isEmpty(permissionsWithChangedDefinition);
- if (hasOldPkg || hasPermissionDefinitionChanges) {
- // We need to call revokeRuntimePermissionsIfGroupChanged async as permission
- // revoke callbacks from this method might need to kill apps which need the
- // mPackages lock on a different thread. This would dead lock.
- //
- // Hence create a copy of all package names and pass it into
- // revokeRuntimePermissionsIfGroupChanged. Only for those permissions might get
- // revoked. If a new package is added before the async code runs the permission
- // won't be granted yet, hence new packages are no problem.
- final ArrayList<String> allPackageNames = new ArrayList<>(mPackages.keySet());
-
- AsyncTask.execute(() -> {
- if (hasOldPkg) {
- mPermissionManager.revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg,
- allPackageNames);
- }
- if (hasPermissionDefinitionChanges) {
- mPermissionManager.revokeRuntimePermissionsIfPermissionDefinitionChanged(
- permissionsWithChangedDefinition, allPackageNames);
- }
- });
- }
+ mPermissionManager.onPackageAdded(pkg, (scanFlags & SCAN_AS_INSTANT_APP) != 0, oldPkg);
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -13024,7 +12960,7 @@
}
}
- void removePackageLI(String packageName, boolean chatty) {
+ private void removePackageLI(String packageName, boolean chatty) {
if (DEBUG_INSTALL) {
if (chatty)
Log.d(TAG, "Removing package " + packageName);
@@ -13039,9 +12975,9 @@
}
}
- void cleanPackageDataStructuresLILPw(AndroidPackage pkg, boolean chatty) {
+ private void cleanPackageDataStructuresLILPw(AndroidPackage pkg, boolean chatty) {
mComponentResolver.removeAllComponents(pkg, chatty);
- mPermissionManager.removeAllPermissions(pkg, chatty);
+ mPermissionManager.onPackageRemoved(pkg);
final int instrumentationSize = ArrayUtils.size(pkg.getInstrumentations());
StringBuilder r = null;
@@ -17864,6 +17800,17 @@
}
}
+ /*
+ * Cannot properly check CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS using CompatChanges
+ * as this only works for packages that are installed
+ *
+ * TODO: Move logic for permission group compatibility into PermissionManagerService
+ */
+ @SuppressWarnings("AndroidFrameworkCompatChange")
+ private static boolean cannotInstallWithBadPermissionGroups(ParsedPackage parsedPackage) {
+ return parsedPackage.getTargetSdkVersion() >= Build.VERSION_CODES.S;
+ }
+
@GuardedBy("mInstallLock")
private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
throws PrepareFailure {
@@ -17906,7 +17853,7 @@
| (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
- ParsedPackage parsedPackage;
+ final ParsedPackage parsedPackage;
try (PackageParser2 pp = new PackageParser2(mSeparateProcesses, false, mMetrics, null,
mPackageParserCallback)) {
parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
@@ -18022,15 +17969,6 @@
}
}
- /*
- * Cannot properly check CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS using CompatChanges
- * as this only works for packages that are installed
- *
- * TODO: Move logic for permission group compatibility into PermissionManagerService
- */
- boolean cannotInstallWithBadPermissionGroups =
- parsedPackage.getTargetSdkVersion() >= Build.VERSION_CODES.S;
-
PackageSetting ps = mSettings.mPackages.get(pkgName);
if (ps != null) {
if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
@@ -18088,7 +18026,8 @@
parsedPackage.getPermissionGroups().get(groupNum);
final PermissionGroupInfo sourceGroup = getPermissionGroupInfo(group.getName(), 0);
- if (sourceGroup != null && cannotInstallWithBadPermissionGroups) {
+ if (sourceGroup != null
+ && cannotInstallWithBadPermissionGroups(parsedPackage)) {
final String sourcePackageName = sourceGroup.packageName;
if ((replace || !parsedPackage.getPackageName().equals(sourcePackageName))
@@ -18163,7 +18102,8 @@
}
}
- if (perm.getGroup() != null && cannotInstallWithBadPermissionGroups) {
+ if (perm.getGroup() != null
+ && cannotInstallWithBadPermissionGroups(parsedPackage)) {
boolean isPermGroupDefinedByPackage = false;
for (int groupNum = 0; groupNum < numGroups; groupNum++) {
if (parsedPackage.getPermissionGroups().get(groupNum).getName()
@@ -19535,33 +19475,13 @@
if (outInfo != null) {
outInfo.removedAppId = removedAppId;
}
- if ((deletedPs.sharedUser == null || deletedPs.sharedUser.packages.size() == 0)
- && !isUpdatedSystemApp(deletedPs)) {
- mPermissionManager.removeAppIdStateTEMP(removedAppId);
+ final SharedUserSetting sus = deletedPs.getSharedUser();
+ List<AndroidPackage> sharedUserPkgs = sus != null ? sus.getPackages() : null;
+ if (sharedUserPkgs == null) {
+ sharedUserPkgs = Collections.emptyList();
}
- mPermissionManager.updatePermissions(deletedPs.name, null);
- if (deletedPs.sharedUser != null) {
- // Remove permissions associated with package. Since runtime
- // permissions are per user we have to kill the removed package
- // or packages running under the shared user of the removed
- // package if revoking the permissions requested only by the removed
- // package is successful and this causes a change in gids.
- boolean shouldKill = false;
- for (int userId : UserManagerService.getInstance().getUserIds()) {
- final int userIdToKill = mPermissionManager
- .revokeSharedUserPermissionsForDeletedPackageTEMP(deletedPs,
- userId);
- shouldKill |= userIdToKill != UserHandle.USER_NULL;
- }
- // If gids changed, kill all affected packages.
- if (shouldKill) {
- mHandler.post(() -> {
- // This has to happen with no lock held.
- killApplication(deletedPs.name, deletedPs.appId,
- KILL_APP_REASON_GIDS_CHANGED);
- });
- }
- }
+ mPermissionManager.onPackageStateRemoved(packageName, deletedPs.appId,
+ deletedPs.pkg, sharedUserPkgs);
clearPackagePreferredActivitiesLPw(
deletedPs.name, changedUsers, UserHandle.USER_ALL);
}
diff --git a/services/core/java/com/android/server/pm/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java
index 69721d0..804faa1 100644
--- a/services/core/java/com/android/server/pm/PreferredComponent.java
+++ b/services/core/java/com/android/server/pm/PreferredComponent.java
@@ -252,43 +252,6 @@
return numMatch == NS;
}
- public boolean sameSet(PreferredComponent pc) {
- if (mSetPackages == null || pc == null || pc.mSetPackages == null
- || !sameComponent(pc.mComponent)) {
- return false;
- }
- final int otherPackageCount = pc.mSetPackages.length;
- final int packageCount = mSetPackages.length;
- int numMatch = 0;
- for (int i = 0; i < otherPackageCount; i++) {
- boolean good = false;
- for (int j = 0; j < packageCount; j++) {
- if (mSetPackages[j].equals(pc.mSetPackages[j])
- && mSetClasses[j].equals(pc.mSetClasses[j])) {
- numMatch++;
- good = true;
- break;
- }
- }
- if (!good) {
- return false;
- }
- }
- return numMatch == packageCount;
- }
-
- /** Returns true if the preferred component represents the provided ComponentName. */
- private boolean sameComponent(ComponentName comp) {
- if (mComponent == null || comp == null) {
- return false;
- }
- if (mComponent.getPackageName().equals(comp.getPackageName())
- && mComponent.getClassName().equals(comp.getClassName())) {
- return true;
- }
- return false;
- }
-
public boolean isSuperset(List<ResolveInfo> query, boolean excludeSetupWizardPackage) {
if (mSetPackages == null) {
return query == null;
diff --git a/services/core/java/com/android/server/pm/PreferredIntentResolver.java b/services/core/java/com/android/server/pm/PreferredIntentResolver.java
index ff3df13..a261e29 100644
--- a/services/core/java/com/android/server/pm/PreferredIntentResolver.java
+++ b/services/core/java/com/android/server/pm/PreferredIntentResolver.java
@@ -22,7 +22,6 @@
import java.io.PrintWriter;
import com.android.server.IntentResolver;
-import java.util.ArrayList;
public class PreferredIntentResolver
extends IntentResolver<PreferredActivity, PreferredActivity> {
@@ -46,24 +45,4 @@
protected IntentFilter getIntentFilter(@NonNull PreferredActivity input) {
return input;
}
-
- public boolean shouldAddPreferredActivity(PreferredActivity pa) {
- ArrayList<PreferredActivity> pal = findFilters(pa);
- if (pal == null || pal.isEmpty()) {
- return true;
- }
- if (!pa.mPref.mAlways) {
- return false;
- }
- final int activityCount = pal.size();
- for (int i = 0; i < activityCount; i++) {
- PreferredActivity cur = pal.get(i);
- if (cur.mPref.mAlways
- && cur.mPref.mMatch == (pa.mPref.mMatch & IntentFilter.MATCH_CATEGORY_MASK)
- && cur.mPref.sameSet(pa.mPref)) {
- return false;
- }
- }
- return true;
- }
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9a5e05f..966090cb 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1312,7 +1312,8 @@
PreferredActivity pa = new PreferredActivity(parser);
if (pa.mPref.getParseError() == null) {
final PreferredIntentResolver resolver = editPreferredActivitiesLPw(userId);
- if (resolver.shouldAddPreferredActivity(pa)) {
+ ArrayList<PreferredActivity> pal = resolver.findFilters(pa);
+ if (pal == null || pal.size() == 0 || pa.mPref.mAlways) {
resolver.addFilter(pa);
}
} else {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index ed51039..2d77182 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2837,10 +2837,14 @@
int queryFlags, int userId, int callingPid, int callingUid) {
final ArrayList<ShortcutInfo> ret = new ArrayList<>();
- final boolean cloneKeyFieldOnly =
- ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0);
- final int cloneFlag = cloneKeyFieldOnly ? ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO
- : ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER;
+ int flags = ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER;
+ if ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0) {
+ flags = ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
+ } else if ((queryFlags & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) {
+ flags &= ~ShortcutInfo.CLONE_REMOVE_PERSON;
+ }
+ final int cloneFlag = flags;
+
if (packageName == null) {
shortcutIds = null; // LauncherAppsService already threw for it though.
}
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 0245b28..687e96c 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -399,8 +399,7 @@
@NonNull
public static Permission createOrUpdate(@Nullable Permission permission,
@NonNull PermissionInfo permissionInfo, @NonNull AndroidPackage pkg,
- @NonNull Collection<Permission> permissionTrees, boolean isOverridingSystemPermission,
- boolean chatty) {
+ @NonNull Collection<Permission> permissionTrees, boolean isOverridingSystemPermission) {
// Allow system apps to redefine non-system permissions
boolean ownerChanged = false;
if (permission != null && !Objects.equals(permission.mPermissionInfo.packageName,
@@ -437,7 +436,7 @@
permission.mPermissionInfo = permissionInfo;
permission.mReconciled = true;
permission.mUid = pkg.getUid();
- if (chatty) {
+ if (PackageManagerService.DEBUG_PACKAGE_SCANNING) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -456,7 +455,7 @@
+ permissionInfo.packageName + " ignored: original from "
+ permission.mPermissionInfo.packageName);
}
- } else if (chatty) {
+ } else if (PackageManagerService.DEBUG_PACKAGE_SCANNING) {
if (r == null) {
r = new StringBuilder(256);
} else {
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 6c03a28..1b35d29 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -93,6 +93,7 @@
import android.content.pm.parsing.component.ParsedPermissionGroup;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.metrics.LogMaker;
+import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.Debug;
@@ -133,6 +134,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IntPair;
import com.android.internal.util.Preconditions;
@@ -145,7 +147,6 @@
import com.android.server.pm.ApexManager;
import com.android.server.pm.PackageManagerServiceUtils;
import com.android.server.pm.PackageSetting;
-import com.android.server.pm.SharedUserSetting;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.parsing.PackageInfoUtils;
@@ -2363,14 +2364,9 @@
*
* @param newPackage The new package that was installed
* @param oldPackage The old package that was updated
- * @param allPackageNames All package names
- * @param permissionCallback Callback for permission changed
*/
- private void revokeRuntimePermissionsIfGroupChanged(
- @NonNull AndroidPackage newPackage,
- @NonNull AndroidPackage oldPackage,
- @NonNull ArrayList<String> allPackageNames,
- @NonNull PermissionCallback permissionCallback) {
+ private void revokeRuntimePermissionsIfGroupChangedInternal(@NonNull AndroidPackage newPackage,
+ @NonNull AndroidPackage oldPackage) {
final int numOldPackagePermissions = ArrayUtils.size(oldPackage.getPermissions());
final ArrayMap<String, String> oldPermissionNameToGroupName
= new ArrayMap<>(numOldPackagePermissions);
@@ -2403,13 +2399,9 @@
if (newPermissionGroupName != null
&& !newPermissionGroupName.equals(oldPermissionGroupName)) {
final int[] userIds = mUserManagerInt.getUserIds();
- final int numUserIds = userIds.length;
- for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) {
- final int userId = userIds[userIdNum];
-
- final int numPackages = allPackageNames.size();
- for (int packageNum = 0; packageNum < numPackages; packageNum++) {
- final String packageName = allPackageNames.get(packageNum);
+ mPackageManagerInt.forEachPackage(pkg -> {
+ final String packageName = pkg.getPackageName();
+ for (final int userId : userIds) {
final int permissionState = checkPermission(permissionName, packageName,
userId);
if (permissionState == PackageManager.PERMISSION_GRANTED) {
@@ -2422,14 +2414,15 @@
try {
revokeRuntimePermissionInternal(permissionName, packageName,
- false, callingUid, userId, null, permissionCallback);
+ false, callingUid, userId, null,
+ mDefaultPermissionCallback);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Could not revoke " + permissionName + " from "
+ packageName, e);
}
}
}
- }
+ });
}
}
}
@@ -2440,18 +2433,11 @@
* granted permissions must be revoked.
*
* @param permissionsToRevoke A list of permission names to revoke
- * @param allPackageNames All package names
- * @param permissionCallback Callback for permission changed
*/
- private void revokeRuntimePermissionsIfPermissionDefinitionChanged(
- @NonNull List<String> permissionsToRevoke,
- @NonNull ArrayList<String> allPackageNames,
- @NonNull PermissionCallback permissionCallback) {
-
+ private void revokeRuntimePermissionsIfPermissionDefinitionChangedInternal(
+ @NonNull List<String> permissionsToRevoke) {
final int[] userIds = mUserManagerInt.getUserIds();
final int numPermissions = permissionsToRevoke.size();
- final int numUserIds = userIds.length;
- final int numPackages = allPackageNames.size();
final int callingUid = Binder.getCallingUid();
for (int permNum = 0; permNum < numPermissions; permNum++) {
@@ -2462,15 +2448,14 @@
continue;
}
}
- for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) {
- final int userId = userIds[userIdNum];
- for (int packageNum = 0; packageNum < numPackages; packageNum++) {
- final String packageName = allPackageNames.get(packageNum);
- final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
- if (uid < Process.FIRST_APPLICATION_UID) {
- // do not revoke from system apps
- continue;
- }
+ mPackageManagerInt.forEachPackage(pkg -> {
+ final String packageName = pkg.getPackageName();
+ final int appId = pkg.getUid();
+ if (appId < Process.FIRST_APPLICATION_UID) {
+ // do not revoke from system apps
+ return;
+ }
+ for (final int userId : userIds) {
final int permissionState = checkPermissionImpl(permName, packageName,
userId);
final int flags = getPermissionFlags(permName, packageName, userId);
@@ -2480,6 +2465,7 @@
| FLAG_PERMISSION_GRANTED_BY_ROLE;
if (permissionState == PackageManager.PERMISSION_GRANTED
&& (flags & flagMask) == 0) {
+ final int uid = UserHandle.getUid(userId, appId);
EventLog.writeEvent(0x534e4554, "154505240", uid,
"Revoking permission " + permName + " from package "
+ packageName + " due to definition change");
@@ -2490,18 +2476,18 @@
+ packageName + " due to definition change");
try {
revokeRuntimePermissionInternal(permName, packageName,
- false, callingUid, userId, null, permissionCallback);
+ false, callingUid, userId, null, mDefaultPermissionCallback);
} catch (Exception e) {
Slog.e(TAG, "Could not revoke " + permName + " from "
+ packageName, e);
}
}
}
- }
+ });
}
}
- private List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) {
+ private List<String> addAllPermissionsInternal(@NonNull AndroidPackage pkg) {
final int N = ArrayUtils.size(pkg.getPermissions());
ArrayList<String> definitionChangedPermissions = new ArrayList<>();
for (int i=0; i<N; i++) {
@@ -2539,7 +2525,7 @@
synchronized (mLock) {
final Permission permission = Permission.createOrUpdate(oldPermission,
permissionInfo, pkg, mRegistry.getPermissionTrees(),
- isOverridingSystemPermission, chatty);
+ isOverridingSystemPermission);
if (p.isTree()) {
mRegistry.addPermissionTree(permission);
} else {
@@ -2557,7 +2543,7 @@
return definitionChangedPermissions;
}
- private void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) {
+ private void addAllPermissionGroupsInternal(@NonNull AndroidPackage pkg) {
synchronized (mLock) {
final int N = ArrayUtils.size(pkg.getPermissionGroups());
StringBuilder r = null;
@@ -2568,7 +2554,7 @@
final boolean isPackageUpdate = pg.getPackageName().equals(curPackageName);
if (cur == null || isPackageUpdate) {
mRegistry.addPermissionGroup(pg);
- if (chatty && DEBUG_PACKAGE_SCANNING) {
+ if (DEBUG_PACKAGE_SCANNING) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -2583,7 +2569,7 @@
Slog.w(TAG, "Permission group " + pg.getName() + " from package "
+ pg.getPackageName() + " ignored: original from "
+ cur.getPackageName());
- if (chatty && DEBUG_PACKAGE_SCANNING) {
+ if (DEBUG_PACKAGE_SCANNING) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -2600,7 +2586,7 @@
}
}
- private void removeAllPermissions(AndroidPackage pkg, boolean chatty) {
+ private void removeAllPermissionsInternal(@NonNull AndroidPackage pkg) {
synchronized (mLock) {
int N = ArrayUtils.size(pkg.getPermissions());
StringBuilder r = null;
@@ -2612,7 +2598,7 @@
}
if (bp != null && bp.isPermission(p)) {
bp.setPermissionInfo(null);
- if (DEBUG_REMOVE && chatty) {
+ if (DEBUG_REMOVE) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -3990,34 +3976,30 @@
}
@UserIdInt
- private int revokeSharedUserPermissionsForDeletedPackage(@NonNull PackageSetting deletedPs,
+ private int revokeSharedUserPermissionsForDeletedPackageInternal(
+ @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
@UserIdInt int userId) {
- if ((deletedPs == null) || (deletedPs.pkg == null)) {
+ if (pkg == null) {
Slog.i(TAG, "Trying to update info for null package. Just ignoring");
return UserHandle.USER_NULL;
}
- SharedUserSetting sus = deletedPs.getSharedUser();
-
- // No sharedUserId
- if (sus == null) {
+ // No shared user packages
+ if (sharedUserPkgs.isEmpty()) {
return UserHandle.USER_NULL;
}
int affectedUserId = UserHandle.USER_NULL;
// Update permissions
- for (String eachPerm : deletedPs.pkg.getRequestedPermissions()) {
+ for (String eachPerm : pkg.getRequestedPermissions()) {
// Check if another package in the shared user needs the permission.
boolean used = false;
- final List<AndroidPackage> pkgs = sus.getPackages();
- if (pkgs != null) {
- for (AndroidPackage pkg : pkgs) {
- if (pkg != null
- && !pkg.getPackageName().equals(deletedPs.pkg.getPackageName())
- && pkg.getRequestedPermissions().contains(eachPerm)) {
- used = true;
- break;
- }
+ for (AndroidPackage sharedUserpkg : sharedUserPkgs) {
+ if (sharedUserpkg != null
+ && !sharedUserpkg.getPackageName().equals(pkg.getPackageName())
+ && sharedUserpkg.getRequestedPermissions().contains(eachPerm)) {
+ used = true;
+ break;
}
}
if (used) {
@@ -4025,7 +4007,7 @@
}
PackageSetting disabledPs = mPackageManagerInt.getDisabledSystemPackage(
- deletedPs.pkg.getPackageName());
+ pkg.getPackageName());
// If the package is shadowing is a disabled system package,
// do not drop permissions that the shadowed package requests.
@@ -4043,9 +4025,9 @@
}
synchronized (mLock) {
- UidPermissionState uidState = getUidStateLocked(deletedPs.pkg, userId);
+ UidPermissionState uidState = getUidStateLocked(pkg, userId);
if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + deletedPs.pkg.getPackageName()
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
+ " and user " + userId);
continue;
}
@@ -4848,10 +4830,113 @@
}
}
- private void transferPermissions(@NonNull String oldPackageName,
- @NonNull String newPackageName) {
- synchronized (mLock) {
- mRegistry.transferPermissions(oldPackageName, newPackageName);
+ private void onPackageAddedInternal(@NonNull AndroidPackage pkg, boolean isInstantApp,
+ @Nullable AndroidPackage oldPkg) {
+ if (!pkg.getAdoptPermissions().isEmpty()) {
+ // This package wants to adopt ownership of permissions from
+ // another package.
+ for (int i = pkg.getAdoptPermissions().size() - 1; i >= 0; i--) {
+ final String origName = pkg.getAdoptPermissions().get(i);
+ if (canAdoptPermissionsInternal(origName, pkg)) {
+ Slog.i(TAG, "Adopting permissions from " + origName + " to "
+ + pkg.getPackageName());
+ synchronized (mLock) {
+ mRegistry.transferPermissions(origName, pkg.getPackageName());
+ }
+ }
+ }
+ }
+
+ // Don't allow ephemeral applications to define new permissions groups.
+ if (isInstantApp) {
+ Slog.w(TAG, "Permission groups from package " + pkg.getPackageName()
+ + " ignored: instant apps cannot define new permission groups.");
+ } else {
+ addAllPermissionGroupsInternal(pkg);
+ }
+
+ // If a permission has had its defining app changed, or it has had its protection
+ // upgraded, we need to revoke apps that hold it
+ final List<String> permissionsWithChangedDefinition;
+ // Don't allow ephemeral applications to define new permissions.
+ if (isInstantApp) {
+ permissionsWithChangedDefinition = null;
+ Slog.w(TAG, "Permissions from package " + pkg.getPackageName()
+ + " ignored: instant apps cannot define new permissions.");
+ } else {
+ permissionsWithChangedDefinition = addAllPermissionsInternal(pkg);
+ }
+
+ boolean hasOldPkg = oldPkg != null;
+ boolean hasPermissionDefinitionChanges =
+ !CollectionUtils.isEmpty(permissionsWithChangedDefinition);
+ if (hasOldPkg || hasPermissionDefinitionChanges) {
+ // We need to call revokeRuntimePermissionsIfGroupChanged async as permission
+ // revoke callbacks from this method might need to kill apps which need the
+ // mPackages lock on a different thread. This would dead lock.
+ AsyncTask.execute(() -> {
+ if (hasOldPkg) {
+ revokeRuntimePermissionsIfGroupChangedInternal(pkg, oldPkg);
+ }
+ if (hasPermissionDefinitionChanges) {
+ revokeRuntimePermissionsIfPermissionDefinitionChangedInternal(
+ permissionsWithChangedDefinition);
+ }
+ });
+ }
+ }
+
+ private boolean canAdoptPermissionsInternal(@NonNull String oldPackageName,
+ @NonNull AndroidPackage newPkg) {
+ final PackageSetting oldPs = mPackageManagerInt.getPackageSetting(oldPackageName);
+ if (oldPs == null) {
+ return false;
+ }
+ if (!oldPs.isSystem()) {
+ Slog.w(TAG, "Unable to update from " + oldPs.name
+ + " to " + newPkg.getPackageName()
+ + ": old package not in system partition");
+ return false;
+ }
+ if (mPackageManagerInt.getPackage(oldPs.name) != null) {
+ Slog.w(TAG, "Unable to update from " + oldPs.name
+ + " to " + newPkg.getPackageName()
+ + ": old package still exists");
+ return false;
+ }
+ return true;
+ }
+
+ private void onPackageRemovedInternal(@NonNull AndroidPackage pkg) {
+ removeAllPermissionsInternal(pkg);
+ }
+
+ private void onPackageStateRemovedInternal(@NonNull String packageName, int appId,
+ @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs) {
+ if (sharedUserPkgs.isEmpty()
+ && mPackageManagerInt.getDisabledSystemPackage(packageName) == null) {
+ removeAppIdState(appId);
+ }
+ updatePermissions(packageName, null, mDefaultPermissionCallback);
+ if (!sharedUserPkgs.isEmpty()) {
+ // Remove permissions associated with package. Since runtime
+ // permissions are per user we have to kill the removed package
+ // or packages running under the shared user of the removed
+ // package if revoking the permissions requested only by the removed
+ // package is successful and this causes a change in gids.
+ boolean shouldKill = false;
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ final int userIdToKill = revokeSharedUserPermissionsForDeletedPackageInternal(pkg,
+ sharedUserPkgs, userId);
+ shouldKill |= userIdToKill != UserHandle.USER_NULL;
+ }
+ // If gids changed, kill all affected packages.
+ if (shouldKill) {
+ mHandler.post(() -> {
+ // This has to happen with no lock held.
+ killUid(appId, UserHandle.USER_ALL, KILL_APP_REASON_GIDS_CHANGED);
+ });
+ }
}
}
@@ -4955,35 +5040,6 @@
}
@Override
- public void revokeRuntimePermissionsIfGroupChanged(
- @NonNull AndroidPackage newPackage,
- @NonNull AndroidPackage oldPackage,
- @NonNull ArrayList<String> allPackageNames) {
- PermissionManagerService.this.revokeRuntimePermissionsIfGroupChanged(newPackage,
- oldPackage, allPackageNames, mDefaultPermissionCallback);
- }
-
- @Override
- public void revokeRuntimePermissionsIfPermissionDefinitionChanged(
- @NonNull List<String> permissionsToRevoke,
- @NonNull ArrayList<String> allPackageNames) {
- PermissionManagerService.this.revokeRuntimePermissionsIfPermissionDefinitionChanged(
- permissionsToRevoke, allPackageNames, mDefaultPermissionCallback);
- }
-
- @Override
- public List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) {
- return PermissionManagerService.this.addAllPermissions(pkg, chatty);
- }
- @Override
- public void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) {
- PermissionManagerService.this.addAllPermissionGroups(pkg, chatty);
- }
- @Override
- public void removeAllPermissions(AndroidPackage pkg, boolean chatty) {
- PermissionManagerService.this.removeAllPermissions(pkg, chatty);
- }
- @Override
public void readLegacyPermissionStateTEMP() {
PermissionManagerService.this.readLegacyPermissionState();
}
@@ -4995,17 +5051,6 @@
public void onUserRemoved(@UserIdInt int userId) {
PermissionManagerService.this.onUserRemoved(userId);
}
- @Override
- public void removeAppIdStateTEMP(@AppIdInt int appId) {
- PermissionManagerService.this.removeAppIdState(appId);
- }
- @Override
- @UserIdInt
- public int revokeSharedUserPermissionsForDeletedPackageTEMP(
- @NonNull PackageSetting deletedPs, @UserIdInt int userId) {
- return PermissionManagerService.this.revokeSharedUserPermissionsForDeletedPackage(
- deletedPs, userId);
- }
@NonNull
@Override
public Set<String> getGrantedPermissions(@NonNull String packageName,
@@ -5313,9 +5358,24 @@
}
@Override
- public void transferPermissions(@NonNull String oldPackageName,
- @NonNull String newPackageName) {
- PermissionManagerService.this.transferPermissions(oldPackageName, newPackageName);
+ public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
+ @Nullable AndroidPackage oldPkg) {
+ Objects.requireNonNull(pkg);
+ onPackageAddedInternal(pkg, isInstantApp, oldPkg);
+ }
+
+ @Override
+ public void onPackageRemoved(@NonNull AndroidPackage pkg) {
+ Objects.requireNonNull(pkg);
+ onPackageRemovedInternal(pkg);
+ }
+
+ @Override
+ public void onPackageStateRemoved(@NonNull String packageName, int appId,
+ @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs) {
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(sharedUserPkgs);
+ onPackageStateRemovedInternal(packageName, appId, pkg, sharedUserPkgs);
}
@Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index df9d0d3..1becbed 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -24,7 +24,6 @@
import android.content.pm.PermissionInfo;
import android.permission.PermissionManagerInternal;
-import com.android.server.pm.PackageSetting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.util.ArrayList;
@@ -277,44 +276,6 @@
public abstract void resetAllRuntimePermissions(@UserIdInt int userId);
/**
- * We might auto-grant permissions if any permission of the group is already granted. Hence if
- * the group of a granted permission changes we need to revoke it to avoid having permissions of
- * the new group auto-granted.
- *
- * @param newPackage The new package that was installed
- * @param oldPackage The old package that was updated
- * @param allPackageNames All packages
- */
- public abstract void revokeRuntimePermissionsIfGroupChanged(
- @NonNull AndroidPackage newPackage,
- @NonNull AndroidPackage oldPackage,
- @NonNull ArrayList<String> allPackageNames);
-
- /**
- * Some permissions might have been owned by a non-system package, and the system then defined
- * said permission. Some other permissions may one have been install permissions, but are now
- * runtime or higher. These permissions should be revoked.
- *
- * @param permissionsToRevoke A list of permission names to revoke
- * @param allPackageNames All packages
- */
- public abstract void revokeRuntimePermissionsIfPermissionDefinitionChanged(
- @NonNull List<String> permissionsToRevoke,
- @NonNull ArrayList<String> allPackageNames);
-
- /**
- * Add all permissions in the given package.
- * <p>
- * NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to
- * the permission settings.
- *
- * @return A list of BasePermissions that were updated, and need to be revoked from packages
- */
- public abstract List<String> addAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
- public abstract void addAllPermissionGroups(@NonNull AndroidPackage pkg, boolean chatty);
- public abstract void removeAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
-
- /**
* Read legacy permission state from package settings.
*
* TODO(zhanghai): This is a temporary method because we should not expose
@@ -337,30 +298,6 @@
public abstract void onUserRemoved(@UserIdInt int userId);
/**
- * Remove the permission state associated with an app ID, called the same time as the
- * removal of a {@code PackageSetitng}.
- *
- * TODO(zhanghai): This is a temporary method before we figure out a way to get notified of app
- * ID removal via API.
- */
- public abstract void removeAppIdStateTEMP(@AppIdInt int appId);
-
- /**
- * Update the shared user setting when a package with a shared user id is removed. The gids
- * associated with each permission of the deleted package are removed from the shared user'
- * gid list only if its not in use by other permissions of packages in the shared user setting.
- *
- * TODO(zhanghai): We should not need this when permission no longer sees an incomplete package
- * state where the updated system package is uninstalled but the disabled system package is yet
- * to be installed. Then we should handle this in restorePermissionState().
- *
- * @return the affected user id, may be a real user ID, USER_ALL, or USER_NULL when none.
- */
- @UserIdInt
- public abstract int revokeSharedUserPermissionsForDeletedPackageTEMP(
- @NonNull PackageSetting deletedPs, @UserIdInt int userId);
-
- /**
* Get all the permissions granted to a package.
*
* @param packageName the name of the package
@@ -578,10 +515,35 @@
@NonNull LegacyPermissionSettings legacyPermissionSettings);
/**
- * Transfers ownership of permissions from one package to another.
+ * Callback when a package has been added.
+ *
+ * @param pkg the added package
+ * @param isInstantApp whether the added package is an instant app
+ * @param oldPkg the old package, or {@code null} if none
*/
- public abstract void transferPermissions(@NonNull String oldPackageName,
- @NonNull String newPackageName);
+ //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ public abstract void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
+ @Nullable AndroidPackage oldPkg);
+
+ /**
+ * Callback when a package has been removed.
+ *
+ * @param pkg the removed package
+ */
+ //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ public abstract void onPackageRemoved(@NonNull AndroidPackage pkg);
+
+ /**
+ * Callback when the state for a package has been removed.
+ *
+ * @param packageName the name of the removed package
+ * @param appId the app ID of the removed package
+ * @param pkg the removed package, or {@code null} if unavailable
+ * @param sharedUserPkgs the packages that are in the same shared user
+ */
+ //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ public abstract void onPackageStateRemoved(@NonNull String packageName, int appId,
+ @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs);
/**
* Check whether a permission can be propagated to instant app.
diff --git a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
index 54f6183..154f9a4 100644
--- a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
@@ -17,6 +17,7 @@
package com.android.server.policy;
import android.annotation.NonNull;
+import android.content.Context;
import com.android.server.devicestate.DeviceStatePolicy;
import com.android.server.devicestate.DeviceStateProvider;
@@ -27,10 +28,12 @@
* @see DeviceStateProviderImpl
*/
public final class DeviceStatePolicyImpl implements DeviceStatePolicy {
+ private final Context mContext;
private final DeviceStateProvider mProvider;
- public DeviceStatePolicyImpl() {
- mProvider = new DeviceStateProviderImpl();
+ public DeviceStatePolicyImpl(Context context) {
+ mContext = context;
+ mProvider = DeviceStateProviderImpl.create(mContext);
}
public DeviceStateProvider getDeviceStateProvider() {
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 85ab0bc..321bb8c 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -16,30 +16,464 @@
package com.android.server.policy;
-import android.annotation.Nullable;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.input.InputManagerInternal;
+import android.os.Environment;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.policy.devicestate.config.Conditions;
+import com.android.server.policy.devicestate.config.DeviceState;
+import com.android.server.policy.devicestate.config.DeviceStateConfig;
+import com.android.server.policy.devicestate.config.LidSwitchCondition;
+import com.android.server.policy.devicestate.config.NumericRange;
+import com.android.server.policy.devicestate.config.SensorCondition;
+import com.android.server.policy.devicestate.config.XmlParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BooleanSupplier;
+
+import javax.xml.datatype.DatatypeConfigurationException;
/**
- * Default implementation of {@link DeviceStateProvider}. Currently only supports
- * {@link #DEFAULT_DEVICE_STATE}.
- *
- * @see DeviceStatePolicyImpl
+ * Implementation of {@link DeviceStateProvider} that reads the set of supported device states
+ * from a configuration file provided at either /vendor/etc/devicestate or
+ * /data/system/devicestate/. By default, the provider supports {@link #DEFAULT_DEVICE_STATE} when
+ * no configuration is provided.
*/
-final class DeviceStateProviderImpl implements DeviceStateProvider {
- private static final int DEFAULT_DEVICE_STATE = 0;
+public final class DeviceStateProviderImpl implements DeviceStateProvider,
+ InputManagerInternal.LidSwitchCallback, SensorEventListener {
+ private static final String TAG = "DeviceStateProviderImpl";
+
+ private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
+
+ @VisibleForTesting
+ static final int DEFAULT_DEVICE_STATE = 0;
+
+ private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
+ private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
+ private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
+
+ /** Interface that allows reading the device state configuration. */
+ interface ReadableConfig {
+ @NonNull
+ InputStream openRead() throws IOException;
+ }
+
+ /**
+ * Returns a new {@link DeviceStateProviderImpl} instance.
+ *
+ * @param context the {@link Context} that should be used to access system services.
+ */
+ public static DeviceStateProviderImpl create(@NonNull Context context) {
+ File configFile = getConfigurationFile();
+ if (configFile == null) {
+ return createFromConfig(context, null);
+ }
+ return createFromConfig(context, new ReadableFileConfig(configFile));
+ }
+
+ /**
+ * Returns a new {@link DeviceStateProviderImpl} instance.
+ *
+ * @param context the {@link Context} that should be used to access system services.
+ * @param readableConfig the config the provider instance should read supported states from.
+ */
+ @VisibleForTesting
+ static DeviceStateProviderImpl createFromConfig(@NonNull Context context,
+ @Nullable ReadableConfig readableConfig) {
+ SparseArray<Conditions> conditionsForState = new SparseArray<>();
+ if (readableConfig != null) {
+ DeviceStateConfig config = parseConfig(readableConfig);
+ if (config != null) {
+ for (DeviceState stateConfig : config.getDeviceState()) {
+ int state = stateConfig.getIdentifier().intValue();
+ Conditions conditions = stateConfig.getConditions();
+ conditionsForState.put(state, conditions);
+ }
+ }
+ }
+
+ if (conditionsForState.size() == 0) {
+ conditionsForState.put(DEFAULT_DEVICE_STATE, null);
+ }
+ return new DeviceStateProviderImpl(context, conditionsForState);
+ }
+
+ // Lock for internal state.
+ private final Object mLock = new Object();
+ private final Context mContext;
+ // List of supported states in ascending order.
+ private final int[] mOrderedStates;
+ // Map of state to a boolean supplier that returns true when all required conditions are met for
+ // the device to be in the state.
+ private final SparseArray<BooleanSupplier> mStateConditions;
@Nullable
+ @GuardedBy("mLock")
private Listener mListener = null;
+ @GuardedBy("mLock")
+ private int mLastReportedState = INVALID_DEVICE_STATE;
+
+ @GuardedBy("mLock")
+ private boolean mIsLidOpen;
+ @GuardedBy("mLock")
+ private final Map<Sensor, SensorEvent> mLatestSensorEvent = new ArrayMap<>();
+
+ private DeviceStateProviderImpl(@NonNull Context context,
+ @NonNull SparseArray<Conditions> conditionsForState) {
+ mContext = context;
+ mOrderedStates = new int[conditionsForState.size()];
+ for (int i = 0; i < conditionsForState.size(); i++) {
+ mOrderedStates[i] = conditionsForState.keyAt(i);
+ }
+
+ // Whether or not this instance should register to receive lid switch notifications from
+ // InputManagerInternal. If there are no device state conditions that are based on the lid
+ // switch there is no need to register for a callback.
+ boolean shouldListenToLidSwitch = false;
+
+ final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
+ // The set of Sensor(s) that this instance should register to receive SensorEvent(s) from.
+ final ArraySet<Sensor> sensorsToListenTo = new ArraySet<>();
+
+ mStateConditions = new SparseArray<>();
+ for (int i = 0; i < mOrderedStates.length; i++) {
+ int state = mOrderedStates[i];
+ Conditions conditions = conditionsForState.get(state);
+ if (conditions == null) {
+ mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
+ continue;
+ }
+
+ List<BooleanSupplier> suppliers = new ArrayList<>();
+
+ LidSwitchCondition lidSwitchCondition = conditions.getLidSwitch();
+ if (lidSwitchCondition != null) {
+ suppliers.add(new LidSwitchBooleanSupplier(lidSwitchCondition.getOpen()));
+ shouldListenToLidSwitch = true;
+ }
+
+ List<SensorCondition> sensorConditions = conditions.getSensor();
+ for (int j = 0; j < sensorConditions.size(); j++) {
+ SensorCondition sensorCondition = sensorConditions.get(j);
+ final int expectedSensorType = sensorCondition.getType().intValue();
+ final String expectedSensorName = sensorCondition.getName();
+
+ List<Sensor> sensors = sensorManager.getSensorList(expectedSensorType);
+ Sensor foundSensor = null;
+ for (int sensorIndex = 0; sensorIndex < sensors.size(); sensorIndex++) {
+ Sensor sensor = sensors.get(sensorIndex);
+ if (sensor.getName().equals(expectedSensorName)) {
+ foundSensor = sensor;
+ break;
+ }
+ }
+
+ if (foundSensor == null) {
+ throw new IllegalStateException("Failed to find Sensor with type: "
+ + expectedSensorType + " and name: " + expectedSensorName);
+ }
+
+ suppliers.add(new SensorBooleanSupplier(foundSensor, sensorCondition.getValue()));
+ sensorsToListenTo.add(foundSensor);
+ }
+
+ if (suppliers.size() > 1) {
+ mStateConditions.put(state, new AndBooleanSupplier(suppliers));
+ } else if (suppliers.size() > 0) {
+ // No need to wrap with an AND supplier if there is only 1.
+ mStateConditions.put(state, suppliers.get(0));
+ } else {
+ // There are no conditions for this state. Default to always true.
+ mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
+ }
+ }
+
+ if (shouldListenToLidSwitch) {
+ InputManagerInternal inputManager = LocalServices.getService(
+ InputManagerInternal.class);
+ inputManager.registerLidSwitchCallback(this);
+ }
+
+ for (int i = 0; i < sensorsToListenTo.size(); i++) {
+ Sensor sensor = sensorsToListenTo.valueAt(i);
+ sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
+ }
+ }
@Override
public void setListener(Listener listener) {
- if (mListener != null) {
- throw new RuntimeException("Provider already has a listener set.");
+ synchronized (mLock) {
+ if (mListener != null) {
+ throw new RuntimeException("Provider already has a listener set.");
+ }
+ mListener = listener;
+ }
+ notifySupportedStatesChanged();
+ notifyDeviceStateChangedIfNeeded();
+ }
+
+ /** Notifies the listener that the set of supported device states has changed. */
+ private void notifySupportedStatesChanged() {
+ int[] supportedStates;
+ synchronized (mLock) {
+ if (mListener == null) {
+ return;
+ }
+
+ supportedStates = Arrays.copyOf(mOrderedStates, mOrderedStates.length);
}
- mListener = listener;
- mListener.onSupportedDeviceStatesChanged(new int[]{ DEFAULT_DEVICE_STATE });
- mListener.onStateChanged(DEFAULT_DEVICE_STATE);
+ mListener.onSupportedDeviceStatesChanged(supportedStates);
+ }
+
+ /** Computes the current device state and notifies the listener of a change, if needed. */
+ void notifyDeviceStateChangedIfNeeded() {
+ int stateToReport = INVALID_DEVICE_STATE;
+ synchronized (mLock) {
+ if (mListener == null) {
+ return;
+ }
+
+ int newState = mOrderedStates[0];
+ for (int i = 1; i < mOrderedStates.length; i++) {
+ int state = mOrderedStates[i];
+ if (mStateConditions.get(state).getAsBoolean()) {
+ newState = state;
+ break;
+ }
+ }
+
+ if (newState != mLastReportedState) {
+ mLastReportedState = newState;
+ stateToReport = newState;
+ }
+ }
+
+ if (stateToReport != INVALID_DEVICE_STATE) {
+ mListener.onStateChanged(stateToReport);
+ }
+ }
+
+ @Override
+ public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+ synchronized (mLock) {
+ mIsLidOpen = lidOpen;
+ }
+ notifyDeviceStateChangedIfNeeded();
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ synchronized (mLock) {
+ mLatestSensorEvent.put(event.sensor, event);
+ }
+ notifyDeviceStateChangedIfNeeded();
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // Do nothing.
+ }
+
+ /**
+ * Implementation of {@link BooleanSupplier} that returns {@code true} if the expected lid
+ * switch open state matches {@link #mIsLidOpen}.
+ */
+ private final class LidSwitchBooleanSupplier implements BooleanSupplier {
+ private final boolean mExpectedOpen;
+
+ LidSwitchBooleanSupplier(boolean expectedOpen) {
+ mExpectedOpen = expectedOpen;
+ }
+
+ @Override
+ public boolean getAsBoolean() {
+ synchronized (mLock) {
+ return mIsLidOpen == mExpectedOpen;
+ }
+ }
+ }
+
+ /**
+ * Implementation of {@link BooleanSupplier} that returns {@code true} if the latest
+ * {@link SensorEvent#values sensor event values} for the specified {@link Sensor} adhere to
+ * the supplied {@link NumericRange ranges}.
+ */
+ private final class SensorBooleanSupplier implements BooleanSupplier {
+ @NonNull
+ private final Sensor mSensor;
+ @NonNull
+ private final List<NumericRange> mExpectedValues;
+
+ SensorBooleanSupplier(@NonNull Sensor sensor, @NonNull List<NumericRange> expectedValues) {
+ mSensor = sensor;
+ mExpectedValues = expectedValues;
+ }
+
+ @Override
+ public boolean getAsBoolean() {
+ synchronized (mLock) {
+ SensorEvent latestEvent = mLatestSensorEvent.get(mSensor);
+ if (latestEvent == null) {
+ // Default to returning false if we have not yet received a sensor event for the
+ // sensor.
+ return false;
+ }
+
+ if (latestEvent.values.length != mExpectedValues.size()) {
+ throw new IllegalStateException("Number of supplied numeric range(s) does not "
+ + "match the number of values in the latest sensor event for sensor: "
+ + mSensor);
+ }
+
+ for (int i = 0; i < latestEvent.values.length; i++) {
+ if (!adheresToRange(latestEvent.values[i], mExpectedValues.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Returns {@code true} if the supplied {@code value} adheres to the constraints specified
+ * in {@code range}.
+ */
+ private boolean adheresToRange(float value, @NonNull NumericRange range) {
+ final BigDecimal min = range.getMin_optional();
+ if (min != null) {
+ if (value <= min.floatValue()) {
+ return false;
+ }
+ }
+
+ final BigDecimal minInclusive = range.getMinInclusive_optional();
+ if (minInclusive != null) {
+ if (value < minInclusive.floatValue()) {
+ return false;
+ }
+ }
+
+ final BigDecimal max = range.getMax_optional();
+ if (max != null) {
+ if (value >= max.floatValue()) {
+ return false;
+ }
+ }
+
+ final BigDecimal maxInclusive = range.getMaxInclusive_optional();
+ if (maxInclusive != null) {
+ if (value > maxInclusive.floatValue()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * Implementation of {@link BooleanSupplier} whose result is the product of an AND operation
+ * applied to the result of all child suppliers.
+ */
+ private static final class AndBooleanSupplier implements BooleanSupplier {
+ @NonNull
+ List<BooleanSupplier> mBooleanSuppliers;
+
+ AndBooleanSupplier(@NonNull List<BooleanSupplier> booleanSuppliers) {
+ mBooleanSuppliers = booleanSuppliers;
+ }
+
+ @Override
+ public boolean getAsBoolean() {
+ for (int i = 0; i < mBooleanSuppliers.size(); i++) {
+ if (!mBooleanSuppliers.get(i).getAsBoolean()) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Returns the device state configuration file that should be used, or {@code null} if no file
+ * is present on the device.
+ * <p>
+ * Defaults to returning a config file present in the data/ dir at
+ * {@link #DATA_CONFIG_FILE_PATH}, and then falls back to the config file in the vendor/ dir
+ * at {@link #VENDOR_CONFIG_FILE_PATH} if no config file is found in the data/ dir.
+ */
+ @Nullable
+ private static File getConfigurationFile() {
+ final File configFileFromDataDir = Environment.buildPath(Environment.getDataDirectory(),
+ DATA_CONFIG_FILE_PATH, CONFIG_FILE_NAME);
+ if (configFileFromDataDir.exists()) {
+ return configFileFromDataDir;
+ }
+
+ final File configFileFromVendorDir = Environment.buildPath(Environment.getVendorDirectory(),
+ VENDOR_CONFIG_FILE_PATH, CONFIG_FILE_NAME);
+ if (configFileFromVendorDir.exists()) {
+ return configFileFromVendorDir;
+ }
+
+ return null;
+ }
+
+ /**
+ * Tries to parse the provided file into a {@link DeviceStateConfig} object. Returns
+ * {@code null} if the file could not be successfully parsed.
+ */
+ @Nullable
+ private static DeviceStateConfig parseConfig(@NonNull ReadableConfig readableConfig) {
+ try (InputStream in = readableConfig.openRead();
+ InputStream bin = new BufferedInputStream(in)) {
+ return XmlParser.read(bin);
+ } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
+ Slog.e(TAG, "Encountered an error while reading device state config", e);
+ }
+ return null;
+ }
+
+ /** Implementation of {@link ReadableConfig} that reads config data from a file. */
+ private static final class ReadableFileConfig implements ReadableConfig {
+ @NonNull
+ private final File mFile;
+
+ private ReadableFileConfig(@NonNull File file) {
+ mFile = file;
+ }
+
+ @Override
+ public InputStream openRead() throws IOException {
+ return new FileInputStream(mFile);
+ }
}
}
diff --git a/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java b/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java
new file mode 100644
index 0000000..6477552
--- /dev/null
+++ b/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.display.DisplayGroup;
+
+/**
+ * Responsible for creating {@link DisplayPowerRequest}s and associating them with
+ * {@link com.android.server.display.DisplayGroup}s.
+ *
+ * Each {@link com.android.server.display.DisplayGroup} has a single {@link DisplayPowerRequest}
+ * which is used to request power state changes to every display in the group.
+ */
+class DisplayPowerRequestMapper {
+
+ private final Object mLock = new Object();
+
+ /** A mapping from LogicalDisplay Id to DisplayGroup Id. */
+ @GuardedBy("mLock")
+ private final SparseIntArray mDisplayGroupIds = new SparseIntArray();
+
+ /** A mapping from DisplayGroup Id to DisplayPowerRequest. */
+ @GuardedBy("mLock")
+ private final SparseArray<DisplayPowerRequest> mDisplayPowerRequests = new SparseArray<>();
+
+ private final DisplayManagerInternal mDisplayManagerInternal;
+
+ private final DisplayManager.DisplayListener mDisplayListener =
+ new DisplayManager.DisplayListener() {
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ synchronized (mLock) {
+ if (mDisplayGroupIds.indexOfKey(displayId) >= 0) {
+ return;
+ }
+ final int displayGroupId = mDisplayManagerInternal.getDisplayGroupId(
+ displayId);
+ if (!mDisplayPowerRequests.contains(displayGroupId)) {
+ // A new DisplayGroup was created; create a new DisplayPowerRequest.
+ mDisplayPowerRequests.append(displayGroupId, new DisplayPowerRequest());
+ }
+ mDisplayGroupIds.append(displayId, displayGroupId);
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ synchronized (mLock) {
+ final int index = mDisplayGroupIds.indexOfKey(displayId);
+ if (index < 0) {
+ return;
+ }
+ final int displayGroupId = mDisplayGroupIds.valueAt(index);
+ mDisplayGroupIds.removeAt(index);
+
+ if (mDisplayGroupIds.indexOfValue(displayGroupId) < 0) {
+ // The DisplayGroup no longer exists; delete the DisplayPowerRequest.
+ mDisplayPowerRequests.delete(displayGroupId);
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ synchronized (mLock) {
+ final int newDisplayGroupId = mDisplayManagerInternal.getDisplayGroupId(
+ displayId);
+ final int oldDisplayGroupId = mDisplayGroupIds.get(displayId);
+
+ if (!mDisplayPowerRequests.contains(newDisplayGroupId)) {
+ // A new DisplayGroup was created; create a new DisplayPowerRequest.
+ mDisplayPowerRequests.append(newDisplayGroupId,
+ new DisplayPowerRequest());
+ }
+ mDisplayGroupIds.put(displayId, newDisplayGroupId);
+
+ if (mDisplayGroupIds.indexOfValue(oldDisplayGroupId) < 0) {
+ // The DisplayGroup no longer exists; delete the DisplayPowerRequest.
+ mDisplayPowerRequests.delete(oldDisplayGroupId);
+ }
+ }
+ }
+ };
+
+ DisplayPowerRequestMapper(DisplayManager displayManager,
+ DisplayManagerInternal displayManagerInternal, Handler handler) {
+ mDisplayManagerInternal = displayManagerInternal;
+ displayManager.registerDisplayListener(mDisplayListener, handler);
+ mDisplayPowerRequests.append(DisplayGroup.DEFAULT, new DisplayPowerRequest());
+ mDisplayGroupIds.append(Display.DEFAULT_DISPLAY, DisplayGroup.DEFAULT);
+ }
+
+ DisplayPowerRequest get(int displayId) {
+ synchronized (mLock) {
+ return mDisplayPowerRequests.get(mDisplayGroupIds.get(displayId));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ccd659d..955e1cd 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -42,6 +42,7 @@
import android.hardware.SensorManager;
import android.hardware.SystemSensorManager;
import android.hardware.display.AmbientDisplayConfiguration;
+import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.hardware.power.Boost;
@@ -348,9 +349,9 @@
// A bitfield that summarizes the effect of the user activity timer.
private int mUserActivitySummary;
- // The desired display power state. The actual state may lag behind the
+ // Manages the desired power state of displays. The actual state may lag behind the
// requested because it is updated asynchronously by the display power controller.
- private final DisplayPowerRequest mDisplayPowerRequest = new DisplayPowerRequest();
+ private DisplayPowerRequestMapper mDisplayPowerRequestMapper;
// True if the display power state has been fully applied, which means the display
// is actually on or actually off or whatever was requested.
@@ -1054,6 +1055,8 @@
mPolicy = getLocalService(WindowManagerPolicy.class);
mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
mAttentionDetector.systemReady(mContext);
+ mDisplayPowerRequestMapper = new DisplayPowerRequestMapper(mContext.getSystemService(
+ DisplayManager.class), mDisplayManagerInternal, mHandler);
SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper());
@@ -2296,10 +2299,12 @@
&& mLastUserActivityTimeNoChangeLights >= mLastWakeTime) {
nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout;
if (now < nextTimeout) {
- if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT
- || mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) {
+ final DisplayPowerRequest displayPowerRequest =
+ mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY);
+ if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT
+ || displayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) {
mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
- } else if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
+ } else if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
}
}
@@ -2771,11 +2776,13 @@
* Returns true if the device is allowed to dream in its current state.
*/
private boolean canDreamLocked() {
+ final DisplayPowerRequest displayPowerRequest =
+ mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY);
if (getWakefulnessLocked() != WAKEFULNESS_DREAMING
|| !mDreamsSupportedConfig
|| !mDreamsEnabledSetting
- || !mDisplayPowerRequest.isBrightOrDim()
- || mDisplayPowerRequest.isVr()
+ || !displayPowerRequest.isBrightOrDim()
+ || displayPowerRequest.isVr()
|| (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT
| USER_ACTIVITY_SCREEN_DIM | USER_ACTIVITY_SCREEN_DREAM)) == 0
|| !mBootCompleted) {
@@ -2826,7 +2833,9 @@
sQuiescent = false;
}
- mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked();
+ final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get(
+ Display.DEFAULT_DISPLAY);
+ displayPowerRequest.policy = getDesiredScreenPolicyLocked();
// Determine appropriate screen brightness and auto-brightness adjustments.
final boolean autoBrightness;
@@ -2846,39 +2855,39 @@
}
// Update display power request.
- mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
- mDisplayPowerRequest.useAutoBrightness = autoBrightness;
- mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
- mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();
+ displayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
+ displayPowerRequest.useAutoBrightness = autoBrightness;
+ displayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
+ displayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();
- updatePowerRequestFromBatterySaverPolicy(mDisplayPowerRequest);
+ updatePowerRequestFromBatterySaverPolicy(displayPowerRequest);
- if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
- mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
+ if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
+ displayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0
&& !mDrawWakeLockOverrideFromSidekick) {
- if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
- mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+ if (displayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
+ displayPowerRequest.dozeScreenState = Display.STATE_DOZE;
}
- if (mDisplayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
- mDisplayPowerRequest.dozeScreenState = Display.STATE_ON;
+ if (displayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
+ displayPowerRequest.dozeScreenState = Display.STATE_ON;
}
}
- mDisplayPowerRequest.dozeScreenBrightness =
+ displayPowerRequest.dozeScreenBrightness =
mDozeScreenBrightnessOverrideFromDreamManagerFloat;
} else {
- mDisplayPowerRequest.dozeScreenState = Display.STATE_UNKNOWN;
- mDisplayPowerRequest.dozeScreenBrightness =
+ displayPowerRequest.dozeScreenState = Display.STATE_UNKNOWN;
+ displayPowerRequest.dozeScreenBrightness =
PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- mDisplayReady = mDisplayManagerInternal.requestPowerState(mDisplayPowerRequest,
+ mDisplayReady = mDisplayManagerInternal.requestPowerState(displayPowerRequest,
mRequestWaitForNegativeProximity);
mRequestWaitForNegativeProximity = false;
if (DEBUG_SPEW) {
Slog.d(TAG, "updateDisplayPowerStateLocked: mDisplayReady=" + mDisplayReady
- + ", policy=" + mDisplayPowerRequest.policy
+ + ", policy=" + displayPowerRequest.policy
+ ", mWakefulness=" + getWakefulnessLocked()
+ ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)
+ ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)
@@ -3049,7 +3058,9 @@
final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0);
final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked();
final boolean autoSuspend = !needDisplaySuspendBlocker;
- final boolean interactive = mDisplayPowerRequest.isBrightOrDim();
+ final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get(
+ Display.DEFAULT_DISPLAY);
+ final boolean interactive = displayPowerRequest.isBrightOrDim();
// Disable auto-suspend if needed.
// FIXME We should consider just leaving auto-suspend enabled forever since
@@ -3108,19 +3119,21 @@
if (!mDisplayReady) {
return true;
}
- if (mDisplayPowerRequest.isBrightOrDim()) {
+ final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get(
+ Display.DEFAULT_DISPLAY);
+ if (displayPowerRequest.isBrightOrDim()) {
// If we asked for the screen to be on but it is off due to the proximity
// sensor then we may suspend but only if the configuration allows it.
// On some hardware it may not be safe to suspend because the proximity
// sensor may not be correctly configured as a wake-up source.
- if (!mDisplayPowerRequest.useProximitySensor || !mProximityPositive
+ if (!displayPowerRequest.useProximitySensor || !mProximityPositive
|| !mSuspendWhenScreenOffDueToProximityConfig) {
return true;
}
}
- if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE
- && mDisplayPowerRequest.dozeScreenState == Display.STATE_ON) {
+ if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE
+ && displayPowerRequest.dozeScreenState == Display.STATE_ON) {
// Although we are in DOZE and would normally allow the device to suspend,
// the doze service has explicitly requested the display to remain in the ON
// state which means we should hold the display suspend blocker.
@@ -5575,7 +5588,10 @@
// DisplayPowerController only reports proximity positive (near) if it's
// positive and the proximity wasn't already being ignored. So it reliably
// also tells us that we're not already ignoring the proximity sensor.
- if (mDisplayPowerRequest.useProximitySensor && mProximityPositive) {
+
+ final DisplayPowerRequest displayPowerRequest =
+ mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY);
+ if (displayPowerRequest.useProximitySensor && mProximityPositive) {
mDisplayManagerInternal.ignoreProximitySensorUntilChanged();
return true;
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index afbe552..510893b 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -87,6 +87,7 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.IoThread;
import com.android.server.SystemService;
@@ -337,6 +338,7 @@
inputState = new TvInputState();
}
inputState.info = info;
+ inputState.uid = getInputUid(info);
inputMap.put(info.getId(), inputState);
}
@@ -371,6 +373,16 @@
userState.inputMap = inputMap;
}
+ private int getInputUid(TvInputInfo info) {
+ try {
+ return getContext().getPackageManager().getApplicationInfo(
+ info.getServiceInfo().packageName, 0).uid;
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "Unable to get UID for " + info, e);
+ return Process.INVALID_UID;
+ }
+ }
+
@GuardedBy("mLock")
private void buildTvContentRatingSystemListLocked(int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
@@ -405,7 +417,7 @@
return;
}
if (mUserStates.contains(mCurrentUserId)) {
- UserState userState = mUserStates.get(mCurrentUserId);
+ UserState userState = getUserStateLocked(mCurrentUserId);
List<SessionState> sessionStatesToRelease = new ArrayList<>();
for (SessionState sessionState : userState.sessionStateMap.values()) {
if (sessionState.session != null && !sessionState.isRecordingSession) {
@@ -474,7 +486,7 @@
private void removeUser(int userId) {
synchronized (mLock) {
- UserState userState = mUserStates.get(userId);
+ UserState userState = getUserStateLocked(userId);
if (userState == null) {
return;
}
@@ -535,7 +547,7 @@
@GuardedBy("mLock")
private UserState getOrCreateUserStateLocked(int userId) {
- UserState userState = mUserStates.get(userId);
+ UserState userState = getUserStateLocked(userId);
if (userState == null) {
userState = new UserState(mContext, userId);
mUserStates.put(userId, userState);
@@ -715,7 +727,8 @@
}
@GuardedBy("mLock")
- private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
+ @Nullable
+ private SessionState releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
SessionState sessionState = null;
try {
sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
@@ -738,6 +751,7 @@
}
}
removeSessionStateLocked(sessionToken, userId);
+ return sessionState;
}
@GuardedBy("mLock")
@@ -908,6 +922,7 @@
return;
}
inputState.info = inputInfo;
+ inputState.uid = getInputUid(inputInfo);
int n = userState.mCallbacks.beginBroadcast();
for (int i = 0; i < n; ++i) {
@@ -1248,7 +1263,22 @@
final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
userId, "createSession");
final long identity = Binder.clearCallingIdentity();
- // Generate a unique session id with a random UUID.
+ /**
+ * A randomly generated id for this this session.
+ *
+ * <p>This field contains no user or device reference and is large enough to be
+ * effectively globally unique.
+ *
+ * <p><b>WARNING</b> Any changes to this field should be carefully reviewed for privacy.
+ * Inspect the code at:
+ *
+ * <ul>
+ * <li>framework/base/cmds/statsd/src/atoms.proto#TifTuneState
+ * <li>{@link #logTuneStateChanged}
+ * <li>{@link TvInputManagerService.BinderService#createSession}
+ * <li>{@link SessionState#sessionId}
+ * </ul>
+ */
String uniqueSessionId = UUID.randomUUID().toString();
try {
synchronized (mLock) {
@@ -1269,6 +1299,8 @@
TvInputInfo info = inputState.info;
ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
if (serviceState == null) {
+ int tisUid = PackageManager.getApplicationInfoAsUserCached(
+ info.getComponent().getPackageName(), 0, resolvedUserId).uid;
serviceState = new ServiceState(info.getComponent(), resolvedUserId);
userState.serviceStateMap.put(info.getComponent(), serviceState);
}
@@ -1301,6 +1333,8 @@
} else {
updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
}
+ logTuneStateChanged(FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__CREATED,
+ sessionState, inputState);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1317,8 +1351,17 @@
userId, "releaseSession");
final long identity = Binder.clearCallingIdentity();
try {
+ SessionState sessionState = null;
+ UserState userState = null;
synchronized (mLock) {
- releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+ sessionState = releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+ userState = getUserStateLocked(userId);
+ }
+ if (sessionState != null) {
+ TvInputState tvInputState = TvInputManagerService.getTvInputState(sessionState,
+ userState);
+ logTuneStateChanged(FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__RELEASED,
+ sessionState, tvInputState);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1372,10 +1415,13 @@
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "setSurface");
final long identity = Binder.clearCallingIdentity();
+ SessionState sessionState = null;
+ UserState userState = null;
try {
synchronized (mLock) {
try {
- SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ userState = getUserStateLocked(userId);
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
resolvedUserId);
if (sessionState.hardwareSessionToken == null) {
getSessionLocked(sessionState).setSurface(surface);
@@ -1392,6 +1438,14 @@
// surface is not used in TvInputManagerService.
surface.release();
}
+ if (sessionState != null) {
+ int state = surface == null
+ ?
+ FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__SURFACE_ATTACHED
+ : FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__SURFACE_DETACHED;
+ logTuneStateChanged(state, sessionState,
+ TvInputManagerService.getTvInputState(sessionState, userState));
+ }
Binder.restoreCallingIdentity(identity);
}
}
@@ -1479,6 +1533,10 @@
return;
}
+ logTuneStateChanged(
+ FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__TUNE_STARTED,
+ sessionState,
+ TvInputManagerService.getTvInputState(sessionState, userState));
// Log the start of watch.
SomeArgs args = SomeArgs.obtain();
args.arg1 = sessionState.componentName.getPackageName();
@@ -2302,6 +2360,16 @@
}
}
+ @Nullable
+ private static TvInputState getTvInputState(
+ SessionState sessionState,
+ @Nullable UserState userState) {
+ if (userState != null) {
+ return userState.inputMap.get(sessionState.inputId);
+ }
+ return null;
+ }
+
@GuardedBy("mLock")
private List<TunedInfo> getCurrentTunedInfosInternalLocked(
UserState userState, int callingPid, int callingUid) {
@@ -2367,6 +2435,28 @@
}
}
+ /**
+ * Log Tune state changes to {@link FrameworkStatsLog}.
+ *
+ * <p><b>WARNING</b> Any changes to this field should be carefully reviewed for privacy.
+ * Inspect the code at:
+ *
+ * <ul>
+ * <li>framework/base/cmds/statsd/src/atoms.proto#TifTuneState
+ * <li>{@link #logTuneStateChanged}
+ * <li>{@link TvInputManagerService.BinderService#createSession}
+ * <li>{@link SessionState#sessionId}
+ * </ul>
+ */
+ private void logTuneStateChanged(int state, SessionState sessionState,
+ @Nullable TvInputState inputState) {
+ // TODO(b/173536904): log input type and id
+ FrameworkStatsLog.write(FrameworkStatsLog.TIF_TUNE_CHANGED,
+ new int[]{sessionState.callingUid,
+ inputState == null ? Process.INVALID_UID : inputState.uid},
+ new String[]{"tif_player", "tv_input_service"}, state, sessionState.sessionId);
+ }
+
private static final class UserState {
// A mapping from the TV input id to its TvInputState.
private Map<String, TvInputState> inputMap = new HashMap<>();
@@ -2464,10 +2554,25 @@
}
private static final class TvInputState {
- // A TvInputInfo object which represents the TV input.
+
+ /** A TvInputInfo object which represents the TV input. */
private TvInputInfo info;
- // The state of TV input. Connected by default.
+ /**
+ * The kernel user-ID that has been assigned to the application the TvInput is a part of.
+ *
+ * <p>
+ * Currently this is not a unique ID (multiple applications can have
+ * the same uid).
+ */
+ private int uid;
+
+ /**
+ * The state of TV input.
+ *
+ * <p>
+ * Connected by default
+ */
private int state = INPUT_STATE_CONNECTED;
@Override
@@ -2478,6 +2583,23 @@
private final class SessionState implements IBinder.DeathRecipient {
private final String inputId;
+
+ /**
+ * A randomly generated id for this this session.
+ *
+ * <p>This field contains no user or device reference and is large enough to be
+ * effectively globally unique.
+ *
+ * <p><b>WARNING</b> Any changes to this field should be carefully reviewed for privacy.
+ * Inspect the code at:
+ *
+ * <ul>
+ * <li>framework/base/cmds/statsd/src/atoms.proto#TifTuneState
+ * <li>{@link #logTuneStateChanged}
+ * <li>{@link TvInputManagerService.BinderService#createSession}
+ * <li>{@link SessionState#sessionId}
+ * </ul>
+ */
private final String sessionId;
private final ComponentName componentName;
private final boolean isRecordingSession;
@@ -2545,7 +2667,7 @@
Slog.d(TAG, "onServiceConnected(component=" + component + ")");
}
synchronized (mLock) {
- UserState userState = mUserStates.get(mUserId);
+ UserState userState = getUserStateLocked(mUserId);
if (userState == null) {
// The user was removed while connecting.
mContext.unbindService(this);
@@ -2815,8 +2937,13 @@
if (mSessionState.session == null || mSessionState.client == null) {
return;
}
+ TvInputState tvInputState = getTvInputState(mSessionState,
+ getUserStateLocked(mCurrentUserId));
try {
mSessionState.client.onVideoAvailable(mSessionState.seq);
+ logTuneStateChanged(
+ FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__VIDEO_AVAILABLE,
+ mSessionState, tvInputState);
} catch (RemoteException e) {
Slog.e(TAG, "error in onVideoAvailable", e);
}
@@ -2832,8 +2959,20 @@
if (mSessionState.session == null || mSessionState.client == null) {
return;
}
+ TvInputState tvInputState = getTvInputState(mSessionState,
+ getUserStateLocked(mCurrentUserId));
try {
mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
+ int loggedReason = reason + FrameworkStatsLog
+ .TIF_TUNE_STATE_CHANGED__STATE__VIDEO_UNAVAILABLE_REASON_UNKNOWN;
+ if (loggedReason < FrameworkStatsLog
+ .TIF_TUNE_STATE_CHANGED__STATE__VIDEO_UNAVAILABLE_REASON_UNKNOWN
+ || loggedReason > FrameworkStatsLog
+ .TIF_TUNE_STATE_CHANGED__STATE__VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN) {
+ loggedReason = FrameworkStatsLog
+ .TIF_TUNE_STATE_CHANGED__STATE__VIDEO_UNAVAILABLE_REASON_UNKNOWN;
+ }
+ logTuneStateChanged(loggedReason, mSessionState, tvInputState);
} catch (RemoteException e) {
Slog.e(TAG, "error in onVideoUnavailable", e);
}
@@ -3019,6 +3158,10 @@
}
}
+ private UserState getUserStateLocked(int userId) {
+ return mUserStates.get(userId);
+ }
+
private static final class WatchLogHandler extends Handler {
// There are only two kinds of watch events that can happen on the system:
// 1. The current TV input session is tuned to a new channel.
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
new file mode 100644
index 0000000..e1feb5a
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.os.Handler;
+import android.os.ParcelUuid;
+
+import java.util.Objects;
+
+/**
+ * Tracks a set of Networks underpinning a VcnGatewayConnection.
+ *
+ * <p>A single UnderlyingNetworkTracker is built to serve a SINGLE VCN Gateway Connection, and MUST
+ * be torn down with the VcnGatewayConnection in order to ensure underlying networks are allowed to
+ * be reaped.
+ *
+ * @hide
+ */
+public class UnderlyingNetworkTracker extends Handler {
+ @NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName();
+
+ @NonNull private final VcnContext mVcnContext;
+ @NonNull private final ParcelUuid mSubscriptionGroup;
+ @NonNull private final UnderlyingNetworkTrackerCallback mCb;
+ @NonNull private final Dependencies mDeps;
+
+ public UnderlyingNetworkTracker(
+ @NonNull VcnContext vcnContext,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull UnderlyingNetworkTrackerCallback cb) {
+ this(vcnContext, subscriptionGroup, cb, new Dependencies());
+ }
+
+ private UnderlyingNetworkTracker(
+ @NonNull VcnContext vcnContext,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull UnderlyingNetworkTrackerCallback cb,
+ @NonNull Dependencies deps) {
+ super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
+ mVcnContext = vcnContext;
+ mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+ mCb = Objects.requireNonNull(cb, "Missing cb");
+ mDeps = Objects.requireNonNull(deps, "Missing deps");
+ }
+
+ /** Tears down this Tracker, and releases all underlying network requests. */
+ public void teardown() {}
+
+ /** An record of a single underlying network, caching relevant fields. */
+ public static class UnderlyingNetworkRecord {
+ @NonNull public final Network network;
+ @NonNull public final NetworkCapabilities networkCapabilities;
+ @NonNull public final LinkProperties linkProperties;
+ public final boolean blocked;
+
+ private UnderlyingNetworkRecord(
+ @NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities,
+ @NonNull LinkProperties linkProperties,
+ boolean blocked) {
+ this.network = network;
+ this.networkCapabilities = networkCapabilities;
+ this.linkProperties = linkProperties;
+ this.blocked = blocked;
+ }
+ }
+
+ /** Callbacks for being notified of the changes in, or to the selected underlying network. */
+ public interface UnderlyingNetworkTrackerCallback {
+ /**
+ * Fired when a new underlying network is selected, or properties have changed.
+ *
+ * <p>This callback does NOT signal a mobility event.
+ *
+ * @param underlying The details of the new underlying network
+ */
+ void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying);
+ }
+
+ private static class Dependencies {}
+}
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
new file mode 100644
index 0000000..d51d16b
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn;
+
+import android.annotation.NonNull;
+import android.net.NetworkRequest;
+import android.net.vcn.VcnConfig;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelUuid;
+
+import java.util.Objects;
+
+/**
+ * Represents an single instance of a VCN.
+ *
+ * <p>Each Vcn instance manages all tunnels for a given subscription group, including per-capability
+ * networks, network selection, and multi-homing.
+ *
+ * @hide
+ */
+public class Vcn extends Handler {
+ private static final String TAG = Vcn.class.getSimpleName();
+
+ @NonNull private final VcnContext mVcnContext;
+ @NonNull private final ParcelUuid mSubscriptionGroup;
+ @NonNull private final Dependencies mDeps;
+
+ @NonNull private VcnConfig mConfig;
+
+ public Vcn(
+ @NonNull VcnContext vcnContext,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull VcnConfig config) {
+ this(vcnContext, subscriptionGroup, config, new Dependencies());
+ }
+
+ private Vcn(
+ @NonNull VcnContext vcnContext,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull VcnConfig config,
+ @NonNull Dependencies deps) {
+ super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
+ mVcnContext = vcnContext;
+ mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+ mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+ mConfig = Objects.requireNonNull(config, "Missing config");
+ }
+
+ /** Asynchronously updates the configuration and triggers a re-evaluation of Networks */
+ public void updateConfig(@NonNull VcnConfig config) {
+ Objects.requireNonNull(config, "Missing config");
+ // TODO: Proxy to handler, and make config there.
+ }
+
+ /** Asynchronously tears down this Vcn instance, along with all tunnels and Networks */
+ public void teardown() {
+ // TODO: Proxy to handler, and teardown there.
+ }
+
+ /** Notifies this Vcn instance of a new NetworkRequest */
+ public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
+ Objects.requireNonNull(request, "Missing request");
+
+ // TODO: Proxy to handler, and handle there.
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ // TODO: Do something
+ }
+
+ /** Retrieves the network score for a VCN Network */
+ private int getNetworkScore() {
+ // TODO: STOPSHIP (b/173549607): Make this use new NetworkSelection, or some magic "max in
+ // subGrp" value
+ return 52;
+ }
+
+ private static class Dependencies {}
+}
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
new file mode 100644
index 0000000..8ab52931
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -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.
+ */
+
+package com.android.server.vcn;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+
+import com.android.server.VcnManagementService.VcnNetworkProvider;
+
+import java.util.Objects;
+
+/**
+ * A simple class to pass around context information.
+ *
+ * @hide
+ */
+public class VcnContext {
+ @NonNull private final Context mContext;
+ @NonNull private final Looper mLooper;
+ @NonNull private final VcnNetworkProvider mVcnNetworkProvider;
+
+ public VcnContext(
+ @NonNull Context context,
+ @NonNull Looper looper,
+ @NonNull VcnNetworkProvider vcnNetworkProvider) {
+ mContext = Objects.requireNonNull(context, "Missing context");
+ mLooper = Objects.requireNonNull(looper, "Missing looper");
+ mVcnNetworkProvider = Objects.requireNonNull(vcnNetworkProvider, "Missing networkProvider");
+ }
+
+ @NonNull
+ public Context getContext() {
+ return mContext;
+ }
+
+ @NonNull
+ public Looper getLooper() {
+ return mLooper;
+ }
+
+ @NonNull
+ public VcnNetworkProvider getVcnNetworkProvider() {
+ return mVcnNetworkProvider;
+ }
+}
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
new file mode 100644
index 0000000..49c9b32
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.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.vcn;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.os.Handler;
+import android.os.ParcelUuid;
+
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
+
+import java.util.Objects;
+
+/**
+ * A single VCN Gateway Connection, providing a single public-facing VCN network.
+ *
+ * <p>This class handles mobility events, performs retries, and tracks safe-mode conditions.
+ *
+ * @hide
+ */
+public class VcnGatewayConnection extends Handler implements UnderlyingNetworkTrackerCallback {
+ private static final String TAG = VcnGatewayConnection.class.getSimpleName();
+
+ @NonNull private final VcnContext mVcnContext;
+ @NonNull private final ParcelUuid mSubscriptionGroup;
+ @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+ @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
+ @NonNull private final Dependencies mDeps;
+
+ public VcnGatewayConnection(
+ @NonNull VcnContext vcnContext,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull VcnGatewayConnectionConfig connectionConfig) {
+ this(vcnContext, subscriptionGroup, connectionConfig, new Dependencies());
+ }
+
+ private VcnGatewayConnection(
+ @NonNull VcnContext vcnContext,
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull VcnGatewayConnectionConfig connectionConfig,
+ @NonNull Dependencies deps) {
+ super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
+ mVcnContext = vcnContext;
+ mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+ mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
+ mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+ mUnderlyingNetworkTracker =
+ mDeps.newUnderlyingNetworkTracker(mVcnContext, subscriptionGroup, this);
+ }
+
+ /** Tears down this GatewayConnection, and any resources used */
+ public void teardown() {
+ mUnderlyingNetworkTracker.teardown();
+ }
+
+ private static class Dependencies {
+ public UnderlyingNetworkTracker newUnderlyingNetworkTracker(
+ VcnContext vcnContext,
+ ParcelUuid subscriptionGroup,
+ UnderlyingNetworkTrackerCallback callback) {
+ return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback);
+ }
+ }
+
+ @Override
+ public void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying) {}
+}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index d8d1a65..b4ca7c5 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -3,8 +3,10 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityManager.processStateAmToProto;
+import static android.app.WaitResult.INVALID_DELAY;
import static android.app.WaitResult.LAUNCH_STATE_COLD;
import static android.app.WaitResult.LAUNCH_STATE_HOT;
+import static android.app.WaitResult.LAUNCH_STATE_RELAUNCH;
import static android.app.WaitResult.LAUNCH_STATE_WARM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -130,7 +132,6 @@
* transition, in the case the launch is standalone (e.g. from recents).
*/
private static final int IGNORE_CALLER = -1;
- private static final int INVALID_DELAY = -1;
// Preallocated strings we are sending to tron, so we don't have to allocate a new one every
// time we log.
@@ -220,6 +221,8 @@
boolean mLoggedStartingWindowDrawn;
/** If the any app transitions have been logged as starting. */
boolean mLoggedTransitionStarting;
+ /** Whether any activity belonging to this transition has relaunched. */
+ boolean mRelaunched;
/** Non-null if the application has reported drawn but its window hasn't. */
@Nullable Runnable mPendingFullyDrawn;
@@ -351,6 +354,7 @@
*/
final int windowsFullyDrawnDelayMs;
final int activityRecordIdHashCode;
+ final boolean relaunched;
private TransitionInfoSnapshot(TransitionInfo info) {
this(info, info.mLastLaunchedActivity, INVALID_DELAY);
@@ -379,6 +383,7 @@
launchedActivityShortComponentName = launchedActivity.shortComponentName;
activityRecordIdHashCode = System.identityHashCode(launchedActivity);
this.windowsFullyDrawnDelayMs = windowsFullyDrawnDelayMs;
+ relaunched = info.mRelaunched;
}
@WaitResult.LaunchState int getLaunchState() {
@@ -386,7 +391,7 @@
case TYPE_TRANSITION_WARM_LAUNCH:
return LAUNCH_STATE_WARM;
case TYPE_TRANSITION_HOT_LAUNCH:
- return LAUNCH_STATE_HOT;
+ return relaunched ? LAUNCH_STATE_RELAUNCH : LAUNCH_STATE_HOT;
case TYPE_TRANSITION_COLD_LAUNCH:
return LAUNCH_STATE_COLD;
default:
@@ -673,6 +678,13 @@
}
}
+ void notifyActivityRelaunched(ActivityRecord r) {
+ final TransitionInfo info = getActiveTransitionInfo(r);
+ if (info != null) {
+ info.mRelaunched = true;
+ }
+ }
+
/** Makes sure that the reference to the removed activity is cleared. */
void notifyActivityRemoved(@NonNull ActivityRecord r) {
mLastTransitionInfo.remove(r);
@@ -800,13 +812,13 @@
FrameworkStatsLog.APP_START_CANCELED,
activity.info.applicationInfo.uid,
activity.packageName,
- convertAppStartTransitionType(type),
+ getAppStartTransitionType(type, info.mRelaunched),
activity.info.name);
if (DEBUG_METRICS) {
Slog.i(TAG, String.format("APP_START_CANCELED(%s, %s, %s, %s)",
activity.info.applicationInfo.uid,
activity.packageName,
- convertAppStartTransitionType(type),
+ getAppStartTransitionType(type, info.mRelaunched),
activity.info.name));
}
}
@@ -871,7 +883,7 @@
FrameworkStatsLog.APP_START_OCCURRED,
info.applicationInfo.uid,
info.packageName,
- convertAppStartTransitionType(info.type),
+ getAppStartTransitionType(info.type, info.relaunched),
info.launchedActivityName,
info.launchedActivityLaunchedFromPackage,
isInstantApp,
@@ -891,7 +903,7 @@
Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
info.applicationInfo.uid,
info.packageName,
- convertAppStartTransitionType(info.type),
+ getAppStartTransitionType(info.type, info.relaunched),
info.launchedActivityName,
info.launchedActivityLaunchedFromPackage));
}
@@ -918,7 +930,7 @@
Log.i(TAG, sb.toString());
}
- private int convertAppStartTransitionType(int tronType) {
+ private static int getAppStartTransitionType(int tronType, boolean relaunched) {
if (tronType == TYPE_TRANSITION_COLD_LAUNCH) {
return FrameworkStatsLog.APP_START_OCCURRED__TYPE__COLD;
}
@@ -926,17 +938,13 @@
return FrameworkStatsLog.APP_START_OCCURRED__TYPE__WARM;
}
if (tronType == TYPE_TRANSITION_HOT_LAUNCH) {
- return FrameworkStatsLog.APP_START_OCCURRED__TYPE__HOT;
+ return relaunched
+ ? FrameworkStatsLog.APP_START_OCCURRED__TYPE__RELAUNCH
+ : FrameworkStatsLog.APP_START_OCCURRED__TYPE__HOT;
}
return FrameworkStatsLog.APP_START_OCCURRED__TYPE__UNKNOWN;
}
- /** @return the last known window drawn delay of the given activity. */
- int getLastDrawnDelayMs(ActivityRecord r) {
- final TransitionInfo info = mLastTransitionInfo.get(r);
- return info != null ? info.mWindowsDrawnDelayMs : INVALID_DELAY;
- }
-
/** @see android.app.Activity#reportFullyDrawn */
TransitionInfoSnapshot logAppTransitionReportedDrawn(ActivityRecord r,
boolean restoredFromBundle) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3591779..c2016de 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -226,7 +226,7 @@
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.ResultInfo;
-import android.app.WaitResult.LaunchState;
+import android.app.WaitResult;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityRelaunchItem;
@@ -1558,10 +1558,6 @@
}
}
- // Application tokens start out hidden.
- setVisible(false);
- mVisibleRequested = false;
-
ColorDisplayService.ColorDisplayServiceInternal cds = LocalServices.getService(
ColorDisplayService.ColorDisplayServiceInternal.class);
cds.attachColorTransformController(packageName, mUserId,
@@ -3130,6 +3126,7 @@
}
void finishRelaunching() {
+ mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
unfreezeBounds();
if (mPendingRelaunchCount > 0) {
@@ -3506,7 +3503,7 @@
}
if (fromActivity.isVisible()) {
setVisible(true);
- mVisibleRequested = true;
+ setVisibleRequested(true);
mVisibleSetFromTransferredStartingWindow = true;
}
setClientVisible(fromActivity.mClientVisible);
@@ -4122,11 +4119,28 @@
void setVisible(boolean visible) {
if (visible != mVisible) {
mVisible = visible;
+ if (app != null) {
+ mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
+ }
scheduleAnimation();
}
}
/**
+ * This is the only place that writes {@link #mVisibleRequested} (except unit test). The caller
+ * outside of this class should use {@link #setVisibility}.
+ */
+ private void setVisibleRequested(boolean visible) {
+ if (visible == mVisibleRequested) {
+ return;
+ }
+ mVisibleRequested = visible;
+ if (app != null) {
+ mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
+ }
+ }
+
+ /**
* Set visibility on this {@link ActivityRecord}
*
* <p class="note"><strong>Note: </strong>This function might not update the visibility of
@@ -4189,7 +4203,7 @@
displayContent.mOpeningApps.remove(this);
displayContent.mClosingApps.remove(this);
waitingToShow = false;
- mVisibleRequested = visible;
+ setVisibleRequested(visible);
mLastDeferHidingClient = deferHidingClient;
if (!visible) {
@@ -4331,7 +4345,7 @@
ANIMATION_TYPE_APP_TRANSITION));
}
setVisible(visible);
- mVisibleRequested = visible;
+ setVisibleRequested(visible);
if (!visible) {
stopFreezingScreen(true, true);
} else {
@@ -4519,7 +4533,7 @@
detachChildren();
}
if (app != null) {
- app.computeProcessActivityState();
+ mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
}
switch (state) {
@@ -5435,14 +5449,15 @@
.getActivityMetricsLogger().notifyWindowsDrawn(this, timestampNs);
final boolean validInfo = info != null;
final int windowsDrawnDelayMs = validInfo ? info.windowsDrawnDelayMs : INVALID_DELAY;
- final @LaunchState int launchState = validInfo ? info.getLaunchState() : -1;
+ final @WaitResult.LaunchState int launchState =
+ validInfo ? info.getLaunchState() : WaitResult.LAUNCH_STATE_UNKNOWN;
// The activity may have been requested to be invisible (another activity has been launched)
// so there is no valid info. But if it is the current top activity (e.g. sleeping), the
// invalid state is still reported to make sure the waiting result is notified.
if (validInfo || this == getDisplayArea().topRunningActivity()) {
mTaskSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
windowsDrawnDelayMs, launchState);
- mTaskSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs);
+ mTaskSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs, launchState);
}
finishLaunchTickingLocked();
if (task != null) {
@@ -6393,13 +6408,10 @@
// The app is just becoming visible, and the parent Task has updated with the
// orientation request. Update the size compat mode.
updateSizeCompatMode();
- if (task.isOrganized()) {
- // WM Shell can override WM Core positioning (e.g. for letterboxing) so ensure
- // that WM Shell is called when an activity becomes visible. Without this, WM Core
- // will handle positioning instead of WM Shell when an app is reopened.
- mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
- task, /* force= */ true);
- }
+ // WM Shell can override WM Core positioning (e.g. for letterboxing) so ensure
+ // that WM Shell is called when an activity becomes visible. Without this, WM Core
+ // will handle positioning instead of WM Shell when an app is reopened.
+ task.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
}
}
@@ -7163,11 +7175,7 @@
} else {
mRelaunchReason = RELAUNCH_REASON_NONE;
}
- if (!attachedToProcess()) {
- ProtoLog.v(WM_DEBUG_CONFIGURATION,
- "Config is destroying non-running %s", this);
- destroyImmediately("config");
- } else if (mState == PAUSING) {
+ if (mState == PAUSING) {
// A little annoying: we are waiting for this activity to finish pausing. Let's not
// do anything now, but just flag that it needs to be restarted when done pausing.
ProtoLog.v(WM_DEBUG_CONFIGURATION,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index f472a5d..acee7b2 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -28,8 +28,6 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WaitResult.LAUNCH_STATE_COLD;
-import static android.app.WaitResult.LAUNCH_STATE_HOT;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -819,8 +817,6 @@
break;
}
case START_TASK_TO_FRONT: {
- mRequest.waitResult.launchState =
- r.attachedToProcess() ? LAUNCH_STATE_HOT : LAUNCH_STATE_COLD;
// ActivityRecord may represent a different activity, but it should not be
// in the resumed state.
if (r.nowVisible && r.isState(RESUMED)) {
@@ -1285,6 +1281,15 @@
return false;
}
+ // IME should always be allowed to start activity, like IME settings.
+ final WindowState imeWindow = mRootWindowContainer.getCurrentInputMethodWindow();
+ if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "Activity start allowed for active ime (" + callingUid + ")");
+ }
+ return false;
+ }
+
// App switching will be allowed if BAL app switching flag is not enabled, or if
// its app switching rule allows it.
// This is used to block background activity launch even if the app is still
@@ -1292,9 +1297,8 @@
final boolean appSwitchAllowed = mService.getBalAppSwitchesAllowed();
// don't abort if the callingUid has a visible window or is a persistent system process
- final int callingUidProcState = mService.getUidState(callingUid);
- final boolean callingUidHasAnyVisibleWindow =
- mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
+ final int callingUidProcState = mService.mActiveUids.getUidState(callingUid);
+ final boolean callingUidHasAnyVisibleWindow = mService.hasActiveVisibleWindow(callingUid);
final boolean isCallingUidForeground = callingUidHasAnyVisibleWindow
|| callingUidProcState == ActivityManager.PROCESS_STATE_TOP
|| callingUidProcState == ActivityManager.PROCESS_STATE_BOUND_TOP;
@@ -1312,10 +1316,10 @@
// take realCallingUid into consideration
final int realCallingUidProcState = (callingUid == realCallingUid)
? callingUidProcState
- : mService.getUidState(realCallingUid);
+ : mService.mActiveUids.getUidState(realCallingUid);
final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid)
? callingUidHasAnyVisibleWindow
- : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(realCallingUid);
+ : mService.hasActiveVisibleWindow(realCallingUid);
final boolean isRealCallingUidForeground = (callingUid == realCallingUid)
? isCallingUidForeground
: realCallingUidHasAnyVisibleWindow
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index f649a2f..a5df2a6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -514,7 +514,6 @@
public abstract void onUidActive(int uid, int procState);
public abstract void onUidInactive(int uid);
- public abstract void onActiveUidsCleared();
public abstract void onUidProcStateChanged(int uid, int procState);
public abstract void onUidAddedToPendingTempAllowlist(int uid, String tag);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e4c607d..ad6b505 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -390,7 +390,7 @@
private UserManagerService mUserManager;
private AppOpsManager mAppOpsManager;
/** All active uids in the system. */
- private final MirrorActiveUids mActiveUids = new MirrorActiveUids();
+ final MirrorActiveUids mActiveUids = new MirrorActiveUids();
private final SparseArray<String> mPendingTempAllowlist = new SparseArray<>();
/** All processes currently running that might have a window organized by name. */
final ProcessMap<WindowProcessController> mProcessNames = new ProcessMap<>();
@@ -4956,6 +4956,8 @@
printedAnything = true;
mTaskSupervisor.dump(pw, " ");
mTaskOrganizerController.dump(pw, " ");
+ mVisibleActivityProcessTracker.dump(pw, " ");
+ mActiveUids.dump(pw, " ");
}
if (!printedAnything) {
@@ -5986,13 +5988,12 @@
return null;
}
- int getUidState(int uid) {
- return mActiveUids.getUidState(uid);
- }
-
- boolean isUidForeground(int uid) {
- // A uid is considered to be foreground if it has a visible non-toast window.
- return mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid);
+ /** A uid is considered to be foreground if it has a visible non-toast window. */
+ boolean hasActiveVisibleWindow(int uid) {
+ if (mVisibleActivityProcessTracker.hasVisibleActivity(uid)) {
+ return true;
+ }
+ return mActiveUids.hasNonAppVisibleWindow(uid);
}
boolean isDeviceOwner(int uid) {
@@ -7283,12 +7284,6 @@
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
@Override
- public void onActiveUidsCleared() {
- mActiveUids.onActiveUidsCleared();
- }
-
- @HotPath(caller = HotPath.OOM_ADJUSTMENT)
- @Override
public void onUidProcStateChanged(int uid, int procState) {
mActiveUids.onUidProcStateChanged(uid, procState);
}
@@ -7432,9 +7427,7 @@
@Override
public boolean isUidForeground(int uid) {
- synchronized (mGlobalLock) {
- return ActivityTaskManagerService.this.isUidForeground(uid);
- }
+ return ActivityTaskManagerService.this.hasActiveVisibleWindow(uid);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e91a6d8..37f04ce 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -254,6 +254,12 @@
private LaunchParamsController mLaunchParamsController;
/**
+ * The processes with changed states that should eventually call
+ * {@link WindowProcessController#computeProcessActivityState}.
+ */
+ private final ArrayList<WindowProcessController> mActivityStateChangedProcs = new ArrayList<>();
+
+ /**
* Maps the task identifier that activities are currently being started in to the userId of the
* task. Each time a new task is created, the entry for the userId of the task is incremented
*/
@@ -553,14 +559,16 @@
// down to the max limit while they are still waiting to finish.
mFinishingActivities.remove(r);
- stopWaitingForActivityVisible(r, WaitResult.INVALID_DELAY);
+ stopWaitingForActivityVisible(r);
}
+ /** There is no valid launch time, just stop waiting. */
void stopWaitingForActivityVisible(ActivityRecord r) {
- stopWaitingForActivityVisible(r, getActivityMetricsLogger().getLastDrawnDelayMs(r));
+ stopWaitingForActivityVisible(r, WaitResult.INVALID_DELAY, WaitResult.LAUNCH_STATE_UNKNOWN);
}
- void stopWaitingForActivityVisible(ActivityRecord r, long totalTime) {
+ void stopWaitingForActivityVisible(ActivityRecord r, long totalTime,
+ @WaitResult.LaunchState int launchState) {
boolean changed = false;
for (int i = mWaitingForActivityVisible.size() - 1; i >= 0; --i) {
final WaitInfo w = mWaitingForActivityVisible.get(i);
@@ -570,6 +578,7 @@
result.timeout = false;
result.who = w.getComponent();
result.totalTime = totalTime;
+ result.launchState = launchState;
mWaitingForActivityVisible.remove(w);
}
}
@@ -2307,6 +2316,9 @@
/** Ends a batch of visibility updates. */
void endActivityVisibilityUpdate() {
mVisibilityTransactionDepth--;
+ if (mVisibilityTransactionDepth == 0) {
+ computeProcessActivityStateBatch();
+ }
}
/** Returns {@code true} if the caller is on the path to update visibility. */
@@ -2315,6 +2327,34 @@
}
/**
+ * Called when the state or visibility of an attached activity is changed.
+ *
+ * @param wpc The process who owns the activity.
+ * @param forceBatch Whether to put the changed record to a pending list. If the caller is not
+ * in the path of visibility update ({@link #inActivityVisibilityUpdate}), it
+ * must call {@link #computeProcessActivityStateBatch} manually.
+ */
+ void onProcessActivityStateChanged(WindowProcessController wpc, boolean forceBatch) {
+ if (forceBatch || inActivityVisibilityUpdate()) {
+ if (!mActivityStateChangedProcs.contains(wpc)) {
+ mActivityStateChangedProcs.add(wpc);
+ }
+ return;
+ }
+ wpc.computeProcessActivityState();
+ }
+
+ void computeProcessActivityStateBatch() {
+ if (mActivityStateChangedProcs.isEmpty()) {
+ return;
+ }
+ for (int i = mActivityStateChangedProcs.size() - 1; i >= 0; i--) {
+ mActivityStateChangedProcs.get(i).computeProcessActivityState();
+ }
+ mActivityStateChangedProcs.clear();
+ }
+
+ /**
* Begin deferring resume to avoid duplicate resumes in one pass.
*/
void beginDeferResume() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ec95089..4133ea2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -626,12 +626,6 @@
*/
private boolean mInEnsureActivitiesVisible = false;
- /**
- * Last window to be requested focus via {@code SurfaceControl.Transaction#setFocusedWindow} to
- * prevent duplicate requests to input.
- */
- WindowState mLastRequestedFocus = null;
-
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
WindowStateAnimator winAnimator = w.mWinAnimator;
final ActivityRecord activity = w.mActivityRecord;
@@ -5384,20 +5378,6 @@
forAllTasks((t) -> { t.getRootTask().removeChild(t, "removeAllTasks"); });
}
- /**
- * Similar to {@link RootWindowContainer#isAnyNonToastWindowVisibleForUid(int)}, but
- * used for pid.
- */
- boolean isAnyNonToastWindowVisibleForPid(int pid) {
- final PooledPredicate p = PooledLambda.obtainPredicate(
- WindowState::isNonToastWindowVisibleForPid,
- PooledLambda.__(WindowState.class), pid);
-
- final WindowState w = getWindow(p);
- p.recycle();
- return w != null;
- }
-
Context getDisplayUiContext() {
return mDisplayPolicy.getSystemUiContext();
}
diff --git a/services/core/java/com/android/server/wm/ImpressionAttestationController.java b/services/core/java/com/android/server/wm/ImpressionAttestationController.java
new file mode 100644
index 0000000..4793e1b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ImpressionAttestationController.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.service.attestation.ImpressionAttestationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.attestation.IImpressionAttestationService;
+import android.service.attestation.ImpressionAttestationService;
+import android.service.attestation.ImpressionToken;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+
+/**
+ * Handles requests into {@link ImpressionAttestationService}
+ *
+ * Do not hold the {@link WindowManagerService#mGlobalLock} when calling methods since they are
+ * blocking calls into another service.
+ */
+public class ImpressionAttestationController {
+ private static final String TAG = "ImpressionAttestationController";
+ private static final boolean DEBUG = false;
+
+ private final Object mServiceConnectionLock = new Object();
+
+ @GuardedBy("mServiceConnectionLock")
+ private ImpressionAttestationServiceConnection mServiceConnection;
+
+ private final Context mContext;
+
+ /**
+ * Lock used for the cached {@link #mImpressionAlgorithms} array
+ */
+ private final Object mImpressionAlgorithmsLock = new Object();
+
+ @GuardedBy("mImpressionAlgorithmsLock")
+ private String[] mImpressionAlgorithms;
+
+ private final Handler mHandler;
+
+ private final String mSalt;
+
+ private interface Command {
+ void run(IImpressionAttestationService service) throws RemoteException;
+ }
+
+ ImpressionAttestationController(Context context) {
+ mContext = context;
+ mHandler = new Handler(Looper.getMainLooper());
+ mSalt = UUID.randomUUID().toString();
+ }
+
+ String[] getSupportedImpressionAlgorithms() {
+ // We have a separate lock for the impression algorithm array since it doesn't need to make
+ // the request through the service connection. Instead, we have a lock to ensure we can
+ // properly cache the impression algorithms array so we don't need to call into the
+ // ExtServices process for each request.
+ synchronized (mImpressionAlgorithmsLock) {
+ // Already have cached values
+ if (mImpressionAlgorithms != null) {
+ return mImpressionAlgorithms;
+ }
+
+ final ServiceInfo serviceInfo = getServiceInfo();
+ if (serviceInfo == null) return null;
+
+ final PackageManager pm = mContext.getPackageManager();
+ final Resources res;
+ try {
+ res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Error getting application resources for " + serviceInfo, e);
+ return null;
+ }
+
+ final int resourceId = serviceInfo.metaData.getInt(
+ SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS);
+ mImpressionAlgorithms = res.getStringArray(resourceId);
+
+ return mImpressionAlgorithms;
+ }
+ }
+
+ boolean verifyImpressionToken(ImpressionToken impressionToken) {
+ final SyncCommand syncCommand = new SyncCommand();
+ Bundle results = syncCommand.run((service, remoteCallback) -> {
+ try {
+ service.verifyImpressionToken(mSalt, impressionToken, remoteCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to invoke verifyImpressionToken command");
+ }
+ });
+
+ return results.getBoolean(ImpressionAttestationService.EXTRA_VERIFICATION_STATUS);
+ }
+
+ ImpressionToken generateImpressionToken(HardwareBuffer screenshot, Rect bounds,
+ String hashAlgorithm) {
+ final SyncCommand syncCommand = new SyncCommand();
+ Bundle results = syncCommand.run((service, remoteCallback) -> {
+ try {
+ service.generateImpressionToken(mSalt, screenshot, bounds, hashAlgorithm,
+ remoteCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to invoke generateImpressionToken command", e);
+ }
+ });
+
+ return results.getParcelable(ImpressionAttestationService.EXTRA_IMPRESSION_TOKEN);
+ }
+
+ /**
+ * Run a command, starting the service connection if necessary.
+ */
+ private void connectAndRun(@NonNull Command command) {
+ synchronized (mServiceConnectionLock) {
+ mHandler.resetTimeoutMessage();
+ if (mServiceConnection == null) {
+ if (DEBUG) Slog.v(TAG, "creating connection");
+
+ // Create the connection
+ mServiceConnection = new ImpressionAttestationServiceConnection();
+
+ final ComponentName component = getServiceComponentName();
+ if (DEBUG) Slog.v(TAG, "binding to: " + component);
+ if (component != null) {
+ final Intent intent = new Intent();
+ intent.setComponent(component);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.bindServiceAsUser(intent, mServiceConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.CURRENT);
+ if (DEBUG) Slog.v(TAG, "bound");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ mServiceConnection.runCommandLocked(command);
+ }
+ }
+
+ @Nullable
+ private ServiceInfo getServiceInfo() {
+ final String packageName =
+ mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+ if (packageName == null) {
+ Slog.w(TAG, "no external services package!");
+ return null;
+ }
+
+ final Intent intent = new Intent(ImpressionAttestationService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ return resolveInfo.serviceInfo;
+ }
+
+ @Nullable
+ private ComponentName getServiceComponentName() {
+ final ServiceInfo serviceInfo = getServiceInfo();
+ if (serviceInfo == null) return null;
+
+ final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ if (!Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE
+ .equals(serviceInfo.permission)) {
+ Slog.w(TAG, name.flattenToShortString() + " requires permission "
+ + Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE);
+ return null;
+ }
+
+ if (DEBUG) Slog.v(TAG, "getServiceComponentName(): " + name);
+ return name;
+ }
+
+ private class SyncCommand {
+ private static final int WAIT_TIME_S = 5;
+ private Bundle mResult;
+ private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+ public Bundle run(BiConsumer<IImpressionAttestationService, RemoteCallback> func) {
+ connectAndRun(service -> {
+ RemoteCallback callback = new RemoteCallback(result -> {
+ mResult = result;
+ mCountDownLatch.countDown();
+ });
+ func.accept(service, callback);
+ });
+
+ try {
+ mCountDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to wait for command", e);
+ }
+
+ return mResult;
+ }
+ }
+
+ private class ImpressionAttestationServiceConnection implements ServiceConnection {
+ @GuardedBy("mServiceConnectionLock")
+ private IImpressionAttestationService mRemoteService;
+
+ @GuardedBy("mServiceConnectionLock")
+ private ArrayList<Command> mQueuedCommands;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) Slog.v(TAG, "onServiceConnected(): " + name);
+ synchronized (mServiceConnectionLock) {
+ mRemoteService = IImpressionAttestationService.Stub.asInterface(service);
+ if (mQueuedCommands != null) {
+ final int size = mQueuedCommands.size();
+ if (DEBUG) Slog.d(TAG, "running " + size + " queued commands");
+ for (int i = 0; i < size; i++) {
+ final Command queuedCommand = mQueuedCommands.get(i);
+ try {
+ if (DEBUG) Slog.v(TAG, "running queued command #" + i);
+ queuedCommand.run(mRemoteService);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "exception calling " + name + ": " + e);
+ }
+ }
+ mQueuedCommands = null;
+ } else if (DEBUG) {
+ Slog.d(TAG, "no queued commands");
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) Slog.v(TAG, "onServiceDisconnected(): " + name);
+ synchronized (mServiceConnectionLock) {
+ mRemoteService = null;
+ }
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ if (DEBUG) Slog.v(TAG, "onBindingDied(): " + name);
+ synchronized (mServiceConnectionLock) {
+ mRemoteService = null;
+ }
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ if (DEBUG) Slog.v(TAG, "onNullBinding(): " + name);
+ synchronized (mServiceConnectionLock) {
+ mRemoteService = null;
+ }
+ }
+
+ /**
+ * Only call while holding {@link #mServiceConnectionLock}
+ */
+ private void runCommandLocked(Command command) {
+ if (mRemoteService == null) {
+ if (DEBUG) Slog.d(TAG, "service is null; queuing command");
+ if (mQueuedCommands == null) {
+ mQueuedCommands = new ArrayList<>(1);
+ }
+ mQueuedCommands.add(command);
+ } else {
+ try {
+ if (DEBUG) Slog.v(TAG, "running command right away");
+ command.run(mRemoteService);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "exception calling service: " + e);
+ }
+ }
+ }
+ }
+
+ private class Handler extends android.os.Handler {
+ static final long SERVICE_SHUTDOWN_TIMEOUT_MILLIS = 10000; // 10s
+ static final int MSG_SERVICE_SHUTDOWN_TIMEOUT = 1;
+
+ Handler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_SERVICE_SHUTDOWN_TIMEOUT) {
+ if (DEBUG) {
+ Slog.v(TAG, "Shutting down service");
+ }
+ synchronized (mServiceConnectionLock) {
+ if (mServiceConnection != null) {
+ mContext.unbindService(mServiceConnection);
+ mServiceConnection = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Set a timer for {@link #SERVICE_SHUTDOWN_TIMEOUT_MILLIS} so we can tear down the service
+ * if it's inactive. The requests will be coming from apps so it's hard to tell how often
+ * the requests can come in. Therefore, we leave the service running if requests continue
+ * to come in. Once there's been no activity for 10s, we can shut down the service and
+ * restart when we get a new request.
+ */
+ void resetTimeoutMessage() {
+ if (DEBUG) {
+ Slog.v(TAG, "Reset shutdown message");
+ }
+ removeMessages(MSG_SERVICE_SHUTDOWN_TIMEOUT);
+ sendEmptyMessageDelayed(MSG_SERVICE_SHUTDOWN_TIMEOUT, SERVICE_SHUTDOWN_TIMEOUT_MILLIS);
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 457df4e..efd9e2a 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -73,8 +73,8 @@
final class InputMonitor {
private final WindowManagerService mService;
- // Current window with input focus for keys and other non-touch events. May be null.
- private WindowState mInputFocus;
+ // Current input focus token for keys and other non-touch events. May be null.
+ private IBinder mInputFocus = null;
// When true, need to call updateInputWindowsLw().
private boolean mUpdateInputWindowsNeeded = true;
@@ -377,31 +377,62 @@
}
/**
- * Called when the current input focus changes.
+ * Called when the current input focus changes. Will apply it in next updateInputWindows.
* Layer assignment is assumed to be complete by the time this is called.
*/
- public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
+ void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Input focus has changed to %s display=%d",
newWindow, mDisplayId);
+ final IBinder focus = newWindow != null ? newWindow.mInputChannelToken : null;
+ if (focus == mInputFocus) {
+ return;
+ }
- if (newWindow != mInputFocus) {
- if (newWindow != null && newWindow.canReceiveKeys()) {
- // Displaying a window implicitly causes dispatching to be unpaused.
- // This is to protect against bugs if someone pauses dispatching but
- // forgets to resume.
- newWindow.mToken.paused = false;
- }
+ if (newWindow != null && newWindow.canReceiveKeys()) {
+ // Displaying a window implicitly causes dispatching to be unpaused.
+ // This is to protect against bugs if someone pauses dispatching but
+ // forgets to resume.
+ newWindow.mToken.paused = false;
+ }
- mInputFocus = newWindow;
- setUpdateInputWindowsNeededLw();
+ setUpdateInputWindowsNeededLw();
- if (updateInputWindows) {
- updateInputWindowsLw(false /*force*/);
- }
+ if (updateInputWindows) {
+ updateInputWindowsLw(false /*force*/);
}
}
- public void setFocusedAppLw(ActivityRecord newApp) {
+ /**
+ * Called when the current input focus changes.
+ */
+ private void updateInputFocusRequest() {
+ final WindowState focus = mDisplayContent.mCurrentFocus;
+ final IBinder focusToken = focus != null ? focus.mInputChannelToken : null;
+
+ if (focusToken == null) {
+ mInputFocus = null;
+ return;
+ }
+
+ if (!focus.mWinAnimator.hasSurface() || !focus.mInputWindowHandle.isFocusable()) {
+ Slog.v(TAG_WM, "Focus not requested for window=%" + focus
+ + " because it has no surface or is not focusable.");
+ mInputFocus = null;
+ return;
+ }
+
+ if (focusToken == mInputFocus) {
+ return;
+ }
+
+ mInputFocus = focusToken;
+ mInputTransaction.setFocusedWindow(mInputFocus, mDisplayId);
+ EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + focus,
+ "reason=UpdateInputWindows");
+ ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", focus);
+ }
+
+ void setFocusedAppLw(ActivityRecord newApp) {
// Focused app has changed.
mService.mInputManager.setFocusedApplication(mDisplayId,
newApp != null ? newApp.getInputApplicationHandle(true /* update */) : null);
@@ -482,30 +513,6 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- private void updateInputFocusRequest() {
- if (mDisplayContent.mLastRequestedFocus == mDisplayContent.mCurrentFocus) {
- return;
- }
-
- final WindowState focus = mDisplayContent.mCurrentFocus;
- if (focus == null || focus.mInputChannelToken == null) {
- mDisplayContent.mLastRequestedFocus = focus;
- return;
- }
-
- if (!focus.mWinAnimator.hasSurface()) {
- Slog.v(TAG_WM, "Focus not requested for window=%" + focus
- + " because it has no surface.");
- return;
- }
-
- mInputTransaction.setFocusedWindow(focus.mInputChannelToken, mDisplayId);
- EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
- "Focus request " + focus, "reason=UpdateInputWindows");
- mDisplayContent.mLastRequestedFocus = focus;
- ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", focus);
- }
-
@Override
public void accept(WindowState w) {
final InputWindowHandleWrapper inputWindowHandle = w.mInputWindowHandle;
diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
index 9339f34..7a4d13c 100644
--- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
+++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
@@ -63,6 +63,10 @@
return mHandle.displayId;
}
+ boolean isFocusable() {
+ return mHandle.focusable;
+ }
+
InputApplicationHandle getInputApplicationHandle() {
return mHandle.inputApplicationHandle;
}
diff --git a/services/core/java/com/android/server/wm/MirrorActiveUids.java b/services/core/java/com/android/server/wm/MirrorActiveUids.java
index 0047942..4e7f1d4 100644
--- a/services/core/java/com/android/server/wm/MirrorActiveUids.java
+++ b/services/core/java/com/android/server/wm/MirrorActiveUids.java
@@ -18,7 +18,10 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
-import android.util.SparseIntArray;
+import android.app.ActivityManager.ProcessState;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
/**
* This is a partial mirror of {@link @com.android.server.am.ActiveUids}. It is already thread
@@ -26,28 +29,64 @@
* adjustment) or getting state from window manager (background start check).
*/
class MirrorActiveUids {
- private SparseIntArray mUidStates = new SparseIntArray();
+ private final SparseArray<UidRecord> mUidStates = new SparseArray<>();
synchronized void onUidActive(int uid, int procState) {
- mUidStates.put(uid, procState);
+ UidRecord r = mUidStates.get(uid);
+ if (r == null) {
+ r = new UidRecord();
+ mUidStates.put(uid, r);
+ }
+ r.mProcState = procState;
}
synchronized void onUidInactive(int uid) {
mUidStates.delete(uid);
}
- synchronized void onActiveUidsCleared() {
- mUidStates.clear();
- }
-
synchronized void onUidProcStateChanged(int uid, int procState) {
- final int index = mUidStates.indexOfKey(uid);
- if (index >= 0) {
- mUidStates.setValueAt(index, procState);
+ final UidRecord r = mUidStates.get(uid);
+ if (r != null) {
+ r.mProcState = procState;
}
}
- synchronized int getUidState(int uid) {
- return mUidStates.get(uid, PROCESS_STATE_NONEXISTENT);
+ synchronized @ProcessState int getUidState(int uid) {
+ final UidRecord r = mUidStates.get(uid);
+ return r != null ? r.mProcState : PROCESS_STATE_NONEXISTENT;
+ }
+
+ /** Called when the surface of non-application (exclude toast) window is shown or hidden. */
+ synchronized void onNonAppSurfaceVisibilityChanged(int uid, boolean visible) {
+ final UidRecord r = mUidStates.get(uid);
+ if (r != null) {
+ r.mNumNonAppVisibleWindow += visible ? 1 : -1;
+ }
+ }
+
+ /**
+ * Returns {@code true} if the uid has any non-application (exclude toast) window currently
+ * visible to the user. The application window visibility of a uid can be found from
+ * {@link VisibleActivityProcessTracker}.
+ */
+ synchronized boolean hasNonAppVisibleWindow(int uid) {
+ final UidRecord r = mUidStates.get(uid);
+ return r != null && r.mNumNonAppVisibleWindow > 0;
+ }
+
+ synchronized void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix + "NumNonAppVisibleWindowByUid:[");
+ for (int i = mUidStates.size() - 1; i >= 0; i--) {
+ final UidRecord r = mUidStates.valueAt(i);
+ if (r.mNumNonAppVisibleWindow > 0) {
+ pw.print(" " + mUidStates.keyAt(i) + ":" + r.mNumNonAppVisibleWindow);
+ }
+ }
+ pw.println("]");
+ }
+
+ private static final class UidRecord {
+ @ProcessState int mProcState;
+ int mNumNonAppVisibleWindow;
}
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index b117699..823dc51 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -385,12 +385,9 @@
mWindowManager.executeAppTransition();
final Task rootTask = targetStack.getRootTask();
- if (rootTask.isOrganized()) {
- // Client state may have changed during the recents animation, so force
- // send task info so the client can synchronize its state.
- mService.mTaskOrganizerController.dispatchTaskInfoChanged(
- rootTask, true /* force */);
- }
+ // Client state may have changed during the recents animation, so force
+ // send task info so the client can synchronize its state.
+ rootTask.dispatchTaskInfoChangedIfNeeded(true /* force */);
} catch (Exception e) {
Slog.e(TAG, "Failed to clean up recents activity", e);
throw e;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a550e15..7073548 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -276,7 +276,6 @@
// Whether tasks have moved and we need to rank the tasks before next OOM scoring
private boolean mTaskLayersChanged = true;
private int mTmpTaskLayerRank;
- private final ArraySet<WindowProcessController> mTmpTaskLayerChangedProcs = new ArraySet<>();
private final LockedScheduler mRankTaskLayersScheduler;
private boolean mTmpBoolean;
@@ -578,23 +577,6 @@
}
/**
- * Returns {@code true} if the callingUid has any non-toast window currently visible to the
- * user. Also ignores {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_STARTING},
- * since those windows don't belong to apps.
- *
- * @see WindowState#isNonToastOrStarting()
- */
- boolean isAnyNonToastWindowVisibleForUid(int callingUid) {
- final PooledPredicate p = PooledLambda.obtainPredicate(
- WindowState::isNonToastWindowVisibleForUid,
- PooledLambda.__(WindowState.class), callingUid);
-
- final WindowState w = getWindow(p);
- p.recycle();
- return w != null;
- }
-
- /**
* Returns the app window token for the input binder if it exist in the system.
* NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
* AppWindowToken represents an activity which can only exist on one display.
@@ -1016,9 +998,6 @@
}
}
- // Remove all deferred displays stacks, tasks, and activities.
- handleCompleteDeferredRemoval();
-
forAllDisplays(dc -> {
dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
dc.updateSystemGestureExclusion();
@@ -1367,7 +1346,8 @@
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
DisplayContent dc = mChildren.get(i);
- if (dc.isAnyNonToastWindowVisibleForPid(pid)) {
+ if (dc.getWindow(w -> pid == w.mSession.mPid && w.isVisibleNow()
+ && w.mAttrs.type != WindowManager.LayoutParams.TYPE_TOAST) != null) {
outContexts.add(dc.getDisplayUiContext());
}
}
@@ -2720,16 +2700,16 @@
if (task.mLayerRank != oldRank) {
task.forAllActivities(activity -> {
if (activity.hasProcess()) {
- mTmpTaskLayerChangedProcs.add(activity.app);
+ mTaskSupervisor.onProcessActivityStateChanged(activity.app,
+ true /* forceBatch */);
}
});
}
}, true /* traverseTopToBottom */);
- for (int i = mTmpTaskLayerChangedProcs.size() - 1; i >= 0; i--) {
- mTmpTaskLayerChangedProcs.valueAt(i).computeProcessActivityState();
+ if (!mTaskSupervisor.inActivityVisibilityUpdate()) {
+ mTaskSupervisor.computeProcessActivityStateBatch();
}
- mTmpTaskLayerChangedProcs.clear();
}
void clearOtherAppTimeTrackers(AppTimeTracker except) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3caf86c..ebf5989 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2041,9 +2041,7 @@
}
}
- if (isOrganized()) {
- mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, false /* force */);
- }
+ dispatchTaskInfoChangedIfNeeded(false /* force */);
}
private static boolean setTaskDescriptionFromActivityAboveRoot(
@@ -2272,8 +2270,8 @@
}
// If the task organizer has changed, then it will already be receiving taskAppeared with
// the latest task-info thus the task-info won't have changed.
- if (!taskOrgChanged && isOrganized()) {
- mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, false /* force */);
+ if (!taskOrgChanged) {
+ dispatchTaskInfoChangedIfNeeded(false /* force */);
}
}
@@ -4100,6 +4098,7 @@
info.parentTaskId = rootTask == getParent() && rootTask.mCreatedByOrganizer
? rootTask.mTaskId
: INVALID_TASK_ID;
+ info.isFocused = isFocused();
}
@Nullable PictureInPictureParams getPictureInPictureParams() {
@@ -4120,8 +4119,7 @@
letterboxActivityBounds)) {
mLetterboxActivityBounds = Rect.copyOrNull(letterboxActivityBounds);
// Forcing update to reduce visual jank during the transition.
- mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
- this, /* force= */ true);
+ dispatchTaskInfoChangedIfNeeded(true /* force */);
}
}
@@ -5067,12 +5065,11 @@
*/
void onWindowFocusChanged(boolean hasFocus) {
updateShadowsRadius(hasFocus, getSyncTransaction());
+ dispatchTaskInfoChangedIfNeeded(false /* force */);
}
void onPictureInPictureParamsChanged() {
- if (isOrganized()) {
- mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, true /* force */);
- }
+ dispatchTaskInfoChangedIfNeeded(true /* force */);
}
/**
@@ -7447,9 +7444,7 @@
@Override
void onChildPositionChanged(WindowContainer child) {
- if (isOrganized()) {
- mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, false /* force */);
- }
+ dispatchTaskInfoChangedIfNeeded(false /* force */);
if (!mChildren.contains(child)) {
return;
@@ -7604,6 +7599,12 @@
return super.getBounds();
}
+ void dispatchTaskInfoChangedIfNeeded(boolean force) {
+ if (isOrganized()) {
+ mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, force);
+ }
+ }
+
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTraceLogLevel int logLevel) {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index e64c047..009a7ef 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -27,7 +27,6 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityManager.TaskDescription;
import android.app.WindowConfiguration;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -52,7 +51,6 @@
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Consumer;
@@ -538,16 +536,7 @@
}
mTmpTaskInfo.configuration.unset();
task.fillTaskInfo(mTmpTaskInfo);
- boolean changed = lastInfo == null
- || mTmpTaskInfo.topActivityType != lastInfo.topActivityType
- || mTmpTaskInfo.isResizeable != lastInfo.isResizeable
- || !Objects.equals(
- mTmpTaskInfo.positionInParent,
- lastInfo.positionInParent)
- || isLetterboxInfoChanged(lastInfo, mTmpTaskInfo)
- || mTmpTaskInfo.pictureInPictureParams != lastInfo.pictureInPictureParams
- || mTmpTaskInfo.getWindowingMode() != lastInfo.getWindowingMode()
- || !TaskDescription.equals(mTmpTaskInfo.taskDescription, lastInfo.taskDescription);
+ boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo);
if (!changed) {
int cfgChanges = mTmpTaskInfo.configuration.diff(lastInfo.configuration);
final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
@@ -582,20 +571,6 @@
}
}
- private boolean isLetterboxInfoChanged(
- final RunningTaskInfo lastInfo, final RunningTaskInfo currentInfo) {
- return !Objects.equals(
- currentInfo.letterboxActivityBounds,
- lastInfo.letterboxActivityBounds)
- || !Objects.equals(
- currentInfo.getConfiguration().windowConfiguration.getBounds(),
- lastInfo.getConfiguration().windowConfiguration.getBounds())
- || !Objects.equals(
- currentInfo.getConfiguration().windowConfiguration.getMaxBounds(),
- lastInfo.getConfiguration().windowConfiguration.getMaxBounds())
- || !Objects.equals(currentInfo.parentBounds, lastInfo.parentBounds);
- }
-
@Override
public WindowContainerToken getImeTarget(int displayId) {
enforceTaskPermission("getImeTarget()");
diff --git a/services/core/java/com/android/server/wm/VisibleActivityProcessTracker.java b/services/core/java/com/android/server/wm/VisibleActivityProcessTracker.java
index 7f62630..0e2b71c 100644
--- a/services/core/java/com/android/server/wm/VisibleActivityProcessTracker.java
+++ b/services/core/java/com/android/server/wm/VisibleActivityProcessTracker.java
@@ -21,7 +21,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
+import java.io.PrintWriter;
import java.util.concurrent.Executor;
+import java.util.function.Predicate;
/**
* A quick lookup for all processes with visible activities. It also tracks the CPU usage of
@@ -70,10 +72,22 @@
}
boolean hasResumedActivity(int uid) {
+ return match(uid, WindowProcessController::hasResumedActivity);
+ }
+
+ /**
+ * Returns {@code true} if the uid has a process that contains an activity with
+ * {@link ActivityRecord#mVisibleRequested} or {@link ActivityRecord#isVisible()} is true.
+ */
+ boolean hasVisibleActivity(int uid) {
+ return match(uid, null /* predicate */);
+ }
+
+ private boolean match(int uid, Predicate<WindowProcessController> predicate) {
synchronized (mProcMap) {
for (int i = mProcMap.size() - 1; i >= 0; i--) {
final WindowProcessController wpc = mProcMap.keyAt(i);
- if (wpc.mUid == uid && wpc.hasResumedActivity()) {
+ if (wpc.mUid == uid && (predicate == null || predicate.test(wpc))) {
return true;
}
}
@@ -87,6 +101,16 @@
}
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix + "VisibleActivityProcess:[");
+ synchronized (mProcMap) {
+ for (int i = mProcMap.size() - 1; i >= 0; i--) {
+ pw.print(" " + mProcMap.keyAt(i));
+ }
+ }
+ pw.println("]");
+ }
+
/**
* Get CPU time in background thread because it will access proc files or the lock of cpu
* tracker is held by a background thread.
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index ff5c174..f627ca6 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -145,6 +145,9 @@
ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
mService.openSurfaceTransaction();
try {
+ // Remove all deferred displays, tasks, and activities.
+ mService.mRoot.handleCompleteDeferredRemoval();
+
final AccessibilityController accessibilityController =
mService.mAccessibilityController;
final int numDisplays = mDisplayContentsAnimators.size();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b25fbc0..4574be7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1112,7 +1112,7 @@
// descendant. E.g. if a display is pending to be removed because it contains an
// activity with {@link ActivityRecord#mIsExiting} is true, the display may be
// removed when completing the removal of the last activity from
- // {@link ActivityRecord#checkCompleteDeferredRemoval}.
+ // {@link ActivityRecord#handleCompleteDeferredRemoval}.
return false;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7c7dd6f..580c088 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2978,7 +2978,7 @@
displayContent, true /* includingParents */);
}
}
- syncInputTransactions();
+ syncInputTransactions(true /* waitForAnimations */);
}
/**
@@ -7979,7 +7979,8 @@
}
@Override
- public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode) {
+ public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode,
+ boolean waitForAnimations) {
boolean isDown;
boolean isUp;
@@ -7998,21 +7999,23 @@
// For all mouse events, also sync before injecting.
// For ACTION_UP, sync after injecting.
if (isDown || isMouseEvent) {
- syncInputTransactions();
+ syncInputTransactions(waitForAnimations);
}
final boolean result =
LocalServices.getService(InputManagerInternal.class).injectInputEvent(ev, mode);
if (isUp) {
- syncInputTransactions();
+ syncInputTransactions(waitForAnimations);
}
return result;
}
@Override
- public void syncInputTransactions() {
+ public void syncInputTransactions(boolean waitForAnimations) {
final long token = Binder.clearCallingIdentity();
try {
- waitForAnimationsToComplete();
+ if (waitForAnimations) {
+ waitForAnimationsToComplete();
+ }
// Collect all input transactions from all displays to make sure we could sync all input
// windows at same time.
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 88fd361..c55f059 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -607,7 +607,7 @@
private boolean isBoundByForegroundUid() {
for (int i = mBoundClientUids.size() - 1; i >= 0; --i) {
- if (mAtm.isUidForeground(mBoundClientUids.valueAt(i))) {
+ if (mAtm.hasActiveVisibleWindow(mBoundClientUids.valueAt(i))) {
return true;
}
}
@@ -1049,16 +1049,6 @@
& (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
for (int i = mActivities.size() - 1; i >= 0; i--) {
final ActivityRecord r = mActivities.get(i);
- if (r.app != this) {
- Slog.e(TAG, "Found activity " + r + " in proc activity list using " + r.app
- + " instead of expected " + this);
- if (r.app == null || (r.app.mUid == mUid)) {
- // Only fix things up when they look valid.
- r.setProcess(this);
- } else {
- continue;
- }
- }
if (r.isVisible()) {
stateFlags |= ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE;
}
@@ -1115,6 +1105,7 @@
/** Called when the process has some oom related changes and it is going to update oom-adj. */
private void prepareOomAdjustment() {
mAtm.mRootWindowContainer.rankTaskLayersIfNeeded();
+ mAtm.mTaskSupervisor.computeProcessActivityStateBatch();
}
public int computeRelaunchReason() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b5509f6..0a8ff4d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3286,6 +3286,11 @@
logExclusionRestrictions(EXCLUSION_LEFT);
logExclusionRestrictions(EXCLUSION_RIGHT);
}
+ // Exclude toast because legacy apps may show toast window by themselves, so the misused
+ // apps won't always be considered as foreground state.
+ if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST) {
+ mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown);
+ }
}
private void logExclusionRestrictions(int side) {
@@ -5624,23 +5629,6 @@
true /* ignoreVisibility */));
}
- /**
- * Returns {@code true} if this window is not {@link WindowManager.LayoutParams#TYPE_TOAST}
- * or {@link WindowManager.LayoutParams#TYPE_APPLICATION_STARTING},
- * since this window doesn't belong to apps.
- */
- boolean isNonToastOrStarting() {
- return mAttrs.type != TYPE_TOAST && mAttrs.type != TYPE_APPLICATION_STARTING;
- }
-
- boolean isNonToastWindowVisibleForUid(int callingUid) {
- return getOwningUid() == callingUid && isNonToastOrStarting() && isVisibleNow();
- }
-
- boolean isNonToastWindowVisibleForPid(int pid) {
- return mSession.mPid == pid && isNonToastOrStarting() && isVisibleNow();
- }
-
void setViewVisibility(int viewVisibility) {
mViewVisibility = viewVisibility;
}
diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp
index fb55e75..d1918d8 100644
--- a/services/core/xsd/Android.bp
+++ b/services/core/xsd/Android.bp
@@ -28,3 +28,10 @@
api_dir: "cec-config/schema",
package_name: "com.android.server.hdmi.cec.config",
}
+
+xsd_config {
+ name: "device-state-config",
+ srcs: ["device-state-config/device-state-config.xsd"],
+ api_dir: "device-state-config/schema",
+ package_name: "com.android.server.policy.devicestate.config",
+}
diff --git a/services/core/xsd/device-state-config/device-state-config.xsd b/services/core/xsd/device-state-config/device-state-config.xsd
new file mode 100644
index 0000000..0d8c08c
--- /dev/null
+++ b/services/core/xsd/device-state-config/device-state-config.xsd
@@ -0,0 +1,78 @@
+<?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.
+ -->
+
+<xs:schema version="2.0"
+ elementFormDefault="qualified"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+ <xs:element name="device-state-config">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="device-state" type="deviceState" maxOccurs="256" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:complexType name="deviceState">
+ <xs:sequence>
+ <xs:element name="identifier">
+ <xs:simpleType>
+ <xs:restriction base="xs:integer">
+ <xs:minInclusive value="0" />
+ <xs:maxInclusive value="255" />
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:element>
+ <xs:element name="name" type="xs:string" minOccurs="0" />
+ <xs:element name="conditions" type="conditions" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="conditions">
+ <xs:sequence>
+ <xs:element name="lid-switch" type="lidSwitchCondition" minOccurs="0" />
+ <xs:element name="sensor" type="sensorCondition" minOccurs="0" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="lidSwitchCondition">
+ <xs:sequence>
+ <xs:element name="open" type="xs:boolean" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="sensorCondition">
+ <xs:sequence>
+ <xs:element name="name" type="xs:string" />
+ <xs:element name="type" type="xs:positiveInteger" />
+ <xs:element name="value" type="numericRange" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="numericRange">
+ <xs:sequence>
+ <xs:choice minOccurs="0">
+ <xs:element name="min" type="xs:decimal" />
+ <xs:element name="min-inclusive" type="xs:decimal" />
+ </xs:choice>
+ <xs:choice minOccurs="0">
+ <xs:element name="max" type="xs:decimal" />
+ <xs:element name="max-inclusive" type="xs:decimal"/>
+ </xs:choice>
+ </xs:sequence>
+ </xs:complexType>
+</xs:schema>
diff --git a/services/core/xsd/device-state-config/schema/current.txt b/services/core/xsd/device-state-config/schema/current.txt
new file mode 100644
index 0000000..667d1ad
--- /dev/null
+++ b/services/core/xsd/device-state-config/schema/current.txt
@@ -0,0 +1,61 @@
+// Signature format: 2.0
+package com.android.server.policy.devicestate.config {
+
+ public class Conditions {
+ ctor public Conditions();
+ method public com.android.server.policy.devicestate.config.LidSwitchCondition getLidSwitch();
+ method public java.util.List<com.android.server.policy.devicestate.config.SensorCondition> getSensor();
+ method public void setLidSwitch(com.android.server.policy.devicestate.config.LidSwitchCondition);
+ }
+
+ public class DeviceState {
+ ctor public DeviceState();
+ method public com.android.server.policy.devicestate.config.Conditions getConditions();
+ method public java.math.BigInteger getIdentifier();
+ method public String getName();
+ method public void setConditions(com.android.server.policy.devicestate.config.Conditions);
+ method public void setIdentifier(java.math.BigInteger);
+ method public void setName(String);
+ }
+
+ public class DeviceStateConfig {
+ ctor public DeviceStateConfig();
+ method public java.util.List<com.android.server.policy.devicestate.config.DeviceState> getDeviceState();
+ }
+
+ public class LidSwitchCondition {
+ ctor public LidSwitchCondition();
+ method public boolean getOpen();
+ method public void setOpen(boolean);
+ }
+
+ public class NumericRange {
+ ctor public NumericRange();
+ method public java.math.BigDecimal getMaxInclusive_optional();
+ method public java.math.BigDecimal getMax_optional();
+ method public java.math.BigDecimal getMinInclusive_optional();
+ method public java.math.BigDecimal getMin_optional();
+ method public void setMaxInclusive_optional(java.math.BigDecimal);
+ method public void setMax_optional(java.math.BigDecimal);
+ method public void setMinInclusive_optional(java.math.BigDecimal);
+ method public void setMin_optional(java.math.BigDecimal);
+ }
+
+ public class SensorCondition {
+ ctor public SensorCondition();
+ method public String getName();
+ method public java.math.BigInteger getType();
+ method public java.util.List<com.android.server.policy.devicestate.config.NumericRange> getValue();
+ method public void setName(String);
+ method public void setType(java.math.BigInteger);
+ }
+
+ public class XmlParser {
+ ctor public XmlParser();
+ method public static com.android.server.policy.devicestate.config.DeviceStateConfig read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ }
+
+}
+
diff --git a/services/core/xsd/device-state-config/schema/last_current.txt b/services/core/xsd/device-state-config/schema/last_current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/services/core/xsd/device-state-config/schema/last_current.txt
diff --git a/services/core/xsd/device-state-config/schema/last_removed.txt b/services/core/xsd/device-state-config/schema/last_removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/services/core/xsd/device-state-config/schema/last_removed.txt
diff --git a/services/core/xsd/device-state-config/schema/removed.txt b/services/core/xsd/device-state-config/schema/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/services/core/xsd/device-state-config/schema/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f08ce27..a646682 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4373,7 +4373,7 @@
final int adminUser = admin.getUserHandle().getIdentifier();
// Password complexity is only taken into account from DO/PO
if (isDeviceOwner(adminComponent, adminUser)
- || isProfileOwner(adminComponent, adminUser)) {
+ || isProfileOwnerUncheckedLocked(adminComponent, adminUser)) {
maxRequiredComplexity = Math.max(maxRequiredComplexity, admin.mPasswordComplexity);
}
}
@@ -6216,7 +6216,7 @@
}
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
- final CallerIdentity caller = getAdminCallerIdentity(comp);
+ final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
@@ -7487,6 +7487,12 @@
return who != null && who.equals(profileOwner);
}
+ private boolean isProfileOwnerUncheckedLocked(ComponentName who, int userId) {
+ ensureLocked();
+ final ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
+ return who != null && who.equals(profileOwner);
+ }
+
/**
* Returns {@code true} if the provided caller identity is of a profile owner.
* @param caller identity of caller.
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 77fef12..8795d77 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -284,6 +284,8 @@
return UsageStatsManager.STANDBY_BUCKET_FREQUENT;
case RARE_INDEX:
return UsageStatsManager.STANDBY_BUCKET_RARE;
+ case RESTRICTED_INDEX:
+ return UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
default:
return UsageStatsManager.STANDBY_BUCKET_NEVER;
}
@@ -292,6 +294,7 @@
private void setStandbyBucket(int bucketIndex) {
when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
anyLong())).thenReturn(bucketIndexToUsageStatsBucket(bucketIndex));
+ mQuotaController.updateStandbyBucket(SOURCE_USER_ID, SOURCE_PACKAGE, bucketIndex);
}
private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
index be6bc99..ac23d4e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
@@ -100,6 +100,11 @@
}
@Override
+ public int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ @Override
protected int[] getProfileIds(int userId) {
IntArray profiles = mProfiles.get(userId);
if (profiles != null) {
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index e76c5a4..a02c533 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
@@ -36,6 +37,7 @@
import android.provider.Settings;
import android.telecom.TelecomManager;
import android.test.mock.MockContentResolver;
+import android.testing.TestableLooper;
import android.util.MutableBoolean;
import android.view.KeyEvent;
@@ -44,6 +46,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -65,6 +68,7 @@
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class GestureLauncherServiceTest {
private static final int FAKE_USER_ID = 1337;
@@ -83,6 +87,7 @@
private @Mock StatusBarManagerInternal mStatusBarManagerInternal;
private @Mock TelecomManager mTelecomManager;
private @Mock MetricsLogger mMetricsLogger;
+ @Mock private UiEventLogger mUiEventLogger;
private MockContentResolver mContentResolver;
private GestureLauncherService mGestureLauncherService;
@@ -109,7 +114,8 @@
when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager);
when(mTelecomManager.createLaunchEmergencyDialerIntent(null)).thenReturn(new Intent());
- mGestureLauncherService = new GestureLauncherService(mContext, mMetricsLogger);
+ mGestureLauncherService = new GestureLauncherService(mContext, mMetricsLogger,
+ mUiEventLogger);
}
@Test
@@ -268,6 +274,7 @@
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -312,6 +319,7 @@
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -358,6 +366,7 @@
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -406,6 +415,8 @@
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
verify(mMetricsLogger)
.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+ verify(mUiEventLogger, times(1))
+ .log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -460,6 +471,8 @@
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
verify(mMetricsLogger)
.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+ verify(mUiEventLogger, times(1))
+ .log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
final ArgumentCaptor<Integer> cameraIntervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -499,7 +512,8 @@
assertTrue(intercepted);
assertTrue(outLaunched.value);
- // TODO (b/169960245) Verify metric event equiv. to ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE
+ verify(mUiEventLogger, times(1))
+ .log(GestureLauncherService.GestureLauncherEvent.GESTURE_PANIC_TAP_POWER);
verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -551,7 +565,8 @@
assertTrue(outLaunched.value);
assertTrue(intercepted);
- // TODO (b/169960245) Verify metric event equiv. to ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE
+ verify(mUiEventLogger, times(1))
+ .log(GestureLauncherService.GestureLauncherEvent.GESTURE_PANIC_TAP_POWER);
verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -646,6 +661,7 @@
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(1)).histogram(
@@ -690,6 +706,7 @@
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -736,6 +753,7 @@
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -782,6 +800,7 @@
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -826,6 +845,7 @@
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -869,6 +889,7 @@
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -914,6 +935,7 @@
assertFalse(outLaunched.value);
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -961,6 +983,8 @@
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
verify(mMetricsLogger)
.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+ verify(mUiEventLogger, times(1))
+ .log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -1007,6 +1031,7 @@
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -1051,6 +1076,7 @@
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
@@ -1097,6 +1123,7 @@
verify(mMetricsLogger, never())
.action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+ verify(mUiEventLogger, never()).log(any());
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMetricsLogger, times(2)).histogram(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 1cdd873..e43a002 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -66,7 +66,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-
import java.util.ArrayList;
import java.util.List;
import java.util.function.IntConsumer;
@@ -129,6 +128,8 @@
ScaleChangedListener mMockScaleChangedListener;
@Mock
MagnificationRequestObserver mMagnificationRequestObserver;
+ @Mock
+ WindowMagnificationPromptController mWindowMagnificationPromptController;
private OffsettableClock mClock;
private FullScreenMagnificationGestureHandler mMgh;
@@ -170,7 +171,9 @@
@After
public void tearDown() {
+ mMgh.onDestroy();
mFullScreenMagnificationController.unregister(DISPLAY_0);
+ verify(mWindowMagnificationPromptController).onDestroy();
}
@NonNull
@@ -178,7 +181,8 @@
boolean detectShortcutTrigger) {
FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler(
mContext, mFullScreenMagnificationController, mMockScaleChangedListener,
- detectTripleTap, detectShortcutTrigger, DISPLAY_0);
+ detectTripleTap, detectShortcutTrigger, mWindowMagnificationPromptController,
+ DISPLAY_0);
mHandler = new TestHandler(h.mDetectingState, mClock) {
@Override
protected String messageToString(Message m) {
@@ -434,6 +438,20 @@
returnToNormalFrom(STATE_PANNING);
}
+ @Test
+ public void testZoomedWithTripleTap_invokeShowWindowPromptAction() {
+ goFromStateIdleTo(STATE_ZOOMED);
+
+ verify(mWindowMagnificationPromptController).showNotificationIfNeeded();
+ }
+
+ @Test
+ public void testShortcutTriggered_invokeShowWindowPromptAction() {
+ goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
+
+ verify(mWindowMagnificationPromptController).showNotificationIfNeeded();
+ }
+
private void assertActionsInOrder(List<MotionEvent> actualEvents,
List<Integer> expectedActions) {
assertTrue(actualEvents.size() == expectedActions.size());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java
new file mode 100644
index 0000000..5fd28f57
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT;
+
+import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE;
+import static com.android.server.accessibility.magnification.WindowMagnificationPromptController.ACTION_TURN_ON_IN_SETTINGS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link WindowMagnificationPromptController}.
+ */
+public class WindowMagnificationPromptControllerTest {
+
+ private static final int TEST_USER = 0;
+
+ @Mock
+ private NotificationManager mNotificationManager;
+ @Mock
+ private StatusBarManager mStatusBarManager;
+ @Rule
+ public A11yTestableContext mTestableContext = new A11yTestableContext(
+ InstrumentationRegistry.getContext());
+ private ContentResolver mResolver = mTestableContext.getContentResolver();
+ private WindowMagnificationPromptController mWindowMagnificationPromptController;
+ private BroadcastReceiver mReceiver;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mTestableContext.addMockSystemService(NotificationManager.class, mNotificationManager);
+ mTestableContext.addMockSystemService(StatusBarManager.class, mStatusBarManager);
+ setWindowMagnificationPromptSettings(true);
+ mWindowMagnificationPromptController = new WindowMagnificationPromptController(
+ mTestableContext, TEST_USER);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mWindowMagnificationPromptController.onDestroy();
+ }
+
+ @Test
+ public void showNotificationIfNeeded_promptSettingsIsOn_showNotification() {
+ mWindowMagnificationPromptController.showNotificationIfNeeded();
+
+ verify(mNotificationManager).notify(eq(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE), any(
+ Notification.class));
+ }
+
+ @Test
+ public void tapTurnOnAction_isShown_cancelNotificationAndLaunchMagnificationSettings() {
+ showNotificationAndAssert();
+
+ final Intent intent = new Intent(ACTION_TURN_ON_IN_SETTINGS);
+ mReceiver.onReceive(mTestableContext, intent);
+
+ verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+ verifyLaunchMagnificationSettings();
+ }
+
+ @Test
+ public void tapTurnOnAction_isShown_settingsValueIsFalseAndUnregisterReceiver() {
+ showNotificationAndAssert();
+
+ final Intent intent = new Intent(ACTION_TURN_ON_IN_SETTINGS);
+ mReceiver.onReceive(mTestableContext, intent);
+
+ assertThat(Settings.Secure.getInt(mResolver, ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
+ -1)).isEqualTo(0);
+ verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver);
+ }
+
+ @Test
+ public void tapDismissAction_isShown_cancelNotificationAndUnregisterReceiver() {
+ showNotificationAndAssert();
+
+ final Intent intent = new Intent(WindowMagnificationPromptController.ACTION_DISMISS);
+ mReceiver.onReceive(mTestableContext, intent);
+
+ verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+ verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver);
+ }
+
+ @Test
+ public void promptSettingsChangeToFalse_isShown_cancelNotificationAndUnregisterReceiver() {
+ showNotificationAndAssert();
+
+ setWindowMagnificationPromptSettings(false);
+
+ verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+ verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver);
+ }
+
+ @Test
+ public void onDestroy_isShown_cancelNotificationAndUnregisterReceiver() {
+ showNotificationAndAssert();
+
+ mWindowMagnificationPromptController.onDestroy();
+
+ verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+ verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver);
+ }
+
+ private void verifyLaunchMagnificationSettings() {
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ final ArgumentCaptor<UserHandle> userHandleCaptor = ArgumentCaptor.forClass(
+ UserHandle.class);
+ verify(mTestableContext.getSpyContext()).startActivityAsUser(intentCaptor.capture(),
+ bundleCaptor.capture(), userHandleCaptor.capture());
+ assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+ Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
+ assertThat(userHandleCaptor.getValue().getIdentifier()).isEqualTo(TEST_USER);
+ verify(mStatusBarManager).collapsePanels();
+ }
+
+ private void showNotificationAndAssert() {
+ mWindowMagnificationPromptController.showNotificationIfNeeded();
+ mReceiver = mWindowMagnificationPromptController.mNotificationActionReceiver;
+ assertThat(mReceiver).isNotNull();
+ }
+
+ private void setWindowMagnificationPromptSettings(boolean enable) {
+ Settings.Secure.putIntForUser(mResolver, ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
+ enable ? 1 : 0, TEST_USER);
+ if (mWindowMagnificationPromptController != null) {
+ mWindowMagnificationPromptController.onPromptSettingsValueChanged();
+ }
+ }
+
+ private class A11yTestableContext extends TestableContext {
+
+ private Context mSpyContext;
+
+ A11yTestableContext(Context base) {
+ super(base);
+ mSpyContext = Mockito.mock(Context.class);
+ }
+
+ @Override
+ public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+ mSpyContext.startActivityAsUser(intent, options, user);
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ return mSpyContext.registerReceiver(receiver, filter, broadcastPermission, scheduler);
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ mSpyContext.unregisterReceiver(receiver);
+ }
+
+ Context getSpyContext() {
+ return mSpyContext;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index 726e48a..b929061 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -26,15 +26,17 @@
import android.app.appsearch.SearchSpec;
import android.app.appsearch.exceptions.AppSearchException;
+import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
import com.android.server.appsearch.proto.DocumentProto;
import com.android.server.appsearch.proto.GetOptimizeInfoResultProto;
-import com.android.server.appsearch.proto.IndexingConfig;
import com.android.server.appsearch.proto.PropertyConfigProto;
import com.android.server.appsearch.proto.PropertyProto;
import com.android.server.appsearch.proto.SchemaProto;
import com.android.server.appsearch.proto.SchemaTypeConfigProto;
import com.android.server.appsearch.proto.SearchSpecProto;
+import com.android.server.appsearch.proto.StringIndexingConfig;
import com.android.server.appsearch.proto.TermMatchType;
+
import com.google.common.collect.ImmutableSet;
import org.junit.Before;
@@ -42,18 +44,36 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
public class AppSearchImplTest {
- @Rule
- public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
private AppSearchImpl mAppSearchImpl;
+ private SchemaTypeConfigProto mVisibilitySchemaProto;
@Before
public void setUp() throws Exception {
mAppSearchImpl = AppSearchImpl.create(mTemporaryFolder.newFolder());
+
+ AppSearchSchema visibilityAppSearchSchema =
+ new AppSearchSchema.Builder(
+ VisibilityStore.DATABASE_NAME
+ + AppSearchImpl.DATABASE_DELIMITER
+ + VisibilityStore.SCHEMA_TYPE)
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder(
+ VisibilityStore.PLATFORM_HIDDEN_PROPERTY)
+ .setDataType(
+ AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .build())
+ .build();
+ mVisibilitySchemaProto = SchemaToProtoConverter.convert(visibilityAppSearchSchema);
}
/**
@@ -62,91 +82,217 @@
* schema.
*/
@Test
- public void testRewriteSchema() throws Exception {
- SchemaProto.Builder existingSchemaBuilder = mAppSearchImpl.getSchemaProto().toBuilder();
+ public void testRewriteSchema_addType() throws Exception {
+ SchemaProto.Builder existingSchemaBuilder =
+ SchemaProto.newBuilder()
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("existingDatabase/Foo")
+ .build());
- SchemaProto newSchema = SchemaProto.newBuilder()
- .addTypes(SchemaTypeConfigProto.newBuilder()
- .setSchemaType("Foo").build())
- .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();
+ // Create a copy so we can modify it.
+ List<SchemaTypeConfigProto> existingTypes =
+ new ArrayList<>(existingSchemaBuilder.getTypesList());
- Set<String> newTypes = mAppSearchImpl.rewriteSchema("databaseName", existingSchemaBuilder,
- newSchema);
- assertThat(newTypes).containsExactly("databaseName/Foo", "databaseName/TestType");
+ SchemaProto newSchema =
+ SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build())
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("TestType")
+ .addProperties(
+ PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(
+ PropertyConfigProto.DataType.Code
+ .STRING)
+ .setCardinality(
+ PropertyConfigProto.Cardinality.Code
+ .OPTIONAL)
+ .setStringIndexingConfig(
+ StringIndexingConfig.newBuilder()
+ .setTokenizerType(
+ StringIndexingConfig
+ .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("databaseName/Foo").build())
- .addTypes(SchemaTypeConfigProto.newBuilder()
- .setSchemaType("databaseName/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("databaseName/RefType")
- .build()
- ).build())
- .build();
+ AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
+ mAppSearchImpl.rewriteSchema("newDatabase", existingSchemaBuilder, newSchema);
+
+ // We rewrote all the new types that were added. And nothing was removed.
+ assertThat(rewrittenSchemaResults.mRewrittenQualifiedTypes)
+ .containsExactly("newDatabase/Foo", "newDatabase/TestType");
+ assertThat(rewrittenSchemaResults.mDeletedQualifiedTypes).isEmpty();
+
+ SchemaProto expectedSchema =
+ SchemaProto.newBuilder()
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("newDatabase/Foo")
+ .build())
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("newDatabase/TestType")
+ .addProperties(
+ PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(
+ PropertyConfigProto.DataType.Code
+ .STRING)
+ .setCardinality(
+ PropertyConfigProto.Cardinality.Code
+ .OPTIONAL)
+ .setStringIndexingConfig(
+ StringIndexingConfig.newBuilder()
+ .setTokenizerType(
+ StringIndexingConfig
+ .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("newDatabase/RefType")
+ .build())
+ .build())
+ .build();
+
+ existingTypes.addAll(expectedSchema.getTypesList());
+ assertThat(existingSchemaBuilder.getTypesList()).containsExactlyElementsIn(existingTypes);
+ }
+
+ /**
+ * Ensure that we track all types that were rewritten in the input schema. Even if they were not
+ * technically "added" to the existing schema.
+ */
+ @Test
+ public void testRewriteSchema_rewriteType() throws Exception {
+ SchemaProto.Builder existingSchemaBuilder =
+ SchemaProto.newBuilder()
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("existingDatabase/Foo")
+ .build());
+
+ SchemaProto newSchema =
+ SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build())
+ .build();
+
+ AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
+ mAppSearchImpl.rewriteSchema("existingDatabase", existingSchemaBuilder, newSchema);
+
+ // Nothing was removed, but the method did rewrite the type name.
+ assertThat(rewrittenSchemaResults.mRewrittenQualifiedTypes)
+ .containsExactly("existingDatabase/Foo");
+ assertThat(rewrittenSchemaResults.mDeletedQualifiedTypes).isEmpty();
+
+ // Same schema since nothing was added.
+ SchemaProto expectedSchema = existingSchemaBuilder.build();
+ assertThat(existingSchemaBuilder.getTypesList())
+ .containsExactlyElementsIn(expectedSchema.getTypesList());
+ }
+
+ /**
+ * Ensure that we track which types from the existing schema are deleted when a new schema is
+ * set.
+ */
+ @Test
+ public void testRewriteSchema_deleteType() throws Exception {
+ SchemaProto.Builder existingSchemaBuilder =
+ SchemaProto.newBuilder()
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("existingDatabase/Foo")
+ .build());
+
+ SchemaProto newSchema =
+ SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Bar").build())
+ .build();
+
+ AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
+ mAppSearchImpl.rewriteSchema("existingDatabase", existingSchemaBuilder, newSchema);
+
+ // Bar type was rewritten, but Foo ended up being deleted since it wasn't included in the
+ // new schema.
+ assertThat(rewrittenSchemaResults.mRewrittenQualifiedTypes)
+ .containsExactly("existingDatabase/Bar");
+ assertThat(rewrittenSchemaResults.mDeletedQualifiedTypes)
+ .containsExactly("existingDatabase/Foo");
+
+ // Same schema since nothing was added.
+ SchemaProto expectedSchema =
+ SchemaProto.newBuilder()
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("existingDatabase/Bar")
+ .build())
+ .build();
+
assertThat(existingSchemaBuilder.getTypesList())
.containsExactlyElementsIn(expectedSchema.getTypesList());
}
@Test
public void testAddDocumentTypePrefix() {
- DocumentProto insideDocument = DocumentProto.newBuilder()
- .setUri("inside-uri")
- .setSchema("type")
- .setNamespace("namespace")
- .build();
- DocumentProto documentProto = DocumentProto.newBuilder()
- .setUri("uri")
- .setSchema("type")
- .setNamespace("namespace")
- .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
- .build();
+ DocumentProto insideDocument =
+ DocumentProto.newBuilder()
+ .setUri("inside-uri")
+ .setSchema("type")
+ .setNamespace("namespace")
+ .build();
+ DocumentProto documentProto =
+ DocumentProto.newBuilder()
+ .setUri("uri")
+ .setSchema("type")
+ .setNamespace("namespace")
+ .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
+ .build();
- DocumentProto expectedInsideDocument = DocumentProto.newBuilder()
- .setUri("inside-uri")
- .setSchema("databaseName/type")
- .setNamespace("databaseName/namespace")
- .build();
- DocumentProto expectedDocumentProto = DocumentProto.newBuilder()
- .setUri("uri")
- .setSchema("databaseName/type")
- .setNamespace("databaseName/namespace")
- .addProperties(PropertyProto.newBuilder().addDocumentValues(expectedInsideDocument))
- .build();
+ DocumentProto expectedInsideDocument =
+ DocumentProto.newBuilder()
+ .setUri("inside-uri")
+ .setSchema("databaseName/type")
+ .setNamespace("databaseName/namespace")
+ .build();
+ DocumentProto expectedDocumentProto =
+ DocumentProto.newBuilder()
+ .setUri("uri")
+ .setSchema("databaseName/type")
+ .setNamespace("databaseName/namespace")
+ .addProperties(
+ PropertyProto.newBuilder()
+ .addDocumentValues(expectedInsideDocument))
+ .build();
DocumentProto.Builder actualDocument = documentProto.toBuilder();
mAppSearchImpl.addPrefixToDocument(actualDocument, "databaseName/");
@@ -154,31 +300,37 @@
}
@Test
- public void testRemoveDocumentTypePrefixes() {
- DocumentProto insideDocument = DocumentProto.newBuilder()
- .setUri("inside-uri")
- .setSchema("databaseName1/type")
- .setNamespace("databaseName2/namespace")
- .build();
- DocumentProto documentProto = DocumentProto.newBuilder()
- .setUri("uri")
- .setSchema("databaseName2/type")
- .setNamespace("databaseName3/namespace")
- .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
- .build();
+ public void testRemoveDocumentTypePrefixes() throws Exception {
+ DocumentProto insideDocument =
+ DocumentProto.newBuilder()
+ .setUri("inside-uri")
+ .setSchema("databaseName1/type")
+ .setNamespace("databaseName2/namespace")
+ .build();
+ DocumentProto documentProto =
+ DocumentProto.newBuilder()
+ .setUri("uri")
+ .setSchema("databaseName2/type")
+ .setNamespace("databaseName3/namespace")
+ .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
+ .build();
- DocumentProto expectedInsideDocument = DocumentProto.newBuilder()
- .setUri("inside-uri")
- .setSchema("type")
- .setNamespace("namespace")
- .build();
+ DocumentProto expectedInsideDocument =
+ DocumentProto.newBuilder()
+ .setUri("inside-uri")
+ .setSchema("type")
+ .setNamespace("namespace")
+ .build();
// Since we don't pass in "databaseName3/" as a prefix to remove, it stays on the Document.
- DocumentProto expectedDocumentProto = DocumentProto.newBuilder()
- .setUri("uri")
- .setSchema("type")
- .setNamespace("namespace")
- .addProperties(PropertyProto.newBuilder().addDocumentValues(expectedInsideDocument))
- .build();
+ DocumentProto expectedDocumentProto =
+ DocumentProto.newBuilder()
+ .setUri("uri")
+ .setSchema("type")
+ .setNamespace("namespace")
+ .addProperties(
+ PropertyProto.newBuilder()
+ .addDocumentValues(expectedInsideDocument))
+ .build();
DocumentProto.Builder actualDocument = documentProto.toBuilder();
mAppSearchImpl.removeDatabasesFromDocument(actualDocument);
@@ -190,19 +342,23 @@
// Insert schema
Set<AppSearchSchema> schemas =
Collections.singleton(new AppSearchSchema.Builder("type").build());
- mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/false);
+ mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/ false);
// Insert enough documents.
- for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
- + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
+ for (int i = 0;
+ i
+ < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
+ + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL;
+ i++) {
GenericDocument document =
- new GenericDocument.Builder("uri" + i, "type").setNamespace(
- "namespace").build();
+ new GenericDocument.Builder("uri" + i, "type")
+ .setNamespace("namespace")
+ .build();
mAppSearchImpl.putDocument("database", document);
}
// Check optimize() will release 0 docs since there is no deletion.
- GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+ GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0);
// delete 999 documents , we will reach the threshold to trigger optimize() in next
@@ -212,82 +368,82 @@
}
// optimize() still not be triggered since we are in the interval to call getOptimizeInfo()
- optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+ optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
assertThat(optimizeInfo.getOptimizableDocs())
.isEqualTo(AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1);
// Keep delete docs, will reach the interval this time and trigger optimize().
for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT;
- i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
- + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
+ i
+ < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
+ + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL;
+ i++) {
mAppSearchImpl.remove("database", "namespace", "uri" + i);
}
// Verify optimize() is triggered
- optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+ optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
assertThat(optimizeInfo.getOptimizableDocs())
.isLessThan(AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
}
@Test
- public void testRewriteSearchSpec_OneInstance() throws Exception {
- SearchSpecProto.Builder searchSpecProto =
- SearchSpecProto.newBuilder().setQuery("");
+ public void testRewriteSearchSpec_oneInstance() throws Exception {
+ SearchSpecProto.Builder searchSpecProto = SearchSpecProto.newBuilder().setQuery("");
// Insert schema
Set<AppSearchSchema> schemas =
Collections.singleton(new AppSearchSchema.Builder("type").build());
- mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/false);
+ mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/ false);
// Insert document
- GenericDocument document = new GenericDocument.Builder("uri", "type").setNamespace(
- "namespace").build();
+ GenericDocument document =
+ new GenericDocument.Builder("uri", "type").setNamespace("namespace").build();
mAppSearchImpl.putDocument("database", document);
// Rewrite SearchSpec
- mAppSearchImpl.rewriteSearchSpecForDatabases(searchSpecProto, Collections.singleton(
- "database"));
+ mAppSearchImpl.rewriteSearchSpecForDatabasesLocked(
+ searchSpecProto, Collections.singleton("database"));
assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly("database/type");
assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly("database/namespace");
}
@Test
- public void testRewriteSearchSpec_TwoInstances() throws Exception {
- SearchSpecProto.Builder searchSpecProto =
- SearchSpecProto.newBuilder().setQuery("");
+ public void testRewriteSearchSpec_twoInstances() throws Exception {
+ SearchSpecProto.Builder searchSpecProto = SearchSpecProto.newBuilder().setQuery("");
// Insert schema
- Set<AppSearchSchema> schemas = Set.of(
- new AppSearchSchema.Builder("typeA").build(),
- new AppSearchSchema.Builder("typeB").build());
- mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
- mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/false);
+ Set<AppSearchSchema> schemas =
+ Set.of(
+ new AppSearchSchema.Builder("typeA").build(),
+ new AppSearchSchema.Builder("typeB").build());
+ mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
+ mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/ false);
// Insert documents
- GenericDocument document1 = new GenericDocument.Builder("uri", "typeA").setNamespace(
- "namespace").build();
+ GenericDocument document1 =
+ new GenericDocument.Builder("uri", "typeA").setNamespace("namespace").build();
mAppSearchImpl.putDocument("database1", document1);
- GenericDocument document2 = new GenericDocument.Builder("uri", "typeB").setNamespace(
- "namespace").build();
+ GenericDocument document2 =
+ new GenericDocument.Builder("uri", "typeB").setNamespace("namespace").build();
mAppSearchImpl.putDocument("database2", document2);
// Rewrite SearchSpec
- mAppSearchImpl.rewriteSearchSpecForDatabases(searchSpecProto,
- ImmutableSet.of("database1", "database2"));
- assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly(
- "database1/typeA", "database1/typeB", "database2/typeA", "database2/typeB");
- assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly(
- "database1/namespace", "database2/namespace");
+ mAppSearchImpl.rewriteSearchSpecForDatabasesLocked(
+ searchSpecProto, ImmutableSet.of("database1", "database2"));
+ assertThat(searchSpecProto.getSchemaTypeFiltersList())
+ .containsExactly(
+ "database1/typeA", "database1/typeB", "database2/typeA", "database2/typeB");
+ assertThat(searchSpecProto.getNamespaceFiltersList())
+ .containsExactly("database1/namespace", "database2/namespace");
}
@Test
public void testQueryEmptyDatabase() throws Exception {
SearchSpec searchSpec =
new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
- SearchResultPage searchResultPage = mAppSearchImpl.query(
- "EmptyDatabase",
- "", searchSpec);
+ SearchResultPage searchResultPage = mAppSearchImpl.query("EmptyDatabase", "", searchSpec);
assertThat(searchResultPage.getResults()).isEmpty();
}
@@ -295,25 +451,25 @@
public void testGlobalQueryEmptyDatabase() throws Exception {
SearchSpec searchSpec =
new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
- SearchResultPage searchResultPage = mAppSearchImpl.query(
- "EmptyDatabase",
- "", searchSpec);
+ SearchResultPage searchResultPage = mAppSearchImpl.query("EmptyDatabase", "", searchSpec);
assertThat(searchResultPage.getResults()).isEmpty();
}
@Test
- public void testRemoveEmptyDatabase_NoExceptionThrown() throws Exception {
+ public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception {
SearchSpec searchSpec =
- new SearchSpec.Builder().addSchema("FakeType").setTermMatch(
- TermMatchType.Code.PREFIX_VALUE).build();
- mAppSearchImpl.removeByQuery("EmptyDatabase",
- "", searchSpec);
+ new SearchSpec.Builder()
+ .addSchemaType("FakeType")
+ .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+ .build();
+ mAppSearchImpl.removeByQuery("EmptyDatabase", "", searchSpec);
searchSpec =
- new SearchSpec.Builder().addNamespace("FakeNamespace").setTermMatch(
- TermMatchType.Code.PREFIX_VALUE).build();
- mAppSearchImpl.removeByQuery("EmptyDatabase",
- "", searchSpec);
+ new SearchSpec.Builder()
+ .addNamespace("FakeNamespace")
+ .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+ .build();
+ mAppSearchImpl.removeByQuery("EmptyDatabase", "", searchSpec);
searchSpec = new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
mAppSearchImpl.removeByQuery("EmptyDatabase", "", searchSpec);
@@ -324,14 +480,46 @@
Set<AppSearchSchema> schemas =
Collections.singleton(new AppSearchSchema.Builder("Email").build());
// Set schema Email to AppSearch database1
- mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
+ mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
- // Create excepted schemaType proto.
- SchemaProto exceptedProto = SchemaProto.newBuilder()
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
- .build();
- assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
- .containsExactlyElementsIn(exceptedProto.getTypesList());
+ // Create expected schemaType proto.
+ SchemaProto expectedProto =
+ SchemaProto.newBuilder()
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+ .build();
+
+ List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
+ expectedTypes.add(mVisibilitySchemaProto);
+ expectedTypes.addAll(expectedProto.getTypesList());
+ assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
+ .containsExactlyElementsIn(expectedTypes);
+ }
+
+ @Test
+ public void testSetSchema_existingSchemaRetainsVisibilitySetting() throws Exception {
+ mAppSearchImpl.setSchema(
+ "database",
+ Collections.singleton(new AppSearchSchema.Builder("schema1").build()),
+ /*forceOverride=*/ false);
+ mAppSearchImpl.setVisibility("database", Set.of("schema1"));
+
+ // "schema1" is platform hidden now
+ assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+ .containsExactly("database/schema1");
+
+ // Add a new schema, and include the already-existing "schema1"
+ mAppSearchImpl.setSchema(
+ "database",
+ Set.of(
+ new AppSearchSchema.Builder("schema1").build(),
+ new AppSearchSchema.Builder("schema2").build()),
+ /*forceOverride=*/ false);
+
+ // Check that "schema1" is still platform hidden, but "schema2" is the default platform
+ // visible.
+ assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+ .containsExactly("database/schema1");
}
@Test
@@ -340,35 +528,52 @@
schemas.add(new AppSearchSchema.Builder("Email").build());
schemas.add(new AppSearchSchema.Builder("Document").build());
// Set schema Email and Document to AppSearch database1
- mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
+ mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
- // Create excepted schemaType proto.
- SchemaProto exceptedProto = SchemaProto.newBuilder()
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document"))
- .build();
+ // Create expected schemaType proto.
+ SchemaProto expectedProto =
+ SchemaProto.newBuilder()
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("database1/Document"))
+ .build();
// Check both schema Email and Document saved correctly.
- assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
- .containsExactlyElementsIn(exceptedProto.getTypesList());
+ List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
+ expectedTypes.add(mVisibilitySchemaProto);
+ expectedTypes.addAll(expectedProto.getTypesList());
+ assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
+ .containsExactlyElementsIn(expectedTypes);
- final Set<AppSearchSchema> finalSchemas = Collections.singleton(new AppSearchSchema.Builder(
- "Email").build());
+ final Set<AppSearchSchema> finalSchemas =
+ Collections.singleton(new AppSearchSchema.Builder("Email").build());
// Check the incompatible error has been thrown.
- AppSearchException e = expectThrows(AppSearchException.class, () ->
- mAppSearchImpl.setSchema("database1", finalSchemas, /*forceOverride=*/false));
+ AppSearchException e =
+ expectThrows(
+ AppSearchException.class,
+ () ->
+ mAppSearchImpl.setSchema(
+ "database1", finalSchemas, /*forceOverride=*/ false));
assertThat(e).hasMessageThat().contains("Schema is incompatible");
assertThat(e).hasMessageThat().contains("Deleted types: [database1/Document]");
// ForceOverride to delete.
- mAppSearchImpl.setSchema("database1", finalSchemas, /*forceOverride=*/true);
+ mAppSearchImpl.setSchema("database1", finalSchemas, /*forceOverride=*/ true);
// Check Document schema is removed.
- exceptedProto = SchemaProto.newBuilder()
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
- .build();
- assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
- .containsExactlyElementsIn(exceptedProto.getTypesList());
+ expectedProto =
+ SchemaProto.newBuilder()
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+ .build();
+
+ expectedTypes = new ArrayList<>();
+ expectedTypes.add(mVisibilitySchemaProto);
+ expectedTypes.addAll(expectedProto.getTypesList());
+ assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
+ .containsExactlyElementsIn(expectedTypes);
}
@Test
@@ -379,35 +584,156 @@
schemas.add(new AppSearchSchema.Builder("Document").build());
// Set schema Email and Document to AppSearch database1 and 2
- mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
- mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/false);
+ mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
+ mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/ false);
- // Create excepted schemaType proto.
- SchemaProto exceptedProto = SchemaProto.newBuilder()
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document"))
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document"))
- .build();
+ // Create expected schemaType proto.
+ SchemaProto expectedProto =
+ SchemaProto.newBuilder()
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("database1/Document"))
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("database2/Document"))
+ .build();
// Check Email and Document is saved in database 1 and 2 correctly.
- assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
- .containsExactlyElementsIn(exceptedProto.getTypesList());
+ List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
+ expectedTypes.add(mVisibilitySchemaProto);
+ expectedTypes.addAll(expectedProto.getTypesList());
+ assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
+ .containsExactlyElementsIn(expectedTypes);
// Save only Email to database1 this time.
schemas = Collections.singleton(new AppSearchSchema.Builder("Email").build());
- mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/true);
+ mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ true);
- // Create excepted schemaType list, database 1 should only contain Email but database 2
+ // Create expected schemaType list, database 1 should only contain Email but database 2
// remains in same.
- exceptedProto = SchemaProto.newBuilder()
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document"))
- .build();
+ expectedProto =
+ SchemaProto.newBuilder()
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("database2/Document"))
+ .build();
// Check nothing changed in database2.
- assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
- .containsExactlyElementsIn(exceptedProto.getTypesList());
+ expectedTypes = new ArrayList<>();
+ expectedTypes.add(mVisibilitySchemaProto);
+ expectedTypes.addAll(expectedProto.getTypesList());
+ assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
+ .containsExactlyElementsIn(expectedTypes);
+ }
+
+ @Test
+ public void testRemoveSchema_removedFromVisibilityStore() throws Exception {
+ mAppSearchImpl.setSchema(
+ "database",
+ Collections.singleton(new AppSearchSchema.Builder("schema1").build()),
+ /*forceOverride=*/ false);
+ mAppSearchImpl.setVisibility("database", Set.of("schema1"));
+
+ // "schema1" is platform hidden now
+ assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+ .containsExactly("database/schema1");
+
+ // Remove "schema1" by force overriding
+ mAppSearchImpl.setSchema("database", Collections.emptySet(), /*forceOverride=*/ true);
+
+ // Check that "schema1" is no longer considered platform hidden
+ assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+ .isEmpty();
+
+ // Add "schema1" back, it gets default visibility settings which means it's not platform
+ // hidden.
+ mAppSearchImpl.setSchema(
+ "database",
+ Collections.singleton(new AppSearchSchema.Builder("schema1").build()),
+ /*forceOverride=*/ false);
+ assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+ .isEmpty();
+ }
+
+ @Test
+ public void testSetVisibility_defaultPlatformVisible() throws Exception {
+ mAppSearchImpl.setSchema(
+ "database",
+ Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
+ /*forceOverride=*/ false);
+ assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+ .isEmpty();
+ }
+
+ @Test
+ public void testSetVisibility_platformHidden() throws Exception {
+ mAppSearchImpl.setSchema(
+ "database",
+ Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
+ /*forceOverride=*/ false);
+ mAppSearchImpl.setVisibility("database", Set.of("Schema"));
+ assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+ .containsExactly("database/Schema");
+ }
+
+ @Test
+ public void testSetVisibility_unknownSchema() throws Exception {
+ mAppSearchImpl.setSchema(
+ "database",
+ Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
+ /*forceOverride=*/ false);
+
+ // We'll throw an exception if a client tries to set visibility on a schema we don't know
+ // about.
+ AppSearchException e =
+ expectThrows(
+ AppSearchException.class,
+ () -> mAppSearchImpl.setVisibility("database", Set.of("UnknownSchema")));
+ assertThat(e).hasMessageThat().contains("Unknown schema(s)");
+ }
+
+ @Test
+ public void testHasSchemaType() throws Exception {
+ // Nothing exists yet
+ assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "Schema")).isFalse();
+
+ mAppSearchImpl.setSchema(
+ "database",
+ Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
+ /*forceOverride=*/ false);
+ assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "Schema")).isTrue();
+
+ assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "UnknownSchema")).isFalse();
+ }
+
+ @Test
+ public void testGetDatabases() throws Exception {
+ // No client databases exist yet, but the VisibilityStore's does
+ assertThat(mAppSearchImpl.getDatabasesLocked())
+ .containsExactly(VisibilityStore.DATABASE_NAME);
+
+ // Has database1
+ mAppSearchImpl.setSchema(
+ "database1",
+ Collections.singleton(new AppSearchSchema.Builder("schema").build()),
+ /*forceOverride=*/ false);
+ assertThat(mAppSearchImpl.getDatabasesLocked())
+ .containsExactly(VisibilityStore.DATABASE_NAME, "database1");
+
+ // Has both databases
+ mAppSearchImpl.setSchema(
+ "database2",
+ Collections.singleton(new AppSearchSchema.Builder("schema").build()),
+ /*forceOverride=*/ false);
+ assertThat(mAppSearchImpl.getDatabasesLocked())
+ .containsExactly(VisibilityStore.DATABASE_NAME, "database1", "database2");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/VisibilityStoreTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/VisibilityStoreTest.java
new file mode 100644
index 0000000..dfe2de6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/VisibilityStoreTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.external.localstorage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class VisibilityStoreTest {
+
+ @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ private AppSearchImpl mAppSearchImpl;
+ private VisibilityStore mVisibilityStore;
+
+ @Before
+ public void setUp() throws Exception {
+ mAppSearchImpl = AppSearchImpl.create(mTemporaryFolder.newFolder());
+ mVisibilityStore = mAppSearchImpl.getVisibilityStoreLocked();
+ }
+
+ @Test
+ public void testSetVisibility() throws Exception {
+ mVisibilityStore.setVisibility(
+ "database", /*platformHiddenSchemas=*/ Set.of("schema1", "schema2"));
+ assertThat(mVisibilityStore.getPlatformHiddenSchemas("database"))
+ .containsExactly("schema1", "schema2");
+
+ // New .setVisibility() call completely overrides previous visibility settings. So
+ // "schema1" isn't preserved.
+ mVisibilityStore.setVisibility(
+ "database", /*platformHiddenSchemas=*/ Set.of("schema1", "schema3"));
+ assertThat(mVisibilityStore.getPlatformHiddenSchemas("database"))
+ .containsExactly("schema1", "schema3");
+
+ mVisibilityStore.setVisibility(
+ "database", /*platformHiddenSchemas=*/ Collections.emptySet());
+ assertThat(mVisibilityStore.getPlatformHiddenSchemas("database")).isEmpty();
+ }
+
+ @Test
+ public void testRemoveSchemas() throws Exception {
+ mVisibilityStore.setVisibility(
+ "database", /*platformHiddenSchemas=*/ Set.of("schema1", "schema2"));
+
+ // Removed just schema1
+ mVisibilityStore.updateSchemas("database", /*schemasToRemove=*/ Set.of("schema1"));
+ assertThat(mVisibilityStore.getPlatformHiddenSchemas("database"))
+ .containsExactly("schema2");
+
+ // Removed everything now
+ mVisibilityStore.updateSchemas("database", /*schemasToRemove=*/ Set.of("schema2"));
+ assertThat(mVisibilityStore.getPlatformHiddenSchemas("database")).isEmpty();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
index 85d4f01..98392a7 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
@@ -32,18 +32,18 @@
import java.util.List;
public class GenericDocumentToProtoConverterTest {
- private static final byte[] BYTE_ARRAY_1 = new byte[]{(byte) 1, (byte) 2, (byte) 3};
- private static final byte[] BYTE_ARRAY_2 = new byte[]{(byte) 4, (byte) 5, (byte) 6, (byte) 7};
+ private static final byte[] BYTE_ARRAY_1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+ private static final byte[] BYTE_ARRAY_2 = new byte[] {(byte) 4, (byte) 5, (byte) 6, (byte) 7};
private static final GenericDocument DOCUMENT_PROPERTIES_1 =
new GenericDocument.Builder<GenericDocument.Builder<?>>(
- "sDocumentProperties1", "sDocumentPropertiesSchemaType1")
- .setCreationTimestampMillis(12345L)
- .build();
+ "sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+ .setCreationTimestampMillis(12345L)
+ .build();
private static final GenericDocument DOCUMENT_PROPERTIES_2 =
new GenericDocument.Builder<GenericDocument.Builder<?>>(
- "sDocumentProperties2", "sDocumentPropertiesSchemaType2")
- .setCreationTimestampMillis(6789L)
- .build();
+ "sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+ .setCreationTimestampMillis(6789L)
+ .build();
@Test
public void testDocumentProtoConvert() {
@@ -63,32 +63,42 @@
.build();
// Create the Document proto. Need to sort the property order by key.
- DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
- .setUri("uri1")
- .setSchema("schemaType1")
- .setCreationTimestampMs(5L)
- .setScore(1)
- .setTtlMs(1L)
- .setNamespace("namespace");
+ DocumentProto.Builder documentProtoBuilder =
+ DocumentProto.newBuilder()
+ .setUri("uri1")
+ .setSchema("schemaType1")
+ .setCreationTimestampMs(5L)
+ .setScore(1)
+ .setTtlMs(1L)
+ .setNamespace("namespace");
HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
- propertyProtoMap.put("longKey1",
- PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
- propertyProtoMap.put("doubleKey1",
+ propertyProtoMap.put(
+ "longKey1", PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
+ propertyProtoMap.put(
+ "doubleKey1",
PropertyProto.newBuilder().setName("doubleKey1").addDoubleValues(1.0));
- propertyProtoMap.put("booleanKey1",
+ propertyProtoMap.put(
+ "booleanKey1",
PropertyProto.newBuilder().setName("booleanKey1").addBooleanValues(true));
- propertyProtoMap.put("stringKey1",
+ propertyProtoMap.put(
+ "stringKey1",
PropertyProto.newBuilder().setName("stringKey1").addStringValues("test-value1"));
- propertyProtoMap.put("byteKey1",
- PropertyProto.newBuilder().setName("byteKey1")
+ propertyProtoMap.put(
+ "byteKey1",
+ PropertyProto.newBuilder()
+ .setName("byteKey1")
.addBytesValues(ByteString.copyFrom(BYTE_ARRAY_1))
.addBytesValues(ByteString.copyFrom(BYTE_ARRAY_2)));
- propertyProtoMap.put("documentKey1",
- PropertyProto.newBuilder().setName("documentKey1")
+ propertyProtoMap.put(
+ "documentKey1",
+ PropertyProto.newBuilder()
+ .setName("documentKey1")
.addDocumentValues(
GenericDocumentToProtoConverter.convert(DOCUMENT_PROPERTIES_1)));
- propertyProtoMap.put("documentKey2",
- PropertyProto.newBuilder().setName("documentKey2")
+ propertyProtoMap.put(
+ "documentKey2",
+ PropertyProto.newBuilder()
+ .setName("documentKey2")
.addDocumentValues(
GenericDocumentToProtoConverter.convert(DOCUMENT_PROPERTIES_2)));
List<String> sortedKey = new ArrayList<>(propertyProtoMap.keySet());
@@ -97,8 +107,7 @@
documentProtoBuilder.addProperties(propertyProtoMap.get(key));
}
DocumentProto documentProto = documentProtoBuilder.build();
- assertThat(GenericDocumentToProtoConverter.convert(document))
- .isEqualTo(documentProto);
+ assertThat(GenericDocumentToProtoConverter.convert(document)).isEqualTo(documentProto);
assertThat(document).isEqualTo(GenericDocumentToProtoConverter.convert(documentProto));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
index 7336c3c..dedfca4 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
@@ -20,9 +20,9 @@
import android.app.appsearch.AppSearchSchema;
-import com.android.server.appsearch.proto.IndexingConfig;
import com.android.server.appsearch.proto.PropertyConfigProto;
import com.android.server.appsearch.proto.SchemaTypeConfigProto;
+import com.android.server.appsearch.proto.StringIndexingConfig;
import com.android.server.appsearch.proto.TermMatchType;
import org.junit.Test;
@@ -30,84 +30,126 @@
public class SchemaToProtoConverterTest {
@Test
public void testGetProto_Email() {
- AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
- .addProperty(new AppSearchSchema.PropertyConfig.Builder("subject")
- .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()
- ).addProperty(new AppSearchSchema.PropertyConfig.Builder("body")
- .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()
- ).build();
+ AppSearchSchema emailSchema =
+ new AppSearchSchema.Builder("Email")
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("subject")
+ .setDataType(
+ AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(
+ AppSearchSchema.PropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("body")
+ .setDataType(
+ AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(
+ AppSearchSchema.PropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
- SchemaTypeConfigProto expectedEmailProto = SchemaTypeConfigProto.newBuilder()
- .setSchemaType("Email")
- .addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("subject")
- .setDataType(PropertyConfigProto.DataType.Code.STRING)
- .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
- .setIndexingConfig(
- com.android.server.appsearch.proto.IndexingConfig.newBuilder()
- .setTokenizerType(IndexingConfig.TokenizerType.Code.PLAIN)
- .setTermMatchType(TermMatchType.Code.PREFIX)
- )
- ).addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("body")
- .setDataType(PropertyConfigProto.DataType.Code.STRING)
- .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
- .setIndexingConfig(
- com.android.server.appsearch.proto.IndexingConfig.newBuilder()
- .setTokenizerType(IndexingConfig.TokenizerType.Code.PLAIN)
- .setTermMatchType(TermMatchType.Code.PREFIX)
- )
- ).build();
+ SchemaTypeConfigProto expectedEmailProto =
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("Email")
+ .addProperties(
+ PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(
+ PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setStringIndexingConfig(
+ StringIndexingConfig.newBuilder()
+ .setTokenizerType(
+ StringIndexingConfig.TokenizerType
+ .Code.PLAIN)
+ .setTermMatchType(
+ TermMatchType.Code.PREFIX)))
+ .addProperties(
+ PropertyConfigProto.newBuilder()
+ .setPropertyName("body")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(
+ PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setStringIndexingConfig(
+ StringIndexingConfig.newBuilder()
+ .setTokenizerType(
+ StringIndexingConfig.TokenizerType
+ .Code.PLAIN)
+ .setTermMatchType(
+ TermMatchType.Code.PREFIX)))
+ .build();
assertThat(SchemaToProtoConverter.convert(emailSchema)).isEqualTo(expectedEmailProto);
}
@Test
public void testGetProto_MusicRecording() {
- AppSearchSchema musicRecordingSchema = new AppSearchSchema.Builder("MusicRecording")
- .addProperty(new AppSearchSchema.PropertyConfig.Builder("artist")
- .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
- .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()
- ).addProperty(new AppSearchSchema.PropertyConfig.Builder("pubDate")
- .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
- .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
- .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
- .build()
- ).build();
+ AppSearchSchema musicRecordingSchema =
+ new AppSearchSchema.Builder("MusicRecording")
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("artist")
+ .setDataType(
+ AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .setIndexingType(
+ AppSearchSchema.PropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(
+ new AppSearchSchema.PropertyConfig.Builder("pubDate")
+ .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(
+ AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
+ .setTokenizerType(
+ AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
+ .build())
+ .build();
- SchemaTypeConfigProto expectedMusicRecordingProto = SchemaTypeConfigProto.newBuilder()
- .setSchemaType("MusicRecording")
- .addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("artist")
- .setDataType(PropertyConfigProto.DataType.Code.STRING)
- .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
- .setIndexingConfig(
- com.android.server.appsearch.proto.IndexingConfig.newBuilder()
- .setTokenizerType(IndexingConfig.TokenizerType.Code.PLAIN)
- .setTermMatchType(TermMatchType.Code.PREFIX)
- )
- ).addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("pubDate")
- .setDataType(PropertyConfigProto.DataType.Code.INT64)
- .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
- .setIndexingConfig(
- com.android.server.appsearch.proto.IndexingConfig.newBuilder()
- .setTokenizerType(IndexingConfig.TokenizerType.Code.NONE)
- .setTermMatchType(TermMatchType.Code.UNKNOWN)
- )
- ).build();
+ SchemaTypeConfigProto expectedMusicRecordingProto =
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("MusicRecording")
+ .addProperties(
+ PropertyConfigProto.newBuilder()
+ .setPropertyName("artist")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(
+ PropertyConfigProto.Cardinality.Code.REPEATED)
+ .setStringIndexingConfig(
+ StringIndexingConfig.newBuilder()
+ .setTokenizerType(
+ StringIndexingConfig.TokenizerType
+ .Code.PLAIN)
+ .setTermMatchType(
+ TermMatchType.Code.PREFIX)))
+ .addProperties(
+ PropertyConfigProto.newBuilder()
+ .setPropertyName("pubDate")
+ .setDataType(PropertyConfigProto.DataType.Code.INT64)
+ .setCardinality(
+ PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setStringIndexingConfig(
+ StringIndexingConfig.newBuilder()
+ .setTokenizerType(
+ StringIndexingConfig.TokenizerType
+ .Code.NONE)
+ .setTermMatchType(
+ TermMatchType.Code.UNKNOWN)))
+ .build();
assertThat(SchemaToProtoConverter.convert(musicRecordingSchema))
.isEqualTo(expectedMusicRecordingProto);
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
index 2e9286c..518f532 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
@@ -36,9 +36,10 @@
public void testSingleStringSnippet() {
final String propertyKeyString = "content";
- final String propertyValueString = "A commonly used fake word is foo.\n"
- + " Another nonsense word that’s used a lot\n"
- + " is bar.\n";
+ final String propertyValueString =
+ "A commonly used fake word is foo.\n"
+ + " Another nonsense word that’s used a lot\n"
+ + " is bar.\n";
final String uri = "uri1";
final String schemaType = "schema1";
final String searchWord = "foo";
@@ -46,34 +47,39 @@
final String window = "is foo";
// Building the SearchResult received from query.
- PropertyProto property = PropertyProto.newBuilder()
- .setName(propertyKeyString)
- .addStringValues(propertyValueString)
- .build();
- DocumentProto documentProto = DocumentProto.newBuilder()
- .setUri(uri)
- .setSchema(schemaType)
- .addProperties(property)
- .build();
- SnippetProto snippetProto = SnippetProto.newBuilder()
- .addEntries(SnippetProto.EntryProto.newBuilder()
- .setPropertyName(propertyKeyString)
- .addSnippetMatches(SnippetMatchProto.newBuilder()
- .setValuesIndex(0)
- .setExactMatchPosition(29)
- .setExactMatchBytes(3)
- .setWindowPosition(26)
- .setWindowBytes(6)
- .build())
- .build())
- .build();
- SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder()
- .setDocument(documentProto)
- .setSnippet(snippetProto)
- .build();
- SearchResultProto searchResultProto = SearchResultProto.newBuilder()
- .addResults(resultProto)
- .build();
+ PropertyProto property =
+ PropertyProto.newBuilder()
+ .setName(propertyKeyString)
+ .addStringValues(propertyValueString)
+ .build();
+ DocumentProto documentProto =
+ DocumentProto.newBuilder()
+ .setUri(uri)
+ .setSchema(schemaType)
+ .addProperties(property)
+ .build();
+ SnippetProto snippetProto =
+ SnippetProto.newBuilder()
+ .addEntries(
+ SnippetProto.EntryProto.newBuilder()
+ .setPropertyName(propertyKeyString)
+ .addSnippetMatches(
+ SnippetMatchProto.newBuilder()
+ .setValuesIndex(0)
+ .setExactMatchPosition(29)
+ .setExactMatchBytes(3)
+ .setWindowPosition(26)
+ .setWindowBytes(6)
+ .build())
+ .build())
+ .build();
+ SearchResultProto.ResultProto resultProto =
+ SearchResultProto.ResultProto.newBuilder()
+ .setDocument(documentProto)
+ .setSnippet(snippetProto)
+ .build();
+ SearchResultProto searchResultProto =
+ SearchResultProto.newBuilder().addResults(resultProto).build();
// Making ResultReader and getting Snippet values.
SearchResultPage searchResultPage =
@@ -83,11 +89,11 @@
assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
assertThat(match.getFullText()).isEqualTo(propertyValueString);
assertThat(match.getExactMatch()).isEqualTo(exactMatch);
- assertThat(match.getExactMatchPosition()).isEqualTo(
- new SearchResult.MatchRange(/*lower=*/29, /*upper=*/32));
+ assertThat(match.getExactMatchPosition())
+ .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 29, /*upper=*/ 32));
assertThat(match.getFullText()).isEqualTo(propertyValueString);
- assertThat(match.getSnippetPosition()).isEqualTo(
- new SearchResult.MatchRange(/*lower=*/26, /*upper=*/32));
+ assertThat(match.getSnippetPosition())
+ .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 26, /*upper=*/ 32));
assertThat(match.getSnippet()).isEqualTo(window);
}
}
@@ -97,9 +103,10 @@
public void testNoSnippets() throws Exception {
final String propertyKeyString = "content";
- final String propertyValueString = "A commonly used fake word is foo.\n"
- + " Another nonsense word that’s used a lot\n"
- + " is bar.\n";
+ final String propertyValueString =
+ "A commonly used fake word is foo.\n"
+ + " Another nonsense word that’s used a lot\n"
+ + " is bar.\n";
final String uri = "uri1";
final String schemaType = "schema1";
final String searchWord = "foo";
@@ -107,21 +114,21 @@
final String window = "is foo";
// Building the SearchResult received from query.
- PropertyProto property = PropertyProto.newBuilder()
- .setName(propertyKeyString)
- .addStringValues(propertyValueString)
- .build();
- DocumentProto documentProto = DocumentProto.newBuilder()
- .setUri(uri)
- .setSchema(schemaType)
- .addProperties(property)
- .build();
- SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder()
- .setDocument(documentProto)
- .build();
- SearchResultProto searchResultProto = SearchResultProto.newBuilder()
- .addResults(resultProto)
- .build();
+ PropertyProto property =
+ PropertyProto.newBuilder()
+ .setName(propertyKeyString)
+ .addStringValues(propertyValueString)
+ .build();
+ DocumentProto documentProto =
+ DocumentProto.newBuilder()
+ .setUri(uri)
+ .setSchema(schemaType)
+ .addProperties(property)
+ .build();
+ SearchResultProto.ResultProto resultProto =
+ SearchResultProto.ResultProto.newBuilder().setDocument(documentProto).build();
+ SearchResultProto searchResultProto =
+ SearchResultProto.newBuilder().addResults(resultProto).build();
SearchResultPage searchResultPage =
SearchResultToProtoConverter.convertToSearchResultPage(searchResultProto);
@@ -135,54 +142,57 @@
final String searchWord = "Test";
// Building the SearchResult received from query.
- PropertyProto property1 = PropertyProto.newBuilder()
- .setName("sender.name")
- .addStringValues("Test Name Jr.")
- .build();
- PropertyProto property2 = PropertyProto.newBuilder()
- .setName("sender.email")
- .addStringValues("TestNameJr@gmail.com")
- .build();
- DocumentProto documentProto = DocumentProto.newBuilder()
- .setUri("uri1")
- .setSchema("schema1")
- .addProperties(property1)
- .addProperties(property2)
- .build();
- SnippetProto snippetProto = SnippetProto.newBuilder()
- .addEntries(
- SnippetProto.EntryProto.newBuilder()
- .setPropertyName("sender.name")
- .addSnippetMatches(
- SnippetMatchProto.newBuilder()
- .setValuesIndex(0)
- .setExactMatchPosition(0)
- .setExactMatchBytes(4)
- .setWindowPosition(0)
- .setWindowBytes(9)
- .build())
- .build())
- .addEntries(
- SnippetProto.EntryProto.newBuilder()
- .setPropertyName("sender.email")
- .addSnippetMatches(
- SnippetMatchProto.newBuilder()
- .setValuesIndex(0)
- .setExactMatchPosition(0)
- .setExactMatchBytes(20)
- .setWindowPosition(0)
- .setWindowBytes(20)
- .build())
- .build()
- )
- .build();
- SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder()
- .setDocument(documentProto)
- .setSnippet(snippetProto)
- .build();
- SearchResultProto searchResultProto = SearchResultProto.newBuilder()
- .addResults(resultProto)
- .build();
+ PropertyProto property1 =
+ PropertyProto.newBuilder()
+ .setName("sender.name")
+ .addStringValues("Test Name Jr.")
+ .build();
+ PropertyProto property2 =
+ PropertyProto.newBuilder()
+ .setName("sender.email")
+ .addStringValues("TestNameJr@gmail.com")
+ .build();
+ DocumentProto documentProto =
+ DocumentProto.newBuilder()
+ .setUri("uri1")
+ .setSchema("schema1")
+ .addProperties(property1)
+ .addProperties(property2)
+ .build();
+ SnippetProto snippetProto =
+ SnippetProto.newBuilder()
+ .addEntries(
+ SnippetProto.EntryProto.newBuilder()
+ .setPropertyName("sender.name")
+ .addSnippetMatches(
+ SnippetMatchProto.newBuilder()
+ .setValuesIndex(0)
+ .setExactMatchPosition(0)
+ .setExactMatchBytes(4)
+ .setWindowPosition(0)
+ .setWindowBytes(9)
+ .build())
+ .build())
+ .addEntries(
+ SnippetProto.EntryProto.newBuilder()
+ .setPropertyName("sender.email")
+ .addSnippetMatches(
+ SnippetMatchProto.newBuilder()
+ .setValuesIndex(0)
+ .setExactMatchPosition(0)
+ .setExactMatchBytes(20)
+ .setWindowPosition(0)
+ .setWindowBytes(20)
+ .build())
+ .build())
+ .build();
+ SearchResultProto.ResultProto resultProto =
+ SearchResultProto.ResultProto.newBuilder()
+ .setDocument(documentProto)
+ .setSnippet(snippetProto)
+ .build();
+ SearchResultProto searchResultProto =
+ SearchResultProto.newBuilder().addResults(resultProto).build();
// Making ResultReader and getting Snippet values.
SearchResultPage searchResultPage =
@@ -192,21 +202,21 @@
SearchResult.MatchInfo match1 = result.getMatches().get(0);
assertThat(match1.getPropertyPath()).isEqualTo("sender.name");
assertThat(match1.getFullText()).isEqualTo("Test Name Jr.");
- assertThat(match1.getExactMatchPosition()).isEqualTo(
- new SearchResult.MatchRange(/*lower=*/0, /*upper=*/4));
+ assertThat(match1.getExactMatchPosition())
+ .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 4));
assertThat(match1.getExactMatch()).isEqualTo("Test");
- assertThat(match1.getSnippetPosition()).isEqualTo(
- new SearchResult.MatchRange(/*lower=*/0, /*upper=*/9));
+ assertThat(match1.getSnippetPosition())
+ .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 9));
assertThat(match1.getSnippet()).isEqualTo("Test Name");
SearchResult.MatchInfo match2 = result.getMatches().get(1);
assertThat(match2.getPropertyPath()).isEqualTo("sender.email");
assertThat(match2.getFullText()).isEqualTo("TestNameJr@gmail.com");
- assertThat(match2.getExactMatchPosition()).isEqualTo(
- new SearchResult.MatchRange(/*lower=*/0, /*upper=*/20));
+ assertThat(match2.getExactMatchPosition())
+ .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 20));
assertThat(match2.getExactMatch()).isEqualTo("TestNameJr@gmail.com");
- assertThat(match2.getSnippetPosition()).isEqualTo(
- new SearchResult.MatchRange(/*lower=*/0, /*upper=*/20));
+ assertThat(match2.getSnippetPosition())
+ .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 20));
assertThat(match2.getSnippet()).isEqualTo("TestNameJr@gmail.com");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index ce3b8d6..dfeed13 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -1248,4 +1248,91 @@
assertThat(mHdmiControlService.getActiveSource().getPhysicalAddress()).isEqualTo(
externalDevice.getPhysicalAddress());
}
+
+ @Test
+ public void queryDisplayStatus() {
+ mHdmiControlService.queryDisplayStatus(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ mPlaybackLogicalAddress, Constants.ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ }
+
+ @Test
+ public void toggleAndFollowTvPower_ToTv_TvStatusOn() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV);
+ mStandby = false;
+ mHdmiControlService.toggleAndFollowTvPower();
+ HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+ mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby(
+ mPlaybackLogicalAddress, ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mStandby).isTrue();
+ }
+
+ @Test
+ public void toggleAndFollowTvPower_Broadcast_TvStatusOn() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST);
+ mStandby = false;
+ mHdmiControlService.toggleAndFollowTvPower();
+ HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+ mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby(
+ mPlaybackLogicalAddress, ADDR_BROADCAST);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mStandby).isTrue();
+ }
+
+ @Test
+ public void toggleAndFollowTvPower_TvStatusStandby() {
+ mStandby = false;
+ mHdmiControlService.toggleAndFollowTvPower();
+ HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+ mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_STANDBY);
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress,
+ ADDR_TV);
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+ mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+ assertThat(mStandby).isFalse();
+ }
+
+ @Test
+ public void toggleAndFollowTvPower_TvStatusUnknown() {
+ mStandby = false;
+ mHdmiControlService.toggleAndFollowTvPower();
+ HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+ mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_UNKNOWN);
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
+ mPlaybackLogicalAddress, Constants.ADDR_TV,
+ HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
+ HdmiCecMessage userControlReleased = HdmiCecMessageBuilder.buildUserControlReleased(
+ mPlaybackLogicalAddress, Constants.ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(userControlPressed);
+ assertThat(mNativeWrapper.getResultMessages()).contains(userControlReleased);
+ assertThat(mStandby).isFalse();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
index 6f62014..6496264 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
@@ -22,6 +22,7 @@
import static com.google.common.truth.Truth.assertThat;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.platform.test.annotations.Presubmit;
@@ -103,7 +104,7 @@
@Test
public void buildReportFeatures_basicTv_1_4() {
HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- Constants.VERSION_1_4,
+ HdmiControlManager.HDMI_CEC_VERSION_1_4_b,
Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
@@ -113,7 +114,7 @@
@Test
public void buildReportFeatures_basicPlayback_1_4() {
HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_PLAYBACK_1,
- Constants.VERSION_1_4,
+ HdmiControlManager.HDMI_CEC_VERSION_1_4_b,
Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_TV,
Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
@@ -123,7 +124,7 @@
@Test
public void buildReportFeatures_basicPlaybackAudioSystem_1_4() {
HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_PLAYBACK_1,
- Constants.VERSION_1_4,
+ HdmiControlManager.HDMI_CEC_VERSION_1_4_b,
Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_TV,
Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
@@ -134,7 +135,7 @@
@Test
public void buildReportFeatures_basicTv_2_0() {
HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- Constants.VERSION_2_0,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0,
Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
@@ -144,7 +145,7 @@
@Test
public void buildReportFeatures_remoteControlTv_2_0() {
HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- Constants.VERSION_2_0,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0,
Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
Lists.newArrayList(Constants.RC_PROFILE_TV_ONE), Collections.emptyList());
@@ -154,7 +155,7 @@
@Test
public void buildReportFeatures_remoteControlPlayback_2_0() {
HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- Constants.VERSION_2_0,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0,
Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE,
Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU), Collections.emptyList());
@@ -165,7 +166,7 @@
@Test
public void buildReportFeatures_deviceFeaturesTv_2_0() {
HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- Constants.VERSION_2_0,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0,
Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
Lists.newArrayList(Constants.DEVICE_FEATURE_TV_SUPPORTS_RECORD_TV_SCREEN));
@@ -176,7 +177,7 @@
@Test
public void buildReportFeatures_deviceFeaturesPlayback_2_0() {
HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
- Constants.VERSION_2_0,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0,
Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE,
Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU),
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index f3a4366..a05cbb4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -67,8 +67,8 @@
public void isValid_reportPowerStatus() {
assertMessageValidity("04:90:00").isEqualTo(OK);
assertMessageValidity("04:90:03:05").isEqualTo(OK);
+ assertMessageValidity("0F:90:00").isEqualTo(OK);
- assertMessageValidity("0F:90:00").isEqualTo(ERROR_DESTINATION);
assertMessageValidity("F0:90").isEqualTo(ERROR_SOURCE);
assertMessageValidity("04:90").isEqualTo(ERROR_PARAMETER_SHORT);
assertMessageValidity("04:90:04").isEqualTo(ERROR_PARAMETER);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
new file mode 100644
index 0000000..3cc7c6b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+/** Tests for {@link HdmiCecPowerStatusController} class. */
+public class HdmiCecPowerStatusControllerTest {
+
+ public static final int[] ARRAY_POWER_STATUS = new int[]{HdmiControlManager.POWER_STATUS_ON,
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY,
+ HdmiControlManager.POWER_STATUS_STANDBY};
+ private HdmiCecPowerStatusController mHdmiCecPowerStatusController;
+ private FakeNativeWrapper mNativeWrapper;
+ private TestLooper mTestLooper = new TestLooper();
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private int mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_b;
+ @Mock
+ private IPowerManager mIPowerManagerMock;
+ @Mock
+ private IThermalService mIThermalServiceMock;
+ private HdmiControlService mHdmiControlService;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ Looper myLooper = mTestLooper.getLooper();
+ PowerManager powerManager = new PowerManager(contextSpy, mIPowerManagerMock,
+ mIThermalServiceMock, new Handler(myLooper));
+ when(contextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+ when(contextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+ when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+ HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(contextSpy);
+
+ mHdmiControlService = new HdmiControlService(contextSpy) {
+ @Override
+ boolean isControlEnabled() {
+ return true;
+ }
+
+ @Override
+ boolean isPlaybackDevice() {
+ return true;
+ }
+
+ @Override
+ void writeStringSystemProperty(String key, String value) {
+ // do nothing
+ }
+
+ @Override
+ int getCecVersion() {
+ return mHdmiCecVersion;
+ }
+
+ @Override
+ boolean isPowerStandby() {
+ return false;
+ }
+
+ @Override
+ HdmiCecConfig getHdmiCecConfig() {
+ return hdmiCecConfig;
+ }
+ };
+ mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+ HdmiCecLocalDevicePlayback hdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(
+ mHdmiControlService);
+ hdmiCecLocalDevicePlayback.init();
+ mHdmiControlService.setIoLooper(myLooper);
+ mNativeWrapper = new FakeNativeWrapper();
+ HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiControlService.setCecController(hdmiCecController);
+ mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+ mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+ mLocalDevices.add(hdmiCecLocalDevicePlayback);
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ hdmiPortInfos[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ mNativeWrapper.setPortInfo(hdmiPortInfos);
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ mHdmiControlService.initService();
+ mHdmiControlService.getHdmiCecNetwork().initPortInfo();
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mNativeWrapper.setPhysicalAddress(0x2000);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecPowerStatusController = new HdmiCecPowerStatusController(mHdmiControlService);
+ mNativeWrapper.clearResultMessages();
+ }
+
+ @Test
+ public void setPowerStatus() {
+ for (int status : ARRAY_POWER_STATUS) {
+ mHdmiCecPowerStatusController.setPowerStatus(status);
+ assertThat(mHdmiCecPowerStatusController.getPowerStatus()).isEqualTo(status);
+ }
+ }
+
+ @Test
+ public void isPowerStatusOn() {
+ for (int status : ARRAY_POWER_STATUS) {
+ mHdmiCecPowerStatusController.setPowerStatus(status);
+ assertThat(mHdmiCecPowerStatusController.isPowerStatusOn()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_ON == status);
+ }
+ }
+
+ @Test
+ public void isPowerStatusStandby() {
+ for (int status : ARRAY_POWER_STATUS) {
+ mHdmiCecPowerStatusController.setPowerStatus(status);
+ assertThat(mHdmiCecPowerStatusController.isPowerStatusStandby()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_STANDBY == status);
+ }
+ }
+
+ @Test
+ public void isPowerStatusTransientToOn() {
+ for (int status : ARRAY_POWER_STATUS) {
+ mHdmiCecPowerStatusController.setPowerStatus(status);
+ assertThat(mHdmiCecPowerStatusController.isPowerStatusTransientToOn()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON == status);
+ }
+ }
+
+ @Test
+ public void isPowerStatusTransientToStandby() {
+ for (int status : ARRAY_POWER_STATUS) {
+ mHdmiCecPowerStatusController.setPowerStatus(status);
+ assertThat(mHdmiCecPowerStatusController.isPowerStatusTransientToStandby()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY == status);
+ }
+ }
+
+ @Test
+ public void setPowerStatus_doesntSendBroadcast_1_4() {
+ mHdmiCecPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+ HdmiControlManager.POWER_STATUS_ON);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
+ }
+
+ @Test
+ public void setPowerStatus_transient_doesntSendBroadcast_1_4() {
+ mHdmiCecPowerStatusController.setPowerStatus(
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
+ }
+
+ @Test
+ public void setPowerStatus_fast_transient_doesntSendBroadcast_1_4() {
+ mHdmiCecPowerStatusController.setPowerStatus(
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON, false);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
+ }
+
+ @Test
+ public void setPowerStatus_sendsBroadcast_2_0() {
+ mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
+
+ mHdmiCecPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+ HdmiControlManager.POWER_STATUS_ON);
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportPowerStatus);
+ }
+
+ @Test
+ public void setPowerStatus_transient_sendsBroadcast_2_0() {
+ mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
+
+ mHdmiCecPowerStatusController.setPowerStatus(
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportPowerStatus);
+ }
+
+ @Test
+ public void setPowerStatus_fast_transient_doesntSendBroadcast_2_0() {
+ mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
+
+ mHdmiCecPowerStatusController.setPowerStatus(
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON, false);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 2e4bed9..9d767cd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -243,6 +243,50 @@
}
@Test
+ public void initialPowerStatus_normalBoot_goToStandby_doesNotBroadcastsPowerStatus_1_4() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_1_4_b);
+
+ mHdmiControlService.setControlEnabled(true);
+ mNativeWrapper.clearResultMessages();
+
+ assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
+
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+
+ HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+ HdmiControlManager.POWER_STATUS_STANDBY);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
+ }
+
+ @Test
+ public void initialPowerStatus_normalBoot_goToStandby_broadcastsPowerStatus_2_0() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+
+ mHdmiControlService.setControlEnabled(true);
+ mNativeWrapper.clearResultMessages();
+
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
+
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+ HdmiControlManager.POWER_STATUS_STANDBY);
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportPowerStatus);
+ }
+
+ @Test
public void setAndGetCecVolumeControlEnabled_isApi() {
mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isFalse();
@@ -470,7 +514,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures(
- Constants.ADDR_PLAYBACK_1, Constants.VERSION_2_0,
+ Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0,
Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
mMyPlaybackDevice.getRcProfile(), mMyPlaybackDevice.getRcFeatures(),
mMyPlaybackDevice.getDeviceFeatures());
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 360e11c..e46ab6b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -586,7 +586,7 @@
@Override
boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) {
- return true;
+ return mInjectCheckAccessShortcutsPermission;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index c7a05ba..194ae05 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -490,6 +490,7 @@
mManager.pushDynamicShortcut(s8);
assertEquals(4, getCallerShortcut("s8").getRank());
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s8"), HANDLE_USER_0,
CACHE_OWNER_0);
});
@@ -1456,6 +1457,7 @@
// Cache 1 and 2
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"),
HANDLE_USER_0, CACHE_OWNER_0);
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"),
@@ -1538,6 +1540,7 @@
// Cache some, but non long lived shortcuts will be ignored.
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"),
HANDLE_USER_0, CACHE_OWNER_0);
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
@@ -1597,6 +1600,48 @@
"s2");
}
+ public void testCachedShortcuts_accessShortcutsPermission() {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
+ makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
+ makeLongLivedShortcut("s4"))));
+ });
+
+ // s1 is not long lived and will be ignored.
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = false;
+ assertExpectException(
+ SecurityException.class, "Caller can't access shortcut information", () -> {
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3"),
+ HANDLE_USER_0, CACHE_OWNER_0);
+ });
+ // Give ACCESS_SHORTCUTS permission to LAUNCHER_1
+ mInjectCheckAccessShortcutsPermission = true;
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3"),
+ HANDLE_USER_0, CACHE_OWNER_0);
+ });
+
+ setCaller(CALLING_PACKAGE_1);
+
+ // Get cached shortcuts
+ assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s2", "s3");
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = false;
+ assertExpectException(
+ SecurityException.class, "Caller can't access shortcut information", () -> {
+ mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
+ HANDLE_USER_0, CACHE_OWNER_0);
+ });
+ // Give ACCESS_SHORTCUTS permission to LAUNCHER_1
+ mInjectCheckAccessShortcutsPermission = true;
+ mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
+ HANDLE_USER_0, CACHE_OWNER_0);
+ });
+
+ assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s3");
+ }
+
public void testCachedShortcuts_canPassShortcutLimit() {
// Change the max number of shortcuts.
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=4");
@@ -1609,6 +1654,7 @@
// Cache All
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"),
HANDLE_USER_0, CACHE_OWNER_0);
});
@@ -1808,6 +1854,7 @@
setCaller(LAUNCHER_1);
// Cache some shortcuts. Only long lived shortcuts can get cached.
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"), getCallingUser(),
CACHE_OWNER_0);
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_3, list("s3"), getCallingUser(),
@@ -2009,6 +2056,53 @@
});
}
+ public void testGetShortcuts_personsFlag() {
+ ShortcutInfo s = new ShortcutInfo.Builder(mClientContext, "id")
+ .setShortLabel("label")
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setPerson(makePerson("person", "personKey", "personUri"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .build();
+
+ setCaller(CALLING_PACKAGE_1);
+ assertTrue(mManager.setDynamicShortcuts(list(s)));
+
+ setCaller(LAUNCHER_1);
+
+ assertNull(mLauncherApps.getShortcuts(buildQuery(
+ /* time =*/ 0, CALLING_PACKAGE_1, /* activity =*/ null,
+ ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+ getCallingUser()).get(0).getPersons());
+
+ assertNull(mLauncherApps.getShortcuts(buildQuery(
+ /* time =*/ 0, CALLING_PACKAGE_1, /* activity =*/ null,
+ ShortcutQuery.FLAG_MATCH_DYNAMIC),
+ getCallingUser()).get(0).getPersons());
+
+ // Using FLAG_GET_PERSONS_DATA should fail without permission
+ mInjectCheckAccessShortcutsPermission = false;
+ assertExpectException(
+ SecurityException.class, "Caller can't access shortcut information", () -> {
+ mLauncherApps.getShortcuts(buildQuery(
+ /* time =*/ 0, CALLING_PACKAGE_1, /* activity =*/ null,
+ ShortcutQuery.FLAG_MATCH_DYNAMIC
+ | ShortcutQuery.FLAG_GET_PERSONS_DATA),
+ getCallingUser());
+ });
+
+ mInjectCheckAccessShortcutsPermission = true;
+ assertEquals("person", mLauncherApps.getShortcuts(buildQuery(
+ /* time =*/ 0, CALLING_PACKAGE_1, /* activity =*/ null,
+ ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_GET_PERSONS_DATA),
+ getCallingUser()).get(0).getPersons()[0].getName());
+
+ assertNull(mLauncherApps.getShortcuts(buildQuery(
+ /* time =*/ 0, CALLING_PACKAGE_1, /* activity =*/ null,
+ ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_GET_PERSONS_DATA
+ | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+ getCallingUser()).get(0).getPersons());
+ }
+
// TODO resource
public void testGetShortcutInfo() {
// Create shortcuts.
@@ -8740,6 +8834,7 @@
assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s3", USER_0,
filter_any));
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"), HANDLE_USER_0,
CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
index 6a2b8e0..c8a4052 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
@@ -117,6 +117,7 @@
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0);
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
CACHE_OWNER_0);
});
@@ -216,6 +217,7 @@
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
mTestLooper.getNewExecutor());
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0,
CACHE_OWNER_0);
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0,
@@ -242,6 +244,7 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0,
CACHE_OWNER_0);
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
@@ -274,6 +277,7 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"), HANDLE_USER_0,
CACHE_OWNER_0);
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
@@ -301,6 +305,7 @@
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3"), HANDLE_USER_0,
CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
@@ -500,6 +505,7 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0,
CACHE_OWNER_0);
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
@@ -559,6 +565,7 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
@@ -596,6 +603,7 @@
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
@@ -664,6 +672,7 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
@@ -731,6 +740,7 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
@@ -799,6 +809,7 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
new file mode 100644
index 0000000..92942bb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.policy;
+
+
+import static android.content.Context.SENSOR_SERVICE;
+
+import static com.android.server.policy.DeviceStateProviderImpl.DEFAULT_DEVICE_STATE;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorManager;
+import android.hardware.input.InputManagerInternal;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.LocalServices;
+import com.android.server.devicestate.DeviceStateProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.util.List;
+
+/**
+ * Unit tests for {@link DeviceStateProviderImpl}.
+ * <p/>
+ * Run with <code>atest DeviceStateProviderImplTest</code>.
+ */
+public final class DeviceStateProviderImplTest {
+ private final ArgumentCaptor<int[]> mIntArrayCaptor = ArgumentCaptor.forClass(int[].class);
+ private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ private Context mContext;
+ private SensorManager mSensorManager;
+
+ @Before
+ public void setup() {
+ LocalServices.addService(InputManagerInternal.class, mock(InputManagerInternal.class));
+ mContext = mock(Context.class);
+ mSensorManager = mock(SensorManager.class);
+ when(mContext.getSystemServiceName(eq(SensorManager.class))).thenReturn(SENSOR_SERVICE);
+ when(mContext.getSystemService(eq(SENSOR_SERVICE))).thenReturn(mSensorManager);
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(InputManagerInternal.class);
+ }
+
+ @Test
+ public void create_noConfig() {
+ assertDefaultProviderValues(null);
+ }
+
+ @Test
+ public void create_emptyFile() {
+ String configString = "";
+ DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+
+ assertDefaultProviderValues(config);
+ }
+
+ @Test
+ public void create_emptyConfig() {
+ String configString = "<device-state-config></device-state-config>";
+ DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+
+ assertDefaultProviderValues(config);
+ }
+
+ @Test
+ public void create_invalidConfig() {
+ String configString = "<device-state-config>\n"
+ + " </device-state>\n"
+ + "</device-state-config>\n";
+ DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+
+ assertDefaultProviderValues(config);
+ }
+
+ private void assertDefaultProviderValues(
+ @Nullable DeviceStateProviderImpl.ReadableConfig config) {
+ DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+ config);
+
+ DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+ provider.setListener(listener);
+
+ verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+ assertArrayEquals(new int[] { DEFAULT_DEVICE_STATE }, mIntArrayCaptor.getValue());
+
+ verify(listener).onStateChanged(mIntegerCaptor.capture());
+ assertEquals(DEFAULT_DEVICE_STATE, mIntegerCaptor.getValue().intValue());
+ }
+
+ @Test
+ public void create_lidSwitch() {
+ String configString = "<device-state-config>\n"
+ + " <device-state>\n"
+ + " <identifier>1</identifier>\n"
+ + " <conditions>\n"
+ + " <lid-switch>\n"
+ + " <open>true</open>\n"
+ + " </lid-switch>\n"
+ + " </conditions>\n"
+ + " </device-state>\n"
+ + " <device-state>\n"
+ + " <identifier>2</identifier>\n"
+ + " <conditions>\n"
+ + " <lid-switch>\n"
+ + " <open>false</open>\n"
+ + " </lid-switch>\n"
+ + " </conditions>\n"
+ + " </device-state>\n"
+ + "</device-state-config>\n";
+ DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+ DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+ config);
+
+ DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+ provider.setListener(listener);
+
+ verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+ assertArrayEquals(new int[] { 1, 2 }, mIntArrayCaptor.getValue());
+
+ verify(listener).onStateChanged(mIntegerCaptor.capture());
+ assertEquals(2, mIntegerCaptor.getValue().intValue());
+
+ Mockito.clearInvocations(listener);
+
+ provider.notifyLidSwitchChanged(0, true /* lidOpen */);
+
+ verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+ verify(listener).onStateChanged(mIntegerCaptor.capture());
+ assertEquals(1, mIntegerCaptor.getValue().intValue());
+ }
+
+ @Test
+ public void create_sensor() throws Exception {
+ Sensor sensor = newSensor("sensor", Sensor.TYPE_HINGE_ANGLE);
+ when(mSensorManager.getSensorList(eq(sensor.getType()))).thenReturn(List.of(sensor));
+
+ String configString = "<device-state-config>\n"
+ + " <device-state>\n"
+ + " <identifier>1</identifier>\n"
+ + " <conditions>\n"
+ + " <sensor>\n"
+ + " <name>" + sensor.getName() + "</name>\n"
+ + " <type>" + sensor.getType() + "</type>\n"
+ + " <value>\n"
+ + " <max>90</max>\n"
+ + " </value>\n"
+ + " </sensor>\n"
+ + " </conditions>\n"
+ + " </device-state>\n"
+ + " <device-state>\n"
+ + " <identifier>2</identifier>\n"
+ + " <conditions>\n"
+ + " <sensor>\n"
+ + " <name>" + sensor.getName() + "</name>\n"
+ + " <type>" + sensor.getType() + "</type>\n"
+ + " <value>\n"
+ + " <min-inclusive>90</min-inclusive>\n"
+ + " <max>180</max>\n"
+ + " </value>\n"
+ + " </sensor>\n"
+ + " </conditions>\n"
+ + " </device-state>\n"
+ + " <device-state>\n"
+ + " <identifier>3</identifier>\n"
+ + " <conditions>\n"
+ + " <sensor>\n"
+ + " <name>" + sensor.getName() + "</name>\n"
+ + " <type>" + sensor.getType() + "</type>\n"
+ + " <value>\n"
+ + " <min-inclusive>180</min-inclusive>\n"
+ + " </value>\n"
+ + " </sensor>\n"
+ + " </conditions>\n"
+ + " </device-state>\n"
+ + "</device-state-config>\n";
+ DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+ DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+ config);
+
+ DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+ provider.setListener(listener);
+
+ verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+ assertArrayEquals(new int[] { 1, 2, 3 }, mIntArrayCaptor.getValue());
+
+ verify(listener).onStateChanged(mIntegerCaptor.capture());
+ assertEquals(1, mIntegerCaptor.getValue().intValue());
+
+ Mockito.clearInvocations(listener);
+
+ SensorEvent event0 = mock(SensorEvent.class);
+ event0.sensor = sensor;
+ FieldSetter.setField(event0, event0.getClass().getField("values"), new float[] { 180 });
+
+ provider.onSensorChanged(event0);
+
+ verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+ verify(listener).onStateChanged(mIntegerCaptor.capture());
+ assertEquals(3, mIntegerCaptor.getValue().intValue());
+
+ Mockito.clearInvocations(listener);
+
+ SensorEvent event1 = mock(SensorEvent.class);
+ event1.sensor = sensor;
+ FieldSetter.setField(event1, event1.getClass().getField("values"), new float[] { 90 });
+
+ provider.onSensorChanged(event1);
+
+ verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+ verify(listener).onStateChanged(mIntegerCaptor.capture());
+ assertEquals(2, mIntegerCaptor.getValue().intValue());
+
+ Mockito.clearInvocations(listener);
+
+ SensorEvent event2 = mock(SensorEvent.class);
+ event2.sensor = sensor;
+ FieldSetter.setField(event2, event2.getClass().getField("values"), new float[] { 0 });
+
+ provider.onSensorChanged(event2);
+
+ verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+ verify(listener).onStateChanged(mIntegerCaptor.capture());
+ assertEquals(1, mIntegerCaptor.getValue().intValue());
+ }
+
+ private static Sensor newSensor(String name, int type) throws Exception {
+ Constructor<Sensor> constructor = Sensor.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+
+ Sensor sensor = constructor.newInstance();
+ FieldSetter.setField(sensor, Sensor.class.getDeclaredField("mName"), name);
+ FieldSetter.setField(sensor, Sensor.class.getDeclaredField("mType"), type);
+ return sensor;
+ }
+
+ private static final class TestReadableConfig implements
+ DeviceStateProviderImpl.ReadableConfig {
+ private final byte[] mData;
+
+ TestReadableConfig(String configFileData) {
+ mData = configFileData.getBytes();
+ }
+
+ @NonNull
+ @Override
+ public InputStream openRead() throws IOException {
+ return new ByteArrayInputStream(mData);
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 9766cb5..00f706b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.GROUP_ALERT_ALL;
import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.GROUP_ALERT_SUMMARY;
@@ -38,6 +39,7 @@
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
@@ -50,9 +52,11 @@
import android.app.Notification.Builder;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Color;
+import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.net.Uri;
@@ -1596,6 +1600,77 @@
assertTrue(interrupter.isInterruptive());
}
+ @Test
+ public void testBubbleSuppressedNotificationDoesntMakeSound() {
+ Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
+ mock(PendingIntent.class), mock(Icon.class))
+ .build();
+
+ NotificationRecord record = getBuzzyNotification();
+ metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+ record.getNotification().setBubbleMetadata(metadata);
+ record.setAllowBubble(true);
+ record.getNotification().flags |= FLAG_BUBBLE;
+ record.isUpdate = true;
+ record.setInterruptive(false);
+
+ mService.buzzBeepBlinkLocked(record);
+ verifyNeverVibrate();
+ }
+
+ @Test
+ public void testOverflowBubbleSuppressedNotificationDoesntMakeSound() {
+ Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
+ mock(PendingIntent.class), mock(Icon.class))
+ .build();
+
+ NotificationRecord record = getBuzzyNotification();
+ metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+ record.getNotification().setBubbleMetadata(metadata);
+ record.setFlagBubbleRemoved(true);
+ record.setAllowBubble(true);
+ record.isUpdate = true;
+ record.setInterruptive(false);
+
+ mService.buzzBeepBlinkLocked(record);
+ verifyNeverVibrate();
+ }
+
+ @Test
+ public void testBubbleUpdateMakesSound() {
+ Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
+ mock(PendingIntent.class), mock(Icon.class))
+ .build();
+
+ NotificationRecord record = getBuzzyNotification();
+ record.getNotification().setBubbleMetadata(metadata);
+ record.setAllowBubble(true);
+ record.getNotification().flags |= FLAG_BUBBLE;
+ record.isUpdate = true;
+ record.setInterruptive(true);
+
+ mService.buzzBeepBlinkLocked(record);
+ verifyVibrate(1);
+ }
+
+ @Test
+ public void testNewBubbleSuppressedNotifMakesSound() {
+ Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
+ mock(PendingIntent.class), mock(Icon.class))
+ .build();
+
+ NotificationRecord record = getBuzzyNotification();
+ metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+ record.getNotification().setBubbleMetadata(metadata);
+ record.setAllowBubble(true);
+ record.getNotification().flags |= FLAG_BUBBLE;
+ record.isUpdate = false;
+ record.setInterruptive(true);
+
+ mService.buzzBeepBlinkLocked(record);
+ verifyVibrate(1);
+ }
+
static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
private final int mRepeatIndex;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 338ed06..f1dc098 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -31,6 +31,7 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.timeout;
import android.app.ActivityOptions;
@@ -53,6 +54,7 @@
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
+import java.util.function.ToIntFunction;
/**
* Tests for the {@link ActivityMetricsLaunchObserver} class.
@@ -158,6 +160,41 @@
verifyNoMoreInteractions(mLaunchObserver);
}
+ @Test
+ public void testLaunchState() {
+ final ToIntFunction<Boolean> launchTemplate = doRelaunch -> {
+ clearInvocations(mLaunchObserver);
+ onActivityLaunched(mTopActivity);
+ notifyTransitionStarting(mTopActivity);
+ if (doRelaunch) {
+ mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity);
+ }
+ final ActivityMetricsLogger.TransitionInfoSnapshot info =
+ notifyWindowsDrawn(mTopActivity);
+ verifyOnActivityLaunchFinished(mTopActivity);
+ return info.getLaunchState();
+ };
+
+ final WindowProcessController app = mTopActivity.app;
+ // Assume that the process is started (ActivityBuilder has mocked the returned value of
+ // ATMS#getProcessController) but the activity has not attached process.
+ mTopActivity.app = null;
+ assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+ .isEqualTo(WaitResult.LAUNCH_STATE_WARM);
+
+ mTopActivity.app = app;
+ assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+ .isEqualTo(WaitResult.LAUNCH_STATE_HOT);
+
+ assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(true /* doRelaunch */))
+ .isEqualTo(WaitResult.LAUNCH_STATE_RELAUNCH);
+
+ mTopActivity.app = null;
+ doReturn(null).when(mAtm).getProcessController(app.mName, app.mUid);
+ assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+ .isEqualTo(WaitResult.LAUNCH_STATE_COLD);
+ }
+
private void onActivityLaunched(ActivityRecord activity) {
onIntentStarted(activity.intent);
notifyActivityLaunched(START_SUCCESS, activity);
@@ -168,15 +205,10 @@
@Test
public void testOnActivityLaunchFinished() {
- // Assume that the process is started (ActivityBuilder has mocked the returned value of
- // ATMS#getProcessController) but the activity has not attached process.
- mTopActivity.app = null;
onActivityLaunched(mTopActivity);
notifyTransitionStarting(mTopActivity);
- final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity);
- assertWithMessage("Warm launch").that(info.getLaunchState())
- .isEqualTo(WaitResult.LAUNCH_STATE_WARM);
+ notifyWindowsDrawn(mTopActivity);
verifyOnActivityLaunchFinished(mTopActivity);
verifyNoMoreInteractions(mLaunchObserver);
@@ -231,8 +263,6 @@
assertWithMessage("Record start source").that(info.sourceType)
.isEqualTo(SourceInfo.TYPE_LAUNCHER);
assertWithMessage("Record event time").that(info.sourceEventDelayMs).isAtLeast(10);
- assertWithMessage("Hot launch").that(info.getLaunchState())
- .isEqualTo(WaitResult.LAUNCH_STATE_HOT);
verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mTopActivity), anyLong());
verifyOnActivityLaunchFinished(mTopActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 35ce923..53ade0e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -16,10 +16,8 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
@@ -125,61 +123,65 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class ActivityRecordTests extends WindowTestsBase {
- private Task mStack;
- private Task mTask;
- private ActivityRecord mActivity;
@Before
public void setUp() throws Exception {
- mTask = new TaskBuilder(mSupervisor)
- .setCreateParentTask(true).setCreateActivity(true).build();
- mStack = mTask.getRootTask();
- mActivity = mTask.getTopNonFinishingActivity();
-
setBooted(mAtm);
}
@Test
public void testStackCleanupOnClearingTask() {
- mActivity.onParentChanged(null /*newParent*/, mActivity.getTask());
- verify(mStack, times(1)).cleanUpActivityReferences(any());
+ final ActivityRecord activity = createActivityWith2LevelTask();
+ final Task task = activity.getTask();
+ final Task rootTask = activity.getRootTask();
+ activity.onParentChanged(null /*newParent*/, task);
+ verify(rootTask, times(1)).cleanUpActivityReferences(any());
}
@Test
public void testStackCleanupOnActivityRemoval() {
- mTask.removeChild(mActivity);
- verify(mStack, times(1)).cleanUpActivityReferences(any());
+ final ActivityRecord activity = createActivityWith2LevelTask();
+ final Task task = activity.getTask();
+ final Task rootTask = activity.getRootTask();
+ task.removeChild(activity);
+ verify(rootTask, times(1)).cleanUpActivityReferences(any());
}
@Test
public void testStackCleanupOnTaskRemoval() {
- mStack.removeChild(mTask, null /*reason*/);
- // Stack should be gone on task removal.
- assertNull(mAtm.mRootWindowContainer.getStack(mStack.mTaskId));
+ final ActivityRecord activity = createActivityWith2LevelTask();
+ final Task task = activity.getTask();
+ final Task rootTask = activity.getRootTask();
+ rootTask.removeChild(task, null /*reason*/);
+ // parentTask should be gone on task removal.
+ assertNull(mAtm.mRootWindowContainer.getStack(rootTask.mTaskId));
}
@Test
public void testRemoveChildWithOverlayActivity() {
- final ActivityRecord overlayActivity =
- new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ final ActivityRecord overlayActivity = new ActivityBuilder(mAtm).setTask(task).build();
overlayActivity.setTaskOverlay(true);
- final ActivityRecord overlayActivity2 =
- new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord overlayActivity2 = new ActivityBuilder(mAtm).setTask(task).build();
overlayActivity2.setTaskOverlay(true);
- mTask.removeChild(overlayActivity2, "test");
+ task.removeChild(overlayActivity2, "test");
verify(mSupervisor, never()).removeTask(any(), anyBoolean(), anyBoolean(), any());
}
@Test
public void testNoCleanupMovingActivityInSameStack() {
- final Task newTask = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(mStack).build();
- mActivity.reparent(newTask, 0, null /*reason*/);
- verify(mStack, times(0)).cleanUpActivityReferences(any());
+ final ActivityRecord activity = createActivityWith2LevelTask();
+ final Task rootTask = activity.getRootTask();
+ final Task newTask = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask).build();
+ activity.reparent(newTask, 0, null /*reason*/);
+ verify(rootTask, times(0)).cleanUpActivityReferences(any());
}
@Test
public void testPausingWhenVisibleFromStopped() throws Exception {
+ final ActivityRecord activity = createActivityWithTask();
final MutableBoolean pauseFound = new MutableBoolean(false);
doAnswer((InvocationOnMock invocationOnMock) -> {
final ClientTransaction transaction = invocationOnMock.getArgument(0);
@@ -187,49 +189,50 @@
pauseFound.value = true;
}
return null;
- }).when(mActivity.app.getThread()).scheduleTransaction(any());
+ }).when(activity.app.getThread()).scheduleTransaction(any());
- mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
+ activity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
// The activity is in the focused stack so it should be resumed.
- mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
- assertTrue(mActivity.isState(RESUMED));
+ activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+ assertTrue(activity.isState(RESUMED));
assertFalse(pauseFound.value);
// Make the activity non focusable
- mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
- doReturn(false).when(mActivity).isFocusable();
+ activity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
+ doReturn(false).when(activity).isFocusable();
// If the activity is not focusable, it should move to paused.
- mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
- assertTrue(mActivity.isState(PAUSING));
+ activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+ assertTrue(activity.isState(PAUSING));
assertTrue(pauseFound.value);
// Make sure that the state does not change for current non-stopping states.
- mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
- doReturn(true).when(mActivity).isFocusable();
+ activity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
+ doReturn(true).when(activity).isFocusable();
- mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+ activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
- assertTrue(mActivity.isState(INITIALIZING));
+ assertTrue(activity.isState(INITIALIZING));
// Make sure the state does not change if we are not the current top activity.
- mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind");
+ activity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind");
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
- mStack.mTranslucentActivityWaiting = topActivity;
- mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
- assertTrue(mActivity.isState(STARTED));
+ final Task task = activity.getTask();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
+ task.mTranslucentActivityWaiting = topActivity;
+ activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+ assertTrue(activity.isState(STARTED));
- mStack.mTranslucentActivityWaiting = null;
+ task.mTranslucentActivityWaiting = null;
topActivity.setOccludesParent(false);
- mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind non-opaque");
- mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
- assertTrue(mActivity.isState(STARTED));
+ activity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind non-opaque");
+ activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+ assertTrue(activity.isState(STARTED));
}
- private void ensureActivityConfiguration() {
- mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ private void ensureActivityConfiguration(ActivityRecord activity) {
+ activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
}
@Test
@@ -245,136 +248,145 @@
@Test
public void testsApplyOptionsLocked() {
+ final ActivityRecord activity = createActivityWithTask();
ActivityOptions activityOptions = ActivityOptions.makeBasic();
// Set and apply options for ActivityRecord. Pending options should be cleared
- mActivity.updateOptionsLocked(activityOptions);
- mActivity.applyOptionsLocked();
- assertNull(mActivity.pendingOptions);
+ activity.updateOptionsLocked(activityOptions);
+ activity.applyOptionsLocked();
+ assertNull(activity.pendingOptions);
// Set options for two ActivityRecords in same Task. Apply one ActivityRecord options.
// Pending options should be cleared for both ActivityRecords
- ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(mTask).build();
+ ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(activity.getTask()).build();
activity2.updateOptionsLocked(activityOptions);
- mActivity.updateOptionsLocked(activityOptions);
- mActivity.applyOptionsLocked();
- assertNull(mActivity.pendingOptions);
+ activity.updateOptionsLocked(activityOptions);
+ activity.applyOptionsLocked();
+ assertNull(activity.pendingOptions);
assertNull(activity2.pendingOptions);
// Set options for two ActivityRecords in separate Tasks. Apply one ActivityRecord options.
// Pending options should be cleared for only ActivityRecord that was applied
- Task task2 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(mStack).build();
- activity2 = new ActivityBuilder(mAtm).setTask(task2).build();
+ activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity2.updateOptionsLocked(activityOptions);
- mActivity.updateOptionsLocked(activityOptions);
- mActivity.applyOptionsLocked();
- assertNull(mActivity.pendingOptions);
+ activity.updateOptionsLocked(activityOptions);
+ activity.applyOptionsLocked();
+ assertNull(activity.pendingOptions);
assertNotNull(activity2.pendingOptions);
}
@Test
public void testNewOverrideConfigurationIncrementsSeq() {
+ final ActivityRecord activity = createActivityWithTask();
final Configuration newConfig = new Configuration();
- final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
- mActivity.onRequestedOverrideConfigurationChanged(newConfig);
- assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
+ final int prevSeq = activity.getMergedOverrideConfiguration().seq;
+ activity.onRequestedOverrideConfigurationChanged(newConfig);
+ assertEquals(prevSeq + 1, activity.getMergedOverrideConfiguration().seq);
}
@Test
public void testNewParentConfigurationIncrementsSeq() {
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
final Configuration newConfig = new Configuration(
- mTask.getRequestedOverrideConfiguration());
+ task.getRequestedOverrideConfiguration());
newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT
? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
- final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
- mTask.onRequestedOverrideConfigurationChanged(newConfig);
- assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
+ final int prevSeq = activity.getMergedOverrideConfiguration().seq;
+ task.onRequestedOverrideConfigurationChanged(newConfig);
+ assertEquals(prevSeq + 1, activity.getMergedOverrideConfiguration().seq);
}
@Test
public void testSetsRelaunchReason_NotDragResizing() {
- mActivity.setState(Task.ActivityState.RESUMED, "Testing");
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ activity.setState(Task.ActivityState.RESUMED, "Testing");
- mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
- mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- mActivity.getConfiguration()));
+ task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
+ activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+ activity.getConfiguration()));
- mActivity.info.configChanges &= ~CONFIG_ORIENTATION;
- final Configuration newConfig = new Configuration(mTask.getConfiguration());
+ activity.info.configChanges &= ~CONFIG_ORIENTATION;
+ final Configuration newConfig = new Configuration(task.getConfiguration());
newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT
? ORIENTATION_LANDSCAPE
: ORIENTATION_PORTRAIT;
- mTask.onRequestedOverrideConfigurationChanged(newConfig);
+ task.onRequestedOverrideConfigurationChanged(newConfig);
- mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
+ activity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
- ensureActivityConfiguration();
+ ensureActivityConfiguration(activity);
assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE,
- mActivity.mRelaunchReason);
+ activity.mRelaunchReason);
}
@Test
public void testSetsRelaunchReason_DragResizing() {
- mActivity.setState(Task.ActivityState.RESUMED, "Testing");
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ activity.setState(Task.ActivityState.RESUMED, "Testing");
- mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
- mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- mActivity.getConfiguration()));
+ task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
+ activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+ activity.getConfiguration()));
- mActivity.info.configChanges &= ~CONFIG_ORIENTATION;
- final Configuration newConfig = new Configuration(mTask.getConfiguration());
+ activity.info.configChanges &= ~CONFIG_ORIENTATION;
+ final Configuration newConfig = new Configuration(task.getConfiguration());
newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT
? ORIENTATION_LANDSCAPE
: ORIENTATION_PORTRAIT;
- mTask.onRequestedOverrideConfigurationChanged(newConfig);
+ task.onRequestedOverrideConfigurationChanged(newConfig);
- doReturn(true).when(mTask).isDragResizing();
+ doReturn(true).when(task).isDragResizing();
- mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
+ activity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
- ensureActivityConfiguration();
+ ensureActivityConfiguration(activity);
assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE,
- mActivity.mRelaunchReason);
+ activity.mRelaunchReason);
}
@Test
public void testSetsRelaunchReason_NonResizeConfigChanges() {
- mActivity.setState(Task.ActivityState.RESUMED, "Testing");
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ activity.setState(Task.ActivityState.RESUMED, "Testing");
- mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
- mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- mActivity.getConfiguration()));
+ task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
+ activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+ activity.getConfiguration()));
- mActivity.info.configChanges &= ~ActivityInfo.CONFIG_FONT_SCALE;
- final Configuration newConfig = new Configuration(mTask.getConfiguration());
+ activity.info.configChanges &= ~ActivityInfo.CONFIG_FONT_SCALE;
+ final Configuration newConfig = new Configuration(task.getConfiguration());
newConfig.fontScale = 5;
- mTask.onRequestedOverrideConfigurationChanged(newConfig);
+ task.onRequestedOverrideConfigurationChanged(newConfig);
- mActivity.mRelaunchReason =
+ activity.mRelaunchReason =
ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
- ensureActivityConfiguration();
+ ensureActivityConfiguration(activity);
assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_NONE,
- mActivity.mRelaunchReason);
+ activity.mRelaunchReason);
}
@Test
public void testSetRequestedOrientationUpdatesConfiguration() throws Exception {
- mActivity = new ActivityBuilder(mAtm)
- .setTask(mTask)
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
.setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
.build();
- mActivity.setState(Task.ActivityState.RESUMED, "Testing");
+ activity.setState(Task.ActivityState.RESUMED, "Testing");
- mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- mActivity.getConfiguration()));
+ activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+ activity.getConfiguration()));
- final Configuration newConfig = new Configuration(mActivity.getConfiguration());
+ final Configuration newConfig = new Configuration(activity.getConfiguration());
final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp);
if (newConfig.orientation == ORIENTATION_PORTRAIT) {
@@ -388,7 +400,7 @@
}
// Mimic the behavior that display doesn't handle app's requested orientation.
- final DisplayContent dc = mTask.getDisplayContent();
+ final DisplayContent dc = activity.getTask().getDisplayContent();
doReturn(false).when(dc).onDescendantOrientationChanged(any(), any());
doReturn(false).when(dc).handlesOrientationChangeFromDescendant();
@@ -404,24 +416,26 @@
throw new IllegalStateException("Orientation in new config should be either"
+ "landscape or portrait.");
}
- mActivity.setRequestedOrientation(requestedOrientation);
+ activity.setRequestedOrientation(requestedOrientation);
final ActivityConfigurationChangeItem expected =
ActivityConfigurationChangeItem.obtain(newConfig);
- verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(mActivity.app.getThread()),
- eq(mActivity.appToken), eq(expected));
+ verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()),
+ eq(activity.appToken), eq(expected));
}
@Test
public void ignoreRequestedOrientationInFreeformWindows() {
- mStack.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ task.setWindowingMode(WINDOWING_MODE_FREEFORM);
final Rect stableRect = new Rect();
- mStack.mDisplayContent.getStableRect(stableRect);
+ task.mDisplayContent.getStableRect(stableRect);
// Carve out non-decor insets from stableRect
final Rect insets = new Rect();
- final DisplayInfo displayInfo = mStack.mDisplayContent.getDisplayInfo();
- final DisplayPolicy policy = mStack.mDisplayContent.getDisplayPolicy();
+ final DisplayInfo displayInfo = task.mDisplayContent.getDisplayInfo();
+ final DisplayPolicy policy = task.mDisplayContent.getDisplayPolicy();
policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
displayInfo.logicalHeight, displayInfo.displayCutout, insets);
policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
@@ -440,27 +454,30 @@
bounds.left = stableRect.left + (stableRect.width() - newWidth) / 2;
bounds.right = bounds.left + newWidth;
}
- mTask.setBounds(bounds);
+ task.setBounds(bounds);
// Requests orientation that's different from its bounds.
- mActivity.setRequestedOrientation(
+ activity.setRequestedOrientation(
isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE);
// Asserts it has orientation derived from bounds.
assertEquals(isScreenPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT,
- mActivity.getConfiguration().orientation);
+ activity.getConfiguration().orientation);
}
@Test
public void ignoreRequestedOrientationInSplitWindows() {
- mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ final ActivityRecord activity = createActivityWith2LevelTask();
+ final Task task = activity.getTask();
+ final Task rootTask = activity.getRootTask();
+ rootTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final Rect stableRect = new Rect();
- mStack.mDisplayContent.getStableRect(stableRect);
+ rootTask.mDisplayContent.getStableRect(stableRect);
// Carve out non-decor insets from stableRect
final Rect insets = new Rect();
- final DisplayInfo displayInfo = mStack.mDisplayContent.getDisplayInfo();
- final DisplayPolicy policy = mStack.mDisplayContent.getDisplayPolicy();
+ final DisplayInfo displayInfo = rootTask.mDisplayContent.getDisplayInfo();
+ final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
displayInfo.logicalHeight, displayInfo.displayCutout, insets);
policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
@@ -479,85 +496,91 @@
bounds.left = stableRect.left + (stableRect.width() - newWidth) / 2;
bounds.right = bounds.left + newWidth;
}
- mTask.setBounds(bounds);
+ task.setBounds(bounds);
// Requests orientation that's different from its bounds.
- mActivity.setRequestedOrientation(
+ activity.setRequestedOrientation(
isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE);
// Asserts it has orientation derived from bounds.
assertEquals(isScreenPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT,
- mActivity.getConfiguration().orientation);
+ activity.getConfiguration().orientation);
}
@Test
public void testShouldMakeActive_deferredResume() {
- mActivity.setState(Task.ActivityState.STOPPED, "Testing");
+ final ActivityRecord activity = createActivityWithTask();
+ activity.setState(Task.ActivityState.STOPPED, "Testing");
mSupervisor.beginDeferResume();
- assertEquals(false, mActivity.shouldMakeActive(null /* activeActivity */));
+ assertEquals(false, activity.shouldMakeActive(null /* activeActivity */));
mSupervisor.endDeferResume();
- assertEquals(true, mActivity.shouldMakeActive(null /* activeActivity */));
+ assertEquals(true, activity.shouldMakeActive(null /* activeActivity */));
}
@Test
public void testShouldMakeActive_nonTopVisible() {
- ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(task).build();
finishingActivity.finishing = true;
- ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
- mActivity.setState(Task.ActivityState.STOPPED, "Testing");
+ ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
+ activity.setState(Task.ActivityState.STOPPED, "Testing");
- assertEquals(false, mActivity.shouldMakeActive(null /* activeActivity */));
+ assertEquals(false, activity.shouldMakeActive(null /* activeActivity */));
}
@Test
public void testShouldResume_stackVisibility() {
- mActivity.setState(Task.ActivityState.STOPPED, "Testing");
- spyOn(mStack);
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ activity.setState(Task.ActivityState.STOPPED, "Testing");
- doReturn(TASK_VISIBILITY_VISIBLE).when(mStack).getVisibility(null);
- assertEquals(true, mActivity.shouldResumeActivity(null /* activeActivity */));
+ doReturn(TASK_VISIBILITY_VISIBLE).when(task).getVisibility(null);
+ assertEquals(true, activity.shouldResumeActivity(null /* activeActivity */));
- doReturn(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT).when(mStack).getVisibility(null);
- assertEquals(false, mActivity.shouldResumeActivity(null /* activeActivity */));
+ doReturn(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT).when(task).getVisibility(null);
+ assertEquals(false, activity.shouldResumeActivity(null /* activeActivity */));
- doReturn(TASK_VISIBILITY_INVISIBLE).when(mStack).getVisibility(null);
- assertEquals(false, mActivity.shouldResumeActivity(null /* activeActivity */));
+ doReturn(TASK_VISIBILITY_INVISIBLE).when(task).getVisibility(null);
+ assertEquals(false, activity.shouldResumeActivity(null /* activeActivity */));
}
@Test
public void testShouldResumeOrPauseWithResults() {
- mActivity.setState(Task.ActivityState.STOPPED, "Testing");
- spyOn(mStack);
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ activity.setState(Task.ActivityState.STOPPED, "Testing");
- ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
- mActivity.addResultLocked(topActivity, "resultWho", 0, 0, new Intent());
+ ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
+ activity.addResultLocked(topActivity, "resultWho", 0, 0, new Intent());
topActivity.finishing = true;
- doReturn(TASK_VISIBILITY_VISIBLE).when(mStack).getVisibility(null);
- assertEquals(true, mActivity.shouldResumeActivity(null /* activeActivity */));
- assertEquals(false, mActivity.shouldPauseActivity(null /*activeActivity */));
+ doReturn(TASK_VISIBILITY_VISIBLE).when(task).getVisibility(null);
+ assertEquals(true, activity.shouldResumeActivity(null /* activeActivity */));
+ assertEquals(false, activity.shouldPauseActivity(null /*activeActivity */));
}
@Test
public void testPushConfigurationWhenLaunchTaskBehind() throws Exception {
- mActivity = new ActivityBuilder(mAtm)
- .setTask(mTask)
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
.setLaunchTaskBehind(true)
.setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
.build();
- mActivity.setState(Task.ActivityState.STOPPED, "Testing");
+ final Task task = activity.getTask();
+ activity.setState(Task.ActivityState.STOPPED, "Testing");
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
try {
doReturn(false).when(stack).isTranslucent(any());
- assertTrue(mStack.shouldBeVisible(null /* starting */));
+ assertTrue(task.shouldBeVisible(null /* starting */));
- mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- mActivity.getConfiguration()));
+ activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+ activity.getConfiguration()));
- final Configuration newConfig = new Configuration(mActivity.getConfiguration());
+ final Configuration newConfig = new Configuration(activity.getConfiguration());
final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp);
if (newConfig.orientation == ORIENTATION_PORTRAIT) {
@@ -570,15 +593,15 @@
newConfig.screenHeightDp = longSide;
}
- mTask.onConfigurationChanged(newConfig);
+ task.onConfigurationChanged(newConfig);
- mActivity.ensureActivityConfiguration(0 /* globalChanges */,
+ activity.ensureActivityConfiguration(0 /* globalChanges */,
false /* preserveWindow */, true /* ignoreStopState */);
final ActivityConfigurationChangeItem expected =
ActivityConfigurationChangeItem.obtain(newConfig);
verify(mAtm.getLifecycleManager()).scheduleTransaction(
- eq(mActivity.app.getThread()), eq(mActivity.appToken), eq(expected));
+ eq(activity.app.getThread()), eq(activity.appToken), eq(expected));
} finally {
stack.getDisplayArea().removeChild(stack);
}
@@ -586,16 +609,18 @@
@Test
public void testShouldStartWhenMakeClientActive() {
- ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord activity = createActivityWithTask();
+ ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(activity.getTask()).build();
topActivity.setOccludesParent(false);
- mActivity.setState(Task.ActivityState.STOPPED, "Testing");
- mActivity.setVisibility(true);
- mActivity.makeActiveIfNeeded(null /* activeActivity */);
- assertEquals(STARTED, mActivity.getState());
+ activity.setState(Task.ActivityState.STOPPED, "Testing");
+ activity.setVisibility(true);
+ activity.makeActiveIfNeeded(null /* activeActivity */);
+ assertEquals(STARTED, activity.getState());
}
@Test
public void testTakeOptions() {
+ final ActivityRecord activity = createActivityWithTask();
ActivityOptions opts = ActivityOptions.makeRemoteAnimation(
new RemoteAnimationAdapter(new Stub() {
@@ -611,13 +636,13 @@
}
}, 0, 0));
- mActivity.updateOptionsLocked(opts);
- assertNotNull(mActivity.takeOptionsLocked(true /* fromClient */));
- assertNotNull(mActivity.pendingOptions);
+ activity.updateOptionsLocked(opts);
+ assertNotNull(activity.takeOptionsLocked(true /* fromClient */));
+ assertNotNull(activity.pendingOptions);
- mActivity.updateOptionsLocked(ActivityOptions.makeBasic());
- assertNotNull(mActivity.takeOptionsLocked(false /* fromClient */));
- assertNull(mActivity.pendingOptions);
+ activity.updateOptionsLocked(ActivityOptions.makeBasic());
+ assertNotNull(activity.takeOptionsLocked(false /* fromClient */));
+ assertNull(activity.pendingOptions);
}
@Test
@@ -626,7 +651,7 @@
Resources.getSystem().getString(R.string.config_chooserActivity));
ActivityRecord chooserActivity = new ActivityBuilder(mAtm).setComponent(
chooserComponent).build();
- assertThat(mActivity.canLaunchHomeActivity(NOBODY_UID, chooserActivity)).isTrue();
+ assertThat(chooserActivity.canLaunchHomeActivity(NOBODY_UID, chooserActivity)).isTrue();
}
/**
@@ -635,49 +660,52 @@
*/
@Test
public void testHasSavedState() {
- assertTrue(mActivity.hasSavedState());
+ final ActivityRecord activity = createActivityWithTask();
+ assertTrue(activity.hasSavedState());
- ActivityRecord.activityResumedLocked(mActivity.appToken);
- assertFalse(mActivity.hasSavedState());
- assertNull(mActivity.getSavedState());
+ ActivityRecord.activityResumedLocked(activity.appToken);
+ assertFalse(activity.hasSavedState());
+ assertNull(activity.getSavedState());
}
/** Verify the behavior of {@link ActivityRecord#setSavedState(Bundle)}. */
@Test
public void testUpdateSavedState() {
- mActivity.setSavedState(null /* savedState */);
- assertFalse(mActivity.hasSavedState());
- assertNull(mActivity.getSavedState());
+ final ActivityRecord activity = createActivityWithTask();
+ activity.setSavedState(null /* savedState */);
+ assertFalse(activity.hasSavedState());
+ assertNull(activity.getSavedState());
final Bundle savedState = new Bundle();
savedState.putString("test", "string");
- mActivity.setSavedState(savedState);
- assertTrue(mActivity.hasSavedState());
- assertEquals(savedState, mActivity.getSavedState());
+ activity.setSavedState(savedState);
+ assertTrue(activity.hasSavedState());
+ assertEquals(savedState, activity.getSavedState());
}
/** Verify the correct updates of saved state when activity client reports stop. */
@Test
public void testUpdateSavedState_activityStopped() {
+ final ActivityRecord activity = createActivityWithTask();
final Bundle savedState = new Bundle();
savedState.putString("test", "string");
final PersistableBundle persistentSavedState = new PersistableBundle();
persistentSavedState.putString("persist", "string");
// Set state to STOPPING, or ActivityRecord#activityStoppedLocked() call will be ignored.
- mActivity.setState(STOPPING, "test");
- mActivity.activityStopped(savedState, persistentSavedState, "desc");
- assertTrue(mActivity.hasSavedState());
- assertEquals(savedState, mActivity.getSavedState());
- assertEquals(persistentSavedState, mActivity.getPersistentSavedState());
+ activity.setState(STOPPING, "test");
+ activity.activityStopped(savedState, persistentSavedState, "desc");
+ assertTrue(activity.hasSavedState());
+ assertEquals(savedState, activity.getSavedState());
+ assertEquals(persistentSavedState, activity.getPersistentSavedState());
// Sending 'null' for saved state can only happen due to timeout, so previously stored saved
// states should not be overridden.
- mActivity.setState(STOPPING, "test");
- mActivity.activityStopped(null /* savedState */, null /* persistentSavedState */, "desc");
- assertTrue(mActivity.hasSavedState());
- assertEquals(savedState, mActivity.getSavedState());
- assertEquals(persistentSavedState, mActivity.getPersistentSavedState());
+ activity.setState(STOPPING, "test");
+ activity.activityStopped(null /* savedState */, null /* persistentSavedState */, "desc");
+ assertTrue(activity.hasSavedState());
+ assertEquals(savedState, activity.getSavedState());
+ assertEquals(persistentSavedState, activity.getPersistentSavedState());
}
/**
@@ -686,19 +714,20 @@
*/
@Test
public void testFinishActivityIfPossible_cancelled() {
+ final ActivityRecord activity = createActivityWithTask();
// Mark activity as finishing
- mActivity.finishing = true;
+ activity.finishing = true;
assertEquals("Duplicate finish request must be ignored", FINISH_RESULT_CANCELLED,
- mActivity.finishIfPossible("test", false /* oomAdj */));
- assertTrue(mActivity.finishing);
- assertTrue(mActivity.isInStackLocked());
+ activity.finishIfPossible("test", false /* oomAdj */));
+ assertTrue(activity.finishing);
+ assertTrue(activity.isInStackLocked());
// Remove activity from task
- mActivity.finishing = false;
- mActivity.onParentChanged(null /*newParent*/, mActivity.getTask());
+ activity.finishing = false;
+ activity.onParentChanged(null /*newParent*/, activity.getTask());
assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_CANCELLED,
- mActivity.finishIfPossible("test", false /* oomAdj */));
- assertFalse(mActivity.finishing);
+ activity.finishIfPossible("test", false /* oomAdj */));
+ assertFalse(activity.finishing);
}
/**
@@ -707,20 +736,21 @@
*/
@Test
public void testFinishActivityIfPossible_requested() {
- mActivity.finishing = false;
+ final ActivityRecord activity = createActivityWithTask();
+ activity.finishing = false;
assertEquals("Currently resumed activity must be prepared removal", FINISH_RESULT_REQUESTED,
- mActivity.finishIfPossible("test", false /* oomAdj */));
- assertTrue(mActivity.finishing);
- assertTrue(mActivity.isInStackLocked());
+ activity.finishIfPossible("test", false /* oomAdj */));
+ assertTrue(activity.finishing);
+ assertTrue(activity.isInStackLocked());
// First request to finish activity must schedule a "destroy" request to the client.
// Activity must be removed from history after the client reports back or after timeout.
- mActivity.finishing = false;
- mActivity.setState(STOPPED, "test");
+ activity.finishing = false;
+ activity.setState(STOPPED, "test");
assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_REQUESTED,
- mActivity.finishIfPossible("test", false /* oomAdj */));
- assertTrue(mActivity.finishing);
- assertTrue(mActivity.isInStackLocked());
+ activity.finishIfPossible("test", false /* oomAdj */));
+ assertTrue(activity.finishing);
+ assertTrue(activity.isInStackLocked());
}
/**
@@ -728,26 +758,28 @@
*/
@Test
public void testFinishActivityIfPossible_removed() {
+ final ActivityRecord activity = createActivityWithTask();
// Prepare the activity record to be ready for immediate removal. It should be invisible and
// have no process. Otherwise, request to finish it will send a message to client first.
- mActivity.setState(STOPPED, "test");
- mActivity.mVisibleRequested = false;
- mActivity.nowVisible = false;
+ activity.setState(STOPPED, "test");
+ activity.mVisibleRequested = false;
+ activity.nowVisible = false;
// Set process to 'null' to allow immediate removal, but don't call mActivity.setProcess() -
// this will cause NPE when updating task's process.
- mActivity.app = null;
+ activity.app = null;
// Put a visible activity on top, so the finishing activity doesn't have to wait until the
// next activity reports idle to destroy it.
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(activity.getTask()).build();
topActivity.mVisibleRequested = true;
topActivity.nowVisible = true;
topActivity.setState(RESUMED, "test");
assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_REMOVED,
- mActivity.finishIfPossible("test", false /* oomAdj */));
- assertTrue(mActivity.finishing);
- assertFalse(mActivity.isInStackLocked());
+ activity.finishIfPossible("test", false /* oomAdj */));
+ assertTrue(activity.finishing);
+ assertFalse(activity.isInStackLocked());
}
/**
@@ -756,24 +788,26 @@
*/
@Test
public void testFinishActivityIfPossible_adjustStackOrder() {
- // Prepare the stacks with order (top to bottom): mStack, stack1, stack2.
- final Task stack1 = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
- mStack.moveToFront("test");
- // The stack2 is needed here for moving back to simulate the
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ // Prepare the tasks with order (top to bottom): task, task1, task2.
+ final Task task1 = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ task.moveToFront("test");
+ // The task2 is needed here for moving back to simulate the
// {@link DisplayContent#mPreferredTopFocusableStack} is cleared, so
// {@link DisplayContent#getFocusedStack} will rely on the order of focusable-and-visible
- // stacks. Then when mActivity is finishing, its stack will be invisible (no running
- // activities in the stack) that is the key condition to verify.
- final Task stack2 = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
- stack2.moveToBack("test", stack2.getBottomMostTask());
+ // tasks. Then when mActivity is finishing, its task will be invisible (no running
+ // activities in the task) that is the key condition to verify.
+ final Task task2 = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ task2.moveToBack("test", task2.getBottomMostTask());
- assertTrue(mStack.isTopStackInDisplayArea());
+ assertTrue(task.isTopStackInDisplayArea());
- mActivity.setState(RESUMED, "test");
- mActivity.finishIfPossible(0 /* resultCode */, null /* resultData */,
+ activity.setState(RESUMED, "test");
+ activity.finishIfPossible(0 /* resultCode */, null /* resultData */,
null /* resultGrants */, "test", false /* oomAdj */);
- assertTrue(stack1.isTopStackInDisplayArea());
+ assertTrue(task1.isTopStackInDisplayArea());
}
/**
@@ -783,22 +817,25 @@
@Test
public void testFinishActivityIfPossible_adjustStackOrderOrganizedRoot() {
// Make mStack be a the root task that created by task organizer
- mStack.mCreatedByOrganizer = true;
+ final Task rootableTask = new TaskBuilder(mSupervisor)
+ .setCreateParentTask(true).setCreateActivity(true).build();
+ final Task rootTask = rootableTask.getRootTask();
+ rootTask.mCreatedByOrganizer = true;
- // Have two tasks (topRootableTask and mTask) as the children of mStack.
- ActivityRecord topActivity = new ActivityBuilder(mActivity.mAtmService)
+ // Have two tasks (topRootableTask and rootableTask) as the children of rootTask.
+ ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setCreateTask(true)
- .setParentTask(mStack)
+ .setParentTask(rootTask)
.build();
Task topRootableTask = topActivity.getTask();
topRootableTask.moveToFront("test");
- assertTrue(mStack.isTopStackInDisplayArea());
+ assertTrue(rootTask.isTopStackInDisplayArea());
// Finish top activity and verify the next focusable rootable task has adjusted to top.
topActivity.setState(RESUMED, "test");
topActivity.finishIfPossible(0 /* resultCode */, null /* resultData */,
null /* resultGrants */, "test", false /* oomAdj */);
- assertEquals(mTask, mStack.getTopMostTask());
+ assertEquals(rootableTask, rootTask.getTopMostTask());
}
/**
@@ -808,6 +845,8 @@
*/
@Test
public void testFinishActivityIfPossible_PreferredTopStackChanged() {
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
final ActivityRecord topActivityOnNonTopDisplay =
createActivityOnDisplay(true /* defaultDisplay */, null /* process */);
Task topRootableTask = topActivityOnNonTopDisplay.getRootTask();
@@ -830,8 +869,8 @@
topActivityOnNonTopDisplay.setState(RESUMED, "test");
topActivityOnNonTopDisplay.finishIfPossible(0 /* resultCode */, null /* resultData */,
null /* resultGrants */, "test", false /* oomAdj */);
- assertEquals(mTask, mStack.getTopMostTask());
- assertEquals(mStack, mActivity.getDisplayArea().mPreferredTopFocusableStack);
+ assertEquals(task, task.getTopMostTask());
+ assertEquals(task, activity.getDisplayArea().mPreferredTopFocusableStack);
}
/**
@@ -839,13 +878,14 @@
*/
@Test
public void testFinishActivityIfPossible_resumedStartsPausing() {
- mActivity.finishing = false;
- mActivity.setState(RESUMED, "test");
+ final ActivityRecord activity = createActivityWithTask();
+ activity.finishing = false;
+ activity.setState(RESUMED, "test");
assertEquals("Currently resumed activity must be paused before removal",
- FINISH_RESULT_REQUESTED, mActivity.finishIfPossible("test", false /* oomAdj */));
- assertEquals(PAUSING, mActivity.getState());
- verify(mActivity).setVisibility(eq(false));
- verify(mActivity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
+ FINISH_RESULT_REQUESTED, activity.finishIfPossible("test", false /* oomAdj */));
+ assertEquals(PAUSING, activity.getState());
+ verify(activity).setVisibility(eq(false));
+ verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
}
/**
@@ -853,14 +893,15 @@
*/
@Test
public void testFinishActivityIfPossible_nonResumedFinishCompletesImmediately() {
+ final ActivityRecord activity = createActivityWithTask();
final ActivityState[] states = {INITIALIZING, STARTED, PAUSED, STOPPING, STOPPED};
for (ActivityState state : states) {
- mActivity.finishing = false;
- mActivity.setState(state, "test");
- reset(mActivity);
+ activity.finishing = false;
+ activity.setState(state, "test");
+ reset(activity);
assertEquals("Finish must be requested", FINISH_RESULT_REQUESTED,
- mActivity.finishIfPossible("test", false /* oomAdj */));
- verify(mActivity).completeFinishing(anyString());
+ activity.finishIfPossible("test", false /* oomAdj */));
+ verify(activity).completeFinishing(anyString());
}
}
@@ -869,11 +910,12 @@
*/
@Test
public void testFinishActivityIfPossible_pausing() {
- mActivity.finishing = false;
- mActivity.setState(PAUSING, "test");
+ final ActivityRecord activity = createActivityWithTask();
+ activity.finishing = false;
+ activity.setState(PAUSING, "test");
assertEquals("Finish must be requested", FINISH_RESULT_REQUESTED,
- mActivity.finishIfPossible("test", false /* oomAdj */));
- verify(mActivity, never()).completeFinishing(anyString());
+ activity.finishIfPossible("test", false /* oomAdj */));
+ verify(activity, never()).completeFinishing(anyString());
}
/**
@@ -882,14 +924,16 @@
*/
@Test
public void testFinishActivityIfPossible_visibleResumedPreparesAppTransition() {
- mActivity.finishing = false;
- mActivity.mVisibleRequested = true;
- mActivity.setState(RESUMED, "test");
- mActivity.finishIfPossible("test", false /* oomAdj */);
+ final ActivityRecord activity = createActivityWithTask();
+ clearInvocations(activity.mDisplayContent);
+ activity.finishing = false;
+ activity.mVisibleRequested = true;
+ activity.setState(RESUMED, "test");
+ activity.finishIfPossible("test", false /* oomAdj */);
- verify(mActivity).setVisibility(eq(false));
- verify(mActivity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
- verify(mActivity.mDisplayContent, never()).executeAppTransition();
+ verify(activity).setVisibility(eq(false));
+ verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
+ verify(activity.mDisplayContent, never()).executeAppTransition();
}
/**
@@ -897,14 +941,16 @@
*/
@Test
public void testFinishActivityIfPossible_visibleNotResumedExecutesAppTransition() {
- mActivity.finishing = false;
- mActivity.mVisibleRequested = true;
- mActivity.setState(PAUSED, "test");
- mActivity.finishIfPossible("test", false /* oomAdj */);
+ final ActivityRecord activity = createActivityWithTask();
+ clearInvocations(activity.mDisplayContent);
+ activity.finishing = false;
+ activity.mVisibleRequested = true;
+ activity.setState(PAUSED, "test");
+ activity.finishIfPossible("test", false /* oomAdj */);
- verify(mActivity, atLeast(1)).setVisibility(eq(false));
- verify(mActivity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
- verify(mActivity.mDisplayContent).executeAppTransition();
+ verify(activity, atLeast(1)).setVisibility(eq(false));
+ verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
+ verify(activity.mDisplayContent).executeAppTransition();
}
/**
@@ -912,15 +958,16 @@
*/
@Test
public void testFinishActivityIfPossible_nonVisibleNoAppTransition() {
+ final ActivityRecord activity = createActivityWithTask();
// Put an activity on top of test activity to make it invisible and prevent us from
// accidentally resuming the topmost one again.
new ActivityBuilder(mAtm).build();
- mActivity.mVisibleRequested = false;
- mActivity.setState(STOPPED, "test");
+ activity.mVisibleRequested = false;
+ activity.setState(STOPPED, "test");
- mActivity.finishIfPossible("test", false /* oomAdj */);
+ activity.finishIfPossible("test", false /* oomAdj */);
- verify(mActivity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
+ verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
}
/**
@@ -928,8 +975,9 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testCompleteFinishing_failNotFinishing() {
- mActivity.finishing = false;
- mActivity.completeFinishing("test");
+ final ActivityRecord activity = createActivityWithTask();
+ activity.finishing = false;
+ activity.completeFinishing("test");
}
/**
@@ -937,8 +985,9 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testCompleteFinishing_failResumed() {
- mActivity.setState(RESUMED, "test");
- mActivity.completeFinishing("test");
+ final ActivityRecord activity = createActivityWithTask();
+ activity.setState(RESUMED, "test");
+ activity.completeFinishing("test");
}
/**
@@ -947,13 +996,14 @@
*/
@Test
public void testCompleteFinishing_pausing() {
- mActivity.setState(PAUSING, "test");
- mActivity.finishing = true;
+ final ActivityRecord activity = createActivityWithTask();
+ activity.setState(PAUSING, "test");
+ activity.finishing = true;
assertEquals("Activity must not be removed immediately - waiting for paused",
- mActivity, mActivity.completeFinishing("test"));
- assertEquals(PAUSING, mActivity.getState());
- verify(mActivity, never()).destroyIfPossible(anyString());
+ activity, activity.completeFinishing("test"));
+ assertEquals(PAUSING, activity.getState());
+ verify(activity, never()).destroyIfPossible(anyString());
}
/**
@@ -965,7 +1015,9 @@
*/
@Test
public void testCompleteFinishing_keepStateOfNextInvisible() {
- final ActivityRecord currentTop = mActivity;
+ final ActivityRecord currentTop = createActivityWithTask();
+ final Task task = currentTop.getTask();
+
currentTop.mVisibleRequested = currentTop.nowVisible = true;
// Simulates that {@code currentTop} starts an existing activity from background (so its
@@ -974,7 +1026,7 @@
final ActivityRecord nextTop = nextStack.getTopNonFinishingActivity();
nextTop.setState(STOPPED, "test");
- mStack.mPausingActivity = currentTop;
+ task.mPausingActivity = currentTop;
currentTop.finishing = true;
currentTop.setState(PAUSED, "test");
currentTop.completeFinishing("completePauseLocked");
@@ -991,16 +1043,18 @@
*/
@Test
public void testCompleteFinishing_waitForNextVisible() {
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord activity = createActivityWithTask();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(activity.getTask()).build();
topActivity.mVisibleRequested = true;
topActivity.nowVisible = true;
topActivity.finishing = true;
topActivity.setState(PAUSED, "true");
// Mark the bottom activity as not visible, so that we will wait for it before removing
// the top one.
- mActivity.mVisibleRequested = false;
- mActivity.nowVisible = false;
- mActivity.setState(STOPPED, "test");
+ activity.mVisibleRequested = false;
+ activity.nowVisible = false;
+ activity.setState(STOPPED, "test");
assertEquals("Activity must not be removed immediately - waiting for next visible",
topActivity, topActivity.completeFinishing("test"));
@@ -1017,23 +1071,24 @@
*/
@Test
public void testCompleteFinishing_noWaitForNextVisible_sleeping() {
+ final ActivityRecord activity = createActivityWithTask();
// Create a top activity on a new task
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord topActivity = createActivityWithTask();
mDisplayContent.setIsSleeping(true);
- doReturn(true).when(mActivity).shouldBeVisible();
+ doReturn(true).when(activity).shouldBeVisible();
topActivity.mVisibleRequested = false;
topActivity.nowVisible = false;
topActivity.finishing = true;
topActivity.setState(STOPPED, "true");
// Mark the activity behind (on a separate task) as not visible
- mActivity.mVisibleRequested = false;
- mActivity.nowVisible = false;
- mActivity.setState(STOPPED, "test");
+ activity.mVisibleRequested = false;
+ activity.nowVisible = false;
+ activity.setState(STOPPED, "test");
- clearInvocations(mActivity);
+ clearInvocations(activity);
topActivity.completeFinishing("test");
- verify(mActivity).setState(eq(RESUMED), any());
+ verify(activity).setState(eq(RESUMED), any());
verify(topActivity).destroyIfPossible(anyString());
}
@@ -1042,16 +1097,18 @@
*/
@Test
public void testCompleteFinishing_noWaitForNextVisible_alreadyInvisible() {
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord activity = createActivityWithTask();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(activity.getTask()).build();
topActivity.mVisibleRequested = false;
topActivity.nowVisible = false;
topActivity.finishing = true;
topActivity.setState(STOPPED, "true");
// Mark the bottom activity as not visible, so that we would wait for it before removing
// the top one.
- mActivity.mVisibleRequested = false;
- mActivity.nowVisible = false;
- mActivity.setState(STOPPED, "test");
+ activity.mVisibleRequested = false;
+ activity.nowVisible = false;
+ activity.setState(STOPPED, "test");
topActivity.completeFinishing("test");
@@ -1064,15 +1121,17 @@
*/
@Test
public void testCompleteFinishing_waitForIdle() {
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord activity = createActivityWithTask();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(activity.getTask()).build();
topActivity.mVisibleRequested = true;
topActivity.nowVisible = true;
topActivity.finishing = true;
topActivity.setState(PAUSED, "true");
// Mark the bottom activity as already visible, so that there is no need to wait for it.
- mActivity.mVisibleRequested = true;
- mActivity.nowVisible = true;
- mActivity.setState(RESUMED, "test");
+ activity.mVisibleRequested = true;
+ activity.nowVisible = true;
+ activity.setState(RESUMED, "test");
topActivity.completeFinishing("test");
@@ -1085,15 +1144,17 @@
*/
@Test
public void testCompleteFinishing_noWaitForNextVisible_stopped() {
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord activity = createActivityWithTask();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(activity.getTask()).build();
topActivity.mVisibleRequested = false;
topActivity.nowVisible = false;
topActivity.finishing = true;
topActivity.setState(STOPPED, "true");
// Mark the bottom activity as already visible, so that there is no need to wait for it.
- mActivity.mVisibleRequested = true;
- mActivity.nowVisible = true;
- mActivity.setState(RESUMED, "test");
+ activity.mVisibleRequested = true;
+ activity.nowVisible = true;
+ activity.setState(RESUMED, "test");
topActivity.completeFinishing("test");
@@ -1106,15 +1167,17 @@
*/
@Test
public void testCompleteFinishing_noWaitForNextVisible_nonFocusedStack() {
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord activity = createActivityWithTask();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(activity.getTask()).build();
topActivity.mVisibleRequested = true;
topActivity.nowVisible = true;
topActivity.finishing = true;
topActivity.setState(PAUSED, "true");
// Mark the bottom activity as already visible, so that there is no need to wait for it.
- mActivity.mVisibleRequested = true;
- mActivity.nowVisible = true;
- mActivity.setState(RESUMED, "test");
+ activity.mVisibleRequested = true;
+ activity.nowVisible = true;
+ activity.setState(RESUMED, "test");
// Add another stack to become focused and make the activity there visible. This way it
// simulates finishing in non-focused stack in split-screen.
@@ -1136,10 +1199,12 @@
*/
@Test
public void testCompleteFinishing_showWhenLocked() {
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
// Make keyguard locked and set the top activity show-when-locked.
- KeyguardController keyguardController = mActivity.mTaskSupervisor.getKeyguardController();
+ KeyguardController keyguardController = activity.mTaskSupervisor.getKeyguardController();
doReturn(true).when(keyguardController).isKeyguardLocked();
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
topActivity.mVisibleRequested = true;
topActivity.nowVisible = true;
topActivity.setState(RESUMED, "true");
@@ -1150,7 +1215,7 @@
topActivity.setShowWhenLocked(true);
// Verify the stack-top activity is occluded keyguard.
- assertEquals(topActivity, mStack.topRunningActivity());
+ assertEquals(topActivity, task.topRunningActivity());
assertTrue(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
// Finish the top activity
@@ -1159,7 +1224,7 @@
topActivity.completeFinishing("test");
// Verify new top activity does not occlude keyguard.
- assertEquals(mActivity, mStack.topRunningActivity());
+ assertEquals(activity, task.topRunningActivity());
assertFalse(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
}
@@ -1169,18 +1234,19 @@
*/
@Test
public void testCompleteFinishing_ensureActivitiesVisible() {
- final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
firstActivity.mVisibleRequested = false;
firstActivity.nowVisible = false;
firstActivity.setState(STOPPED, "true");
- final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
secondActivity.mVisibleRequested = true;
secondActivity.nowVisible = true;
secondActivity.setState(PAUSED, "true");
- final ActivityRecord translucentActivity =
- new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm).setTask(task).build();
translucentActivity.mVisibleRequested = true;
translucentActivity.nowVisible = true;
translucentActivity.setState(RESUMED, "true");
@@ -1208,13 +1274,13 @@
*/
@Test
public void testDestroyIfPossible() {
+ final ActivityRecord activity = createActivityWithTask();
doReturn(false).when(mRootWindowContainer).resumeFocusedStacksTopActivities();
- spyOn(mStack);
- mActivity.destroyIfPossible("test");
+ activity.destroyIfPossible("test");
- assertEquals(DESTROYING, mActivity.getState());
- assertTrue(mActivity.finishing);
- verify(mActivity).destroyImmediately(anyString());
+ assertEquals(DESTROYING, activity.getState());
+ assertTrue(activity.finishing);
+ verify(activity).destroyImmediately(anyString());
}
/**
@@ -1224,23 +1290,23 @@
*/
@Test
public void testDestroyIfPossible_lastActivityAboveEmptyHomeStack() {
+ final ActivityRecord activity = createActivityWithTask();
// Empty the home stack.
- final Task homeStack = mActivity.getDisplayArea().getRootHomeTask();
+ final Task homeStack = activity.getDisplayArea().getRootHomeTask();
homeStack.forAllLeafTasks((t) -> {
homeStack.removeChild(t, "test");
}, true /* traverseTopToBottom */);
- mActivity.finishing = true;
+ activity.finishing = true;
doReturn(false).when(mRootWindowContainer).resumeFocusedStacksTopActivities();
- spyOn(mStack);
// Try to destroy the last activity above the home stack.
- mActivity.destroyIfPossible("test");
+ activity.destroyIfPossible("test");
// Verify that the activity was not actually destroyed, but waits for next one to come up
// instead.
- verify(mActivity, never()).destroyImmediately(anyString());
- assertEquals(FINISHING, mActivity.getState());
- assertTrue(mActivity.mTaskSupervisor.mFinishingActivities.contains(mActivity));
+ verify(activity, never()).destroyImmediately(anyString());
+ assertEquals(FINISHING, activity.getState());
+ assertTrue(activity.mTaskSupervisor.mFinishingActivities.contains(activity));
}
/**
@@ -1250,22 +1316,23 @@
*/
@Test
public void testCompleteFinishing_lastActivityAboveEmptyHomeStack() {
+ final ActivityRecord activity = createActivityWithTask();
// Empty the home root task.
- final Task homeRootTask = mActivity.getDisplayArea().getRootHomeTask();
+ final Task homeRootTask = activity.getDisplayArea().getRootHomeTask();
homeRootTask.forAllLeafTasks((t) -> {
homeRootTask.removeChild(t, "test");
}, true /* traverseTopToBottom */);
- mActivity.finishing = true;
- mActivity.mVisibleRequested = true;
- spyOn(mStack);
+ activity.setState(STARTED, "test");
+ activity.finishing = true;
+ activity.mVisibleRequested = true;
// Try to finish the last activity above the home stack.
- mActivity.completeFinishing("test");
+ activity.completeFinishing("test");
// Verify that the activity is not destroyed immediately, but waits for next one to come up.
- verify(mActivity, never()).destroyImmediately(anyString());
- assertEquals(FINISHING, mActivity.getState());
- assertTrue(mActivity.mTaskSupervisor.mFinishingActivities.contains(mActivity));
+ verify(activity, never()).destroyImmediately(anyString());
+ assertEquals(FINISHING, activity.getState());
+ assertTrue(activity.mTaskSupervisor.mFinishingActivities.contains(activity));
}
/**
@@ -1274,10 +1341,11 @@
*/
@Test
public void testDestroyImmediately_hadApp_finishing() {
- mActivity.finishing = true;
- mActivity.destroyImmediately("test");
+ final ActivityRecord activity = createActivityWithTask();
+ activity.finishing = true;
+ activity.destroyImmediately("test");
- assertEquals(DESTROYING, mActivity.getState());
+ assertEquals(DESTROYING, activity.getState());
}
/**
@@ -1286,10 +1354,11 @@
*/
@Test
public void testDestroyImmediately_hadApp_notFinishing() {
- mActivity.finishing = false;
- mActivity.destroyImmediately("test");
+ final ActivityRecord activity = createActivityWithTask();
+ activity.finishing = false;
+ activity.destroyImmediately("test");
- assertEquals(DESTROYED, mActivity.getState());
+ assertEquals(DESTROYED, activity.getState());
}
/**
@@ -1298,14 +1367,15 @@
*/
@Test
public void testDestroyImmediately_noApp_finishing() {
- mActivity.app = null;
- mActivity.finishing = true;
- final Task task = mActivity.getTask();
+ final ActivityRecord activity = createActivityWithTask();
+ activity.app = null;
+ activity.finishing = true;
+ final Task task = activity.getTask();
- mActivity.destroyImmediately("test");
+ activity.destroyImmediately("test");
- assertEquals(DESTROYED, mActivity.getState());
- assertNull(mActivity.getTask());
+ assertEquals(DESTROYED, activity.getState());
+ assertNull(activity.getTask());
assertEquals(0, task.getChildCount());
}
@@ -1315,14 +1385,15 @@
*/
@Test
public void testDestroyImmediately_noApp_notFinishing() {
- mActivity.app = null;
- mActivity.finishing = false;
- final Task task = mActivity.getTask();
+ final ActivityRecord activity = createActivityWithTask();
+ activity.app = null;
+ activity.finishing = false;
+ final Task task = activity.getTask();
- mActivity.destroyImmediately("test");
+ activity.destroyImmediately("test");
- assertEquals(DESTROYED, mActivity.getState());
- assertEquals(task, mActivity.getTask());
+ assertEquals(DESTROYED, activity.getState());
+ assertEquals(task, activity.getTask());
assertEquals(1, task.getChildCount());
}
@@ -1331,11 +1402,12 @@
*/
@Test
public void testSafelyDestroy_nonDestroyable() {
- doReturn(false).when(mActivity).isDestroyable();
+ final ActivityRecord activity = createActivityWithTask();
+ doReturn(false).when(activity).isDestroyable();
- mActivity.safelyDestroy("test");
+ activity.safelyDestroy("test");
- verify(mActivity, never()).destroyImmediately(anyString());
+ verify(activity, never()).destroyImmediately(anyString());
}
/**
@@ -1343,29 +1415,31 @@
*/
@Test
public void testSafelyDestroy_destroyable() {
- doReturn(true).when(mActivity).isDestroyable();
+ final ActivityRecord activity = createActivityWithTask();
+ doReturn(true).when(activity).isDestroyable();
- mActivity.safelyDestroy("test");
+ activity.safelyDestroy("test");
- verify(mActivity).destroyImmediately(anyString());
+ verify(activity).destroyImmediately(anyString());
}
@Test
public void testRemoveFromHistory() {
- final Task stack = mActivity.getRootTask();
- final Task task = mActivity.getTask();
- final WindowProcessController wpc = mActivity.app;
+ final ActivityRecord activity = createActivityWithTask();
+ final Task rootTask = activity.getRootTask();
+ final Task task = activity.getTask();
+ final WindowProcessController wpc = activity.app;
assertTrue(wpc.hasActivities());
- mActivity.removeFromHistory("test");
+ activity.removeFromHistory("test");
- assertEquals(DESTROYED, mActivity.getState());
- assertNull(mActivity.app);
- assertNull(mActivity.getTask());
+ assertEquals(DESTROYED, activity.getState());
+ assertNull(activity.app);
+ assertNull(activity.getTask());
assertFalse(wpc.hasActivities());
assertEquals(0, task.getChildCount());
assertEquals(task.getRootTask(), task);
- assertEquals(0, stack.getChildCount());
+ assertEquals(0, rootTask.getChildCount());
}
/**
@@ -1374,8 +1448,9 @@
*/
@Test(expected = IllegalStateException.class)
public void testDestroyed_notDestroying() {
- mActivity.setState(STOPPED, "test");
- mActivity.destroyed("test");
+ final ActivityRecord activity = createActivityWithTask();
+ activity.setState(STOPPED, "test");
+ activity.destroyed("test");
}
/**
@@ -1383,10 +1458,11 @@
*/
@Test
public void testDestroyed_destroying() {
- mActivity.setState(DESTROYING, "test");
- mActivity.destroyed("test");
+ final ActivityRecord activity = createActivityWithTask();
+ activity.setState(DESTROYING, "test");
+ activity.destroyed("test");
- verify(mActivity).removeFromHistory(anyString());
+ verify(activity).removeFromHistory(anyString());
}
/**
@@ -1394,15 +1470,17 @@
*/
@Test
public void testDestroyed_destroyed() {
- mActivity.setState(DESTROYED, "test");
- mActivity.destroyed("test");
+ final ActivityRecord activity = createActivityWithTask();
+ activity.setState(DESTROYED, "test");
+ activity.destroyed("test");
- verify(mActivity).removeFromHistory(anyString());
+ verify(activity).removeFromHistory(anyString());
}
@Test
public void testActivityOverridesProcessConfig() {
- final WindowProcessController wpc = mActivity.app;
+ final ActivityRecord activity = createActivityWithTask();
+ final WindowProcessController wpc = activity.app;
assertTrue(wpc.registeredForActivityConfigChanges());
assertFalse(wpc.registeredForDisplayConfigChanges());
@@ -1410,18 +1488,19 @@
createActivityOnDisplay(false /* defaultDisplay */, null /* process */);
assertTrue(wpc.registeredForActivityConfigChanges());
- assertEquals(0, mActivity.getMergedOverrideConfiguration()
+ assertEquals(0, activity.getMergedOverrideConfiguration()
.diff(wpc.getRequestedOverrideConfiguration()));
- assertNotEquals(mActivity.getConfiguration(),
+ assertNotEquals(activity.getConfiguration(),
secondaryDisplayActivity.getConfiguration());
}
@Test
public void testActivityOverridesProcessConfig_TwoActivities() {
- final WindowProcessController wpc = mActivity.app;
+ final ActivityRecord activity = createActivityWithTask();
+ final WindowProcessController wpc = activity.app;
assertTrue(wpc.registeredForActivityConfigChanges());
- final Task firstTaskRecord = mActivity.getTask();
+ final Task firstTaskRecord = activity.getTask();
final ActivityRecord secondActivityRecord =
new ActivityBuilder(mAtm).setTask(firstTaskRecord).setUseProcess(wpc).build();
@@ -1432,11 +1511,12 @@
@Test
public void testActivityOverridesProcessConfig_TwoActivities_SecondaryDisplay() {
- final WindowProcessController wpc = mActivity.app;
+ final ActivityRecord activity = createActivityWithTask();
+ final WindowProcessController wpc = activity.app;
assertTrue(wpc.registeredForActivityConfigChanges());
final ActivityRecord secondActivityRecord =
- new ActivityBuilder(mAtm).setTask(mTask).setUseProcess(wpc).build();
+ new ActivityBuilder(mAtm).setTask(activity.getTask()).setUseProcess(wpc).build();
assertTrue(wpc.registeredForActivityConfigChanges());
assertEquals(0, secondActivityRecord.getMergedOverrideConfiguration()
@@ -1445,7 +1525,8 @@
@Test
public void testActivityOverridesProcessConfig_TwoActivities_DifferentTasks() {
- final WindowProcessController wpc = mActivity.app;
+ final ActivityRecord activity = createActivityWithTask();
+ final WindowProcessController wpc = activity.app;
assertTrue(wpc.registeredForActivityConfigChanges());
final ActivityRecord secondActivityRecord =
@@ -1458,10 +1539,11 @@
@Test
public void testActivityOnCancelFixedRotationTransform() {
- final DisplayRotation displayRotation = mActivity.mDisplayContent.getDisplayRotation();
+ final ActivityRecord activity = createActivityWithTask();
+ final DisplayRotation displayRotation = activity.mDisplayContent.getDisplayRotation();
spyOn(displayRotation);
- final DisplayContent display = mActivity.mDisplayContent;
+ final DisplayContent display = activity.mDisplayContent;
final int originalRotation = display.getRotation();
// Make {@link DisplayContent#sendNewConfiguration} not apply rotation immediately.
@@ -1469,17 +1551,17 @@
doReturn((originalRotation + 1) % 4).when(displayRotation).rotationForOrientation(
anyInt() /* orientation */, anyInt() /* lastRotation */);
// Set to visible so the activity can freeze the screen.
- mActivity.setVisibility(true);
+ activity.setVisibility(true);
- display.rotateInDifferentOrientationIfNeeded(mActivity);
- display.setFixedRotationLaunchingAppUnchecked(mActivity);
+ display.rotateInDifferentOrientationIfNeeded(activity);
+ display.setFixedRotationLaunchingAppUnchecked(activity);
displayRotation.updateRotationUnchecked(true /* forceUpdate */);
assertTrue(displayRotation.isRotatingSeamlessly());
// The launching rotated app should not be cleared when waiting for remote rotation.
display.continueUpdateOrientationForDiffOrienLaunchingApp();
- assertTrue(display.isFixedRotationLaunchingApp(mActivity));
+ assertTrue(display.isFixedRotationLaunchingApp(activity));
// Simulate the rotation has been updated to previous one, e.g. sensor updates before the
// remote rotation is completed.
@@ -1487,16 +1569,16 @@
anyInt() /* orientation */, anyInt() /* lastRotation */);
display.updateOrientation();
- final DisplayInfo rotatedInfo = mActivity.getFixedRotationTransformDisplayInfo();
- mActivity.finishFixedRotationTransform();
+ final DisplayInfo rotatedInfo = activity.getFixedRotationTransformDisplayInfo();
+ activity.finishFixedRotationTransform();
final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
assertNotNull(rotationAnim);
rotationAnim.setRotation(display.getPendingTransaction(), originalRotation);
// Because the display doesn't rotate, the rotated activity needs to cancel the fixed
// rotation. There should be a rotation animation to cover the change of activity.
- verify(mActivity).onCancelFixedRotationTransform(rotatedInfo.rotation);
- assertTrue(mActivity.isFreezingScreen());
+ verify(activity).onCancelFixedRotationTransform(rotatedInfo.rotation);
+ assertTrue(activity.isFreezingScreen());
assertFalse(displayRotation.isRotatingSeamlessly());
assertTrue(rotationAnim.isRotating());
@@ -1504,44 +1586,46 @@
// the rotated activity should also be restored by clearing the transform.
displayRotation.updateRotationUnchecked(true /* forceUpdate */);
doReturn(false).when(displayRotation).isWaitingForRemoteRotation();
- clearInvocations(mActivity);
- display.setFixedRotationLaunchingAppUnchecked(mActivity);
+ clearInvocations(activity);
+ display.setFixedRotationLaunchingAppUnchecked(activity);
display.sendNewConfiguration();
assertFalse(display.hasTopFixedRotationLaunchingApp());
- assertFalse(mActivity.hasFixedRotationTransform());
+ assertFalse(activity.hasFixedRotationTransform());
}
@Test
public void testIsSnapshotCompatible() {
+ final ActivityRecord activity = createActivityWithTask();
final TaskSnapshot snapshot = new TaskSnapshotPersisterTestBase.TaskSnapshotBuilder()
- .setRotation(mActivity.getWindowConfiguration().getRotation())
+ .setRotation(activity.getWindowConfiguration().getRotation())
.build();
- assertTrue(mActivity.isSnapshotCompatible(snapshot));
+ assertTrue(activity.isSnapshotCompatible(snapshot));
- setRotatedScreenOrientationSilently(mActivity);
+ setRotatedScreenOrientationSilently(activity);
- assertFalse(mActivity.isSnapshotCompatible(snapshot));
+ assertFalse(activity.isSnapshotCompatible(snapshot));
}
@Test
public void testFixedRotationSnapshotStartingWindow() {
+ final ActivityRecord activity = createActivityWithTask();
// TaskSnapshotSurface requires a fullscreen opaque window.
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
params.width = params.height = WindowManager.LayoutParams.MATCH_PARENT;
final TestWindowState w = new TestWindowState(
- mAtm.mWindowManager, mock(Session.class), new TestIWindow(), params, mActivity);
- mActivity.addWindow(w);
+ mAtm.mWindowManager, mock(Session.class), new TestIWindow(), params, activity);
+ activity.addWindow(w);
// Assume the activity is launching in different rotation, and there was an available
// snapshot accepted by {@link Activity#isSnapshotCompatible}.
final TaskSnapshot snapshot = new TaskSnapshotPersisterTestBase.TaskSnapshotBuilder()
- .setRotation((mActivity.getWindowConfiguration().getRotation() + 1) % 4)
+ .setRotation((activity.getWindowConfiguration().getRotation() + 1) % 4)
.build();
- setRotatedScreenOrientationSilently(mActivity);
- mActivity.setVisible(false);
+ setRotatedScreenOrientationSilently(activity);
+ activity.setVisible(false);
final IWindowSession session = WindowManagerGlobal.getWindowSession();
spyOn(session);
@@ -1553,7 +1637,7 @@
any() /* requestedVisibility */, any() /* outFrame */,
any() /* outDisplayCutout */, any() /* outInputChannel */,
any() /* outInsetsState */, any() /* outActiveControls */);
- TaskSnapshotSurface.create(mAtm.mWindowManager, mActivity, snapshot);
+ TaskSnapshotSurface.create(mAtm.mWindowManager, activity, snapshot);
} catch (RemoteException ignored) {
} finally {
reset(session);
@@ -1562,8 +1646,8 @@
// Because the rotation of snapshot and the corresponding top activity are different, fixed
// rotation should be applied when creating snapshot surface if the display rotation may be
// changed according to the activity orientation.
- assertTrue(mActivity.hasFixedRotationTransform());
- assertTrue(mActivity.mDisplayContent.isFixedRotationLaunchingApp(mActivity));
+ assertTrue(activity.hasFixedRotationTransform());
+ assertTrue(activity.mDisplayContent.isFixedRotationLaunchingApp(activity));
}
/**
@@ -1596,11 +1680,12 @@
@Test
public void testActivityReparentChangesProcessOverride() {
- final WindowProcessController wpc = mActivity.app;
- final Task initialTask = mActivity.getTask();
+ final ActivityRecord activity = createActivityWithTask();
+ final WindowProcessController wpc = activity.app;
+ final Task initialTask = activity.getTask();
final Configuration initialConf =
- new Configuration(mActivity.getMergedOverrideConfiguration());
- assertEquals(0, mActivity.getMergedOverrideConfiguration()
+ new Configuration(activity.getMergedOverrideConfiguration());
+ assertEquals(0, activity.getMergedOverrideConfiguration()
.diff(wpc.getRequestedOverrideConfiguration()));
assertTrue(wpc.registeredForActivityConfigChanges());
@@ -1613,22 +1698,23 @@
assertEquals(newTask.getConfiguration().densityDpi, newConfig.densityDpi);
// Reparent the activity and verify that config override changed.
- mActivity.reparent(newTask, 0 /* top */, "test");
- assertEquals(mActivity.getConfiguration().densityDpi, newConfig.densityDpi);
- assertEquals(mActivity.getMergedOverrideConfiguration().densityDpi, newConfig.densityDpi);
+ activity.reparent(newTask, 0 /* top */, "test");
+ assertEquals(activity.getConfiguration().densityDpi, newConfig.densityDpi);
+ assertEquals(activity.getMergedOverrideConfiguration().densityDpi, newConfig.densityDpi);
assertTrue(wpc.registeredForActivityConfigChanges());
assertNotEquals(initialConf, wpc.getRequestedOverrideConfiguration());
- assertEquals(0, mActivity.getMergedOverrideConfiguration()
+ assertEquals(0, activity.getMergedOverrideConfiguration()
.diff(wpc.getRequestedOverrideConfiguration()));
}
@Test
public void testActivityReparentDoesntClearProcessOverride_TwoActivities() {
- final WindowProcessController wpc = mActivity.app;
+ final ActivityRecord activity = createActivityWithTask();
+ final WindowProcessController wpc = activity.app;
final Configuration initialConf =
- new Configuration(mActivity.getMergedOverrideConfiguration());
- final Task initialTask = mActivity.getTask();
+ new Configuration(activity.getMergedOverrideConfiguration());
+ final Task initialTask = activity.getTask();
final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(initialTask)
.setUseProcess(wpc).build();
@@ -1652,7 +1738,7 @@
assertNotEquals(initialConf, wpc.getRequestedOverrideConfiguration());
// Reparent the first activity and verify that config override didn't change.
- mActivity.reparent(newTask, 1 /* top */, "test");
+ activity.reparent(newTask, 1 /* top */, "test");
assertTrue(wpc.registeredForActivityConfigChanges());
assertEquals(0, secondActivity.getMergedOverrideConfiguration()
.diff(wpc.getRequestedOverrideConfiguration()));
@@ -1695,67 +1781,90 @@
@Test
public void testFullscreenWindowCanTurnScreenOn() {
- mStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- doReturn(true).when(mActivity).getTurnScreenOnFlag();
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ doReturn(true).when(activity).getTurnScreenOnFlag();
- assertTrue(mActivity.canTurnScreenOn());
+ assertTrue(activity.canTurnScreenOn());
}
@Test
public void testFreeformWindowCanTurnScreenOn() {
- mStack.setWindowingMode(WINDOWING_MODE_FREEFORM);
- doReturn(true).when(mActivity).getTurnScreenOnFlag();
+ final ActivityRecord activity = createActivityWithTask();
+ final Task task = activity.getTask();
+ task.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ doReturn(true).when(activity).getTurnScreenOnFlag();
- assertTrue(mActivity.canTurnScreenOn());
+ assertTrue(activity.canTurnScreenOn());
}
@Test
public void testGetLockTaskLaunchMode() {
+ final ActivityRecord activity = createActivityWithTask();
final ActivityOptions options = ActivityOptions.makeBasic().setLockTaskEnabled(true);
- mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+ activity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
assertEquals(LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED,
- ActivityRecord.getLockTaskLaunchMode(mActivity.info, options));
+ ActivityRecord.getLockTaskLaunchMode(activity.info, options));
- mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
+ activity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
assertEquals(LOCK_TASK_LAUNCH_MODE_DEFAULT,
- ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+ ActivityRecord.getLockTaskLaunchMode(activity.info, null /*options*/));
- mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
+ activity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
assertEquals(LOCK_TASK_LAUNCH_MODE_DEFAULT,
- ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+ ActivityRecord.getLockTaskLaunchMode(activity.info, null /*options*/));
- mActivity.info.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
- mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
+ activity.info.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+ activity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
assertEquals(LOCK_TASK_LAUNCH_MODE_ALWAYS,
- ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+ ActivityRecord.getLockTaskLaunchMode(activity.info, null /*options*/));
- mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
+ activity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
assertEquals(LOCK_TASK_LAUNCH_MODE_NEVER,
- ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+ ActivityRecord.getLockTaskLaunchMode(activity.info, null /*options*/));
}
@Test
public void testProcessInfoUpdateWhenSetState() {
- spyOn(mActivity.app);
- verifyProcessInfoUpdate(RESUMED, true /* shouldUpdate */, true /* activityChange */);
- verifyProcessInfoUpdate(PAUSED, false /* shouldUpdate */, false /* activityChange */);
- verifyProcessInfoUpdate(STOPPED, false /* shouldUpdate */, false /* activityChange */);
- verifyProcessInfoUpdate(STARTED, true /* shouldUpdate */, true /* activityChange */);
+ final ActivityRecord activity = createActivityWithTask();
+ activity.setState(INITIALIZING, "test");
+ spyOn(activity.app);
+ verifyProcessInfoUpdate(activity, RESUMED,
+ true /* shouldUpdate */, true /* activityChange */);
+ verifyProcessInfoUpdate(activity, PAUSED,
+ false /* shouldUpdate */, false /* activityChange */);
+ verifyProcessInfoUpdate(activity, STOPPED,
+ false /* shouldUpdate */, false /* activityChange */);
+ verifyProcessInfoUpdate(activity, STARTED,
+ true /* shouldUpdate */, true /* activityChange */);
- mActivity.app.removeActivity(mActivity, true /* keepAssociation */);
- verifyProcessInfoUpdate(DESTROYING, true /* shouldUpdate */, false /* activityChange */);
- verifyProcessInfoUpdate(DESTROYED, true /* shouldUpdate */, false /* activityChange */);
+ activity.app.removeActivity(activity, true /* keepAssociation */);
+ verifyProcessInfoUpdate(activity, DESTROYING,
+ true /* shouldUpdate */, false /* activityChange */);
+ verifyProcessInfoUpdate(activity, DESTROYED,
+ true /* shouldUpdate */, false /* activityChange */);
}
- private void verifyProcessInfoUpdate(ActivityState state, boolean shouldUpdate,
- boolean activityChange) {
- reset(mActivity.app);
- mActivity.setState(state, "test");
- verify(mActivity.app, times(shouldUpdate ? 1 : 0)).updateProcessInfo(anyBoolean(),
+ private void verifyProcessInfoUpdate(ActivityRecord activity, ActivityState state,
+ boolean shouldUpdate, boolean activityChange) {
+ reset(activity.app);
+ activity.setState(state, "test");
+ verify(activity.app, times(shouldUpdate ? 1 : 0)).updateProcessInfo(anyBoolean(),
eq(activityChange), anyBoolean(), anyBoolean());
}
+ private ActivityRecord createActivityWithTask() {
+ return new ActivityBuilder(mAtm).setCreateTask(true).setOnTop(true).build();
+ }
+
+ private ActivityRecord createActivityWith2LevelTask() {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setCreateParentTask(true).setCreateActivity(true).build();
+ return task.getTopNonFinishingActivity();
+ }
+
/**
* Creates an activity on display. For non-default display request it will also create a new
* display with custom DisplayInfo.
@@ -1769,9 +1878,7 @@
display = new TestDisplayContent.Builder(mAtm, 2000, 1000).setDensityDpi(300)
.setPosition(DisplayContent.POSITION_TOP).build();
}
- final Task stack = display.getDefaultTaskDisplayArea()
- .createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final Task task = new TaskBuilder(mSupervisor).setParentTask(stack).build();
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
return new ActivityBuilder(mAtm).setTask(task).setUseProcess(process).build();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index d872eb8..8ccbb8f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -43,6 +43,7 @@
import static com.android.server.wm.Task.ActivityState.STOPPED;
import static com.android.server.wm.Task.ActivityState.STOPPING;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
+import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE;
import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
@@ -91,59 +92,58 @@
@RunWith(WindowTestRunner.class)
public class ActivityStackTests extends WindowTestsBase {
private TaskDisplayArea mDefaultTaskDisplayArea;
- private Task mStack;
- private Task mTask;
@Before
public void setUp() throws Exception {
mDefaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
- mStack = mDefaultTaskDisplayArea.createStack(WINDOWING_MODE_UNDEFINED,
- ACTIVITY_TYPE_STANDARD, true /* onTop */);
- spyOn(mStack);
- mTask = new TaskBuilder(mSupervisor).setParentTask(mStack).build();
}
@Test
public void testResumedActivity() {
- final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
- assertNull(mStack.getResumedActivity());
+ final ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = r.getTask();
+ assertNull(task.getResumedActivity());
r.setState(RESUMED, "testResumedActivity");
- assertEquals(r, mStack.getResumedActivity());
+ assertEquals(r, task.getResumedActivity());
r.setState(PAUSING, "testResumedActivity");
- assertNull(mStack.getResumedActivity());
+ assertNull(task.getResumedActivity());
}
@Test
public void testResumedActivityFromTaskReparenting() {
- final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final Task parentTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ final ActivityRecord r = new ActivityBuilder(mAtm)
+ .setCreateTask(true).setParentTask(parentTask).build();
+ final Task task = r.getTask();
// Ensure moving task between two stacks updates resumed activity
r.setState(RESUMED, "testResumedActivityFromTaskReparenting");
- assertEquals(r, mStack.getResumedActivity());
+ assertEquals(r, parentTask.getResumedActivity());
- final Task destStack = mDefaultTaskDisplayArea.createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-
- mTask.reparent(destStack, true /* toTop */, Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT,
+ final Task destStack = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ task.reparent(destStack, true /* toTop */, REPARENT_KEEP_ROOT_TASK_AT_FRONT,
false /* animate */, true /* deferResume*/,
"testResumedActivityFromTaskReparenting");
- assertNull(mStack.getResumedActivity());
+ assertNull(parentTask.getResumedActivity());
assertEquals(r, destStack.getResumedActivity());
}
@Test
public void testResumedActivityFromActivityReparenting() {
- final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final Task parentTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ final ActivityRecord r = new ActivityBuilder(mAtm)
+ .setCreateTask(true).setParentTask(parentTask).build();
+ final Task task = r.getTask();
// Ensure moving task between two stacks updates resumed activity
r.setState(RESUMED, "testResumedActivityFromActivityReparenting");
- assertEquals(r, mStack.getResumedActivity());
+ assertEquals(r, parentTask.getResumedActivity());
- final Task destStack = mDefaultTaskDisplayArea.createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- mTask.reparent(destStack, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT, false, false,
+ final Task destStack = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ task.reparent(destStack, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
+ false /* animate */, false /* deferResume*/,
"testResumedActivityFromActivityReparenting");
- assertNull(mStack.getResumedActivity());
+ assertNull(parentTask.getResumedActivity());
assertEquals(r, destStack.getResumedActivity());
}
@@ -292,7 +292,7 @@
public void testStopActivityWhenActivityDestroyed() {
final ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build();
r.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
- mStack.moveToFront("testStopActivityWithDestroy");
+ r.getTask().moveToFront("testStopActivityWithDestroy");
r.stopIfPossible();
// Mostly testing to make sure there is a crash in the call part, so if we get here we are
// good-to-go!
@@ -327,7 +327,8 @@
targetActivity);
final ComponentName alias = new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME,
aliasActivity);
- final Task task = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(mStack).build();
+ final Task parentTask = new TaskBuilder(mAtm.mTaskSupervisor).build();
+ final Task task = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(parentTask).build();
task.origActivity = alias;
task.realActivity = target;
new ActivityBuilder(mAtm).setComponent(target).setTask(task).setTargetActivity(
@@ -337,14 +338,14 @@
final ActivityRecord r1 = new ActivityBuilder(mAtm).setComponent(
target).setTargetActivity(targetActivity).build();
RootWindowContainer.FindTaskResult result = new RootWindowContainer.FindTaskResult();
- result.process(r1, mStack);
+ result.process(r1, parentTask);
assertThat(result.mRecord).isNotNull();
// Using alias activity to find task.
final ActivityRecord r2 = new ActivityBuilder(mAtm).setComponent(
alias).setTargetActivity(targetActivity).build();
result = new RootWindowContainer.FindTaskResult();
- result.process(r2, mStack);
+ result.process(r2, parentTask);
assertThat(result.mRecord).isNotNull();
}
@@ -735,8 +736,6 @@
@Test
public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() {
- mDefaultTaskDisplayArea.removeStack(mStack);
-
final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
final Task fullscreenStack = createStackForShouldBeVisibleTest(
@@ -755,8 +754,6 @@
@Test
public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() {
- mDefaultTaskDisplayArea.removeStack(mStack);
-
final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
final Task fullscreenStack = createStackForShouldBeVisibleTest(
@@ -775,8 +772,6 @@
@Test
public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() {
- mDefaultTaskDisplayArea.removeStack(mStack);
-
final Task fullscreenStack = createStackForShouldBeVisibleTest(
mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
@@ -795,8 +790,6 @@
@Test
public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() {
- mDefaultTaskDisplayArea.removeStack(mStack);
-
final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
@@ -822,8 +815,6 @@
@Test
public void
testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() {
- mDefaultTaskDisplayArea.removeStack(mStack);
-
final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
@@ -846,8 +837,6 @@
@Test
public void testMoveHomeStackBehindStack_BehindHomeStack() {
- mDefaultTaskDisplayArea.removeStack(mStack);
-
final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
@@ -869,8 +858,6 @@
@Test
public void testMoveHomeStackBehindStack() {
- mDefaultTaskDisplayArea.removeStack(mStack);
-
final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
@@ -1019,11 +1006,12 @@
@Test
public void testFinishDisabledPackageActivities_FinishAliveActivities() {
- final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
- final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
+ final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
firstActivity.setState(STOPPED, "testFinishDisabledPackageActivities");
secondActivity.setState(RESUMED, "testFinishDisabledPackageActivities");
- mStack.mResumedActivity = secondActivity;
+ task.mResumedActivity = secondActivity;
// Note the activities have non-null ActivityRecord.app, so it won't remove directly.
mRootWindowContainer.mFinishDisabledPackageActivitiesHelper.process(
@@ -1039,10 +1027,11 @@
@Test
public void testFinishDisabledPackageActivities_RemoveNonAliveActivities() {
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
// The overlay activity is not in the disabled package but it is in the same task.
- final ActivityRecord overlayActivity = new ActivityBuilder(mAtm).setTask(mTask)
+ final ActivityRecord overlayActivity = new ActivityBuilder(mAtm).setTask(task)
.setComponent(new ComponentName("package.overlay", ".OverlayActivity")).build();
// If the task only remains overlay activity, the task should also be removed.
// See {@link ActivityStack#removeFromHistory}.
@@ -1053,7 +1042,7 @@
activity.app = null;
overlayActivity.app = null;
- assertEquals(2, mTask.getChildCount());
+ assertEquals(2, task.getChildCount());
mRootWindowContainer.mFinishDisabledPackageActivitiesHelper.process(
activity.packageName, null /* filterByClasses */, true /* doit */,
@@ -1062,14 +1051,14 @@
// Although the overlay activity is in another package, the non-overlay activities are
// removed from the task. Since the overlay activity should be removed as well, the task
// should be empty.
- assertFalse(mTask.hasChild());
- assertFalse(mStack.hasChild());
+ assertFalse(task.hasChild());
}
@Test
public void testHandleAppDied() {
- final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
- final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
+ final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
// Making the first activity a task overlay means it will be removed from the task's
// activities as well once second activity is removed as handleAppDied processes the
@@ -1080,17 +1069,17 @@
// second activity will be immediately removed as it has no state.
secondActivity.setSavedState(null /* savedState */);
- assertEquals(2, mTask.getChildCount());
+ assertEquals(2, task.getChildCount());
secondActivity.app.handleAppDied();
- assertFalse(mTask.hasChild());
- assertFalse(mStack.hasChild());
+ assertFalse(task.hasChild());
}
@Test
public void testHandleAppDied_RelaunchesAfterCrashDuringWindowingModeResize() {
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
activity.mRelaunchReason = RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
activity.launchCount = 1;
@@ -1098,13 +1087,13 @@
activity.app.handleAppDied();
- assertEquals(1, mTask.getChildCount());
- assertEquals(1, mStack.getChildCount());
+ assertEquals(1, task.getChildCount());
}
@Test
public void testHandleAppDied_NotRelaunchAfterThreeCrashesDuringWindowingModeResize() {
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
activity.mRelaunchReason = RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
activity.launchCount = 3;
@@ -1112,13 +1101,13 @@
activity.app.handleAppDied();
- assertFalse(mTask.hasChild());
- assertFalse(mStack.hasChild());
+ assertFalse(task.hasChild());
}
@Test
public void testHandleAppDied_RelaunchesAfterCrashDuringFreeResize() {
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
activity.mRelaunchReason = RELAUNCH_REASON_FREE_RESIZE;
activity.launchCount = 1;
@@ -1126,13 +1115,13 @@
activity.app.handleAppDied();
- assertEquals(1, mTask.getChildCount());
- assertEquals(1, mStack.getChildCount());
+ assertEquals(1, task.getChildCount());
}
@Test
public void testHandleAppDied_NotRelaunchAfterThreeCrashesDuringFreeResize() {
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
activity.mRelaunchReason = RELAUNCH_REASON_FREE_RESIZE;
activity.launchCount = 3;
@@ -1140,22 +1129,22 @@
activity.app.handleAppDied();
- assertFalse(mTask.hasChild());
- assertFalse(mStack.hasChild());
+ assertFalse(task.hasChild());
}
@Test
public void testCompletePauseOnResumeWhilePausingActivity() {
- final ActivityRecord bottomActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord bottomActivity = new ActivityBuilder(mAtm).setTask(task).build();
doReturn(true).when(bottomActivity).attachedToProcess();
- mStack.mPausingActivity = null;
- mStack.mResumedActivity = bottomActivity;
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ task.mPausingActivity = null;
+ task.mResumedActivity = bottomActivity;
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
topActivity.info.flags |= FLAG_RESUME_WHILE_PAUSING;
- mStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, topActivity,
+ task.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, topActivity,
"test");
- verify(mStack).completePauseLocked(anyBoolean(), eq(topActivity));
+ verify(task).completePauseLocked(anyBoolean(), eq(topActivity));
}
@Test
@@ -1234,10 +1223,11 @@
@Test
public void testStackOrderChangedOnRemoveStack() {
+ final Task task = new TaskBuilder(mSupervisor).build();
StackOrderChangedListener listener = new StackOrderChangedListener();
mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener);
try {
- mDefaultTaskDisplayArea.removeStack(mStack);
+ mDefaultTaskDisplayArea.removeStack(task);
} finally {
mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener);
}
@@ -1246,13 +1236,14 @@
@Test
public void testStackOrderChangedOnAddPositionStack() {
- mDefaultTaskDisplayArea.removeStack(mStack);
+ final Task task = new TaskBuilder(mSupervisor).build();
+ mDefaultTaskDisplayArea.removeStack(task);
StackOrderChangedListener listener = new StackOrderChangedListener();
mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener);
try {
- mStack.mReparenting = true;
- mDefaultTaskDisplayArea.addChild(mStack, 0);
+ task.mReparenting = true;
+ mDefaultTaskDisplayArea.addChild(task, 0);
} finally {
mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener);
}
@@ -1284,20 +1275,21 @@
spyOn(starter);
doReturn(ActivityManager.START_SUCCESS).when(starter).execute();
- final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
- final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask)
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
+ final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task)
.setUid(firstActivity.getUid() + 1).build();
doReturn(starter).when(controller).obtainStarter(eq(firstActivity.intent), anyString());
final IApplicationThread thread = secondActivity.app.getThread();
secondActivity.app.setThread(null);
// This should do nothing from a non-attached caller.
- assertFalse(mStack.navigateUpTo(secondActivity /* source record */,
+ assertFalse(task.navigateUpTo(secondActivity /* source record */,
firstActivity.intent /* destIntent */, null /* destGrants */,
0 /* resultCode */, null /* resultData */, null /* resultGrants */));
secondActivity.app.setThread(thread);
- assertTrue(mStack.navigateUpTo(secondActivity /* source record */,
+ assertTrue(task.navigateUpTo(secondActivity /* source record */,
firstActivity.intent /* destIntent */, null /* destGrants */,
0 /* resultCode */, null /* resultData */, null /* resultGrants */));
// The firstActivity uses default launch mode, so the activities between it and itself will
@@ -1313,9 +1305,10 @@
final String affinity = "affinity";
final ActivityRecord activity = new ActivityBuilder(mAtm).setAffinity(affinity)
.setUid(Binder.getCallingUid()).setCreateTask(true).build();
- activity.getTask().affinity = activity.taskAffinity;
+ final Task task = activity.getTask();
+ task.affinity = activity.taskAffinity;
- assertFalse(mStack.shouldUpRecreateTaskLocked(activity, affinity));
+ assertFalse(task.shouldUpRecreateTaskLocked(activity, affinity));
}
@Test
@@ -1323,21 +1316,23 @@
final String affinity = "affinity";
final ActivityRecord activity = new ActivityBuilder(mAtm).setAffinity(affinity)
.setUid(Binder.getCallingUid()).setCreateTask(true).build();
- activity.getTask().affinity = activity.taskAffinity;
+ final Task task = activity.getTask();
+ task.affinity = activity.taskAffinity;
final String fakeAffinity = activity.getUid() + activity.taskAffinity;
- assertTrue(mStack.shouldUpRecreateTaskLocked(activity, fakeAffinity));
+ assertTrue(task.shouldUpRecreateTaskLocked(activity, fakeAffinity));
}
@Test
public void testResetTaskWithFinishingActivities() {
- final ActivityRecord taskTop = new ActivityBuilder(mAtm).setTask(mStack).build();
+ final ActivityRecord taskTop = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = taskTop.getTask();
// Make all activities in the task are finishing to simulate Task#getTopActivity
// returns null.
taskTop.finishing = true;
final ActivityRecord newR = new ActivityBuilder(mAtm).build();
- final ActivityRecord result = mStack.resetTaskIfNeeded(taskTop, newR);
+ final ActivityRecord result = task.resetTaskIfNeeded(taskTop, newR);
assertThat(result).isEqualTo(taskTop);
}
@@ -1345,14 +1340,15 @@
public void testIterateOccludedActivity() {
final ArrayList<ActivityRecord> occludedActivities = new ArrayList<>();
final Consumer<ActivityRecord> handleOccludedActivity = occludedActivities::add;
- final ActivityRecord bottomActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord bottomActivity = new ActivityBuilder(mAtm).setTask(task).build();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
// Top activity occludes bottom activity.
- doReturn(true).when(mStack).shouldBeVisible(any());
+ doReturn(true).when(task).shouldBeVisible(any());
assertTrue(topActivity.shouldBeVisible());
assertFalse(bottomActivity.shouldBeVisible());
- mStack.forAllOccludedActivities(handleOccludedActivity);
+ task.forAllOccludedActivities(handleOccludedActivity);
assertThat(occludedActivities).containsExactly(bottomActivity);
// Top activity doesn't occlude parent, so the bottom activity is not occluded.
@@ -1360,18 +1356,18 @@
assertTrue(bottomActivity.shouldBeVisible());
occludedActivities.clear();
- mStack.forAllOccludedActivities(handleOccludedActivity);
+ task.forAllOccludedActivities(handleOccludedActivity);
assertThat(occludedActivities).isEmpty();
// A finishing activity should not occlude other activities behind.
- final ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(task).build();
finishingActivity.finishing = true;
doCallRealMethod().when(finishingActivity).occludesParent();
assertTrue(topActivity.shouldBeVisible());
assertTrue(bottomActivity.shouldBeVisible());
occludedActivities.clear();
- mStack.forAllOccludedActivities(handleOccludedActivity);
+ task.forAllOccludedActivities(handleOccludedActivity);
assertThat(occludedActivities).isEmpty();
}
@@ -1385,8 +1381,9 @@
// Start 2 activities that their processes have not yet started.
final ActivityRecord[] activities = new ActivityRecord[2];
mSupervisor.beginDeferResume();
+ final Task task = new TaskBuilder(mSupervisor).build();
for (int i = 0; i < activities.length; i++) {
- final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
+ final ActivityRecord r = new ActivityBuilder(mAtm).setTask(task).build();
activities[i] = r;
doReturn(null).when(mAtm).getProcessController(
eq(r.processName), eq(r.info.applicationInfo.uid));
@@ -1405,7 +1402,7 @@
// Assume the top activity is going to resume and
// {@link RootWindowContainer#cancelInitializingActivities} should clear the unknown
// visibility records that are occluded.
- mStack.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
+ task.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
// Assume the top activity relayouted, just remove it directly.
unknownAppVisibilityController.appRemovedOrHidden(activities[1]);
// All unresolved records should be removed.
@@ -1414,15 +1411,16 @@
@Test
public void testNonTopVisibleActivityNotResume() {
+ final Task task = new TaskBuilder(mSupervisor).build();
final ActivityRecord nonTopVisibleActivity =
- new ActivityBuilder(mAtm).setTask(mTask).build();
- new ActivityBuilder(mAtm).setTask(mTask).build();
+ new ActivityBuilder(mAtm).setTask(task).build();
+ new ActivityBuilder(mAtm).setTask(task).build();
doReturn(false).when(nonTopVisibleActivity).attachedToProcess();
doReturn(true).when(nonTopVisibleActivity).shouldBeVisibleUnchecked();
doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
anyBoolean());
- mStack.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
+ task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
false /* preserveWindows */);
verify(mSupervisor).startSpecificActivity(any(), eq(false) /* andResume */,
anyBoolean());
@@ -1436,16 +1434,17 @@
private void verifyShouldSleepActivities(boolean focusedStack,
boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
boolean expected) {
+ final Task task = new TaskBuilder(mSupervisor).build();
final DisplayContent display = mock(DisplayContent.class);
final KeyguardController keyguardController = mSupervisor.getKeyguardController();
display.isDefaultDisplay = isDefaultDisplay;
- mStack.mDisplayContent = display;
+ task.mDisplayContent = display;
doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway();
doReturn(displaySleeping).when(display).isSleeping();
- doReturn(focusedStack).when(mStack).isFocusedStackOnDisplay();
+ doReturn(focusedStack).when(task).isFocusedStackOnDisplay();
- assertEquals(expected, mStack.shouldSleepActivities());
+ assertEquals(expected, task.shouldSleepActivities());
}
private static class StackOrderChangedListener
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index f607448..bded3f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -126,6 +126,7 @@
private static final String FAKE_CALLING_PACKAGE = "com.whatever.dude";
private static final int UNIMPORTANT_UID = 12345;
private static final int UNIMPORTANT_UID2 = 12346;
+ private static final int CURRENT_IME_UID = 12347;
@Before
public void setUp() throws Exception {
@@ -315,6 +316,12 @@
return prepareStarter(launchFlags, mockGetLaunchStack, LAUNCH_MULTIPLE);
}
+ private void setupImeWindow() {
+ final WindowState imeWindow = createWindow(null, W_INPUT_METHOD,
+ "mImeWindow", CURRENT_IME_UID);
+ mDisplayContent.mInputMethodWindow = imeWindow;
+ }
+
/**
* Creates a {@link ActivityStarter} with default parameters and necessary mocks.
*
@@ -654,6 +661,14 @@
UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
false, false, false, false, true);
+
+ setupImeWindow();
+ runAndVerifyBackgroundActivityStartsSubtest(
+ "disallowed_callingPackageNameIsIme_notAborted", false,
+ CURRENT_IME_UID, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ false, false, false, false, false);
+
}
private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted,
@@ -680,14 +695,11 @@
boolean isCallingUidDeviceOwner,
boolean isPinnedSingleInstance) {
// window visibility
- doReturn(callingUidHasVisibleWindow).when(mAtm.mWindowManager.mRoot)
- .isAnyNonToastWindowVisibleForUid(callingUid);
- doReturn(realCallingUidHasVisibleWindow).when(mAtm.mWindowManager.mRoot)
- .isAnyNonToastWindowVisibleForUid(realCallingUid);
-
+ doReturn(callingUidHasVisibleWindow).when(mAtm).hasActiveVisibleWindow(callingUid);
+ doReturn(realCallingUidHasVisibleWindow).when(mAtm).hasActiveVisibleWindow(realCallingUid);
// process importance
- doReturn(callingUidProcState).when(mAtm).getUidState(callingUid);
- doReturn(realCallingUidProcState).when(mAtm).getUidState(realCallingUid);
+ mAtm.mActiveUids.onUidActive(callingUid, callingUidProcState);
+ mAtm.mActiveUids.onUidActive(realCallingUid, realCallingUidProcState);
// foreground activities
final IApplicationThread caller = mock(IApplicationThread.class);
final WindowProcessListener listener = mock(WindowProcessListener.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index f61253c..017ed88 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -19,8 +19,6 @@
import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -44,7 +42,6 @@
import androidx.test.filters.MediumTest;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,13 +55,6 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class ActivityTaskSupervisorTests extends WindowTestsBase {
- private Task mFullscreenTask;
-
- @Before
- public void setUp() throws Exception {
- mFullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- }
/**
* Ensures that an activity is removed from the stopping activities list once it is resumed.
@@ -72,7 +62,7 @@
@Test
public void testStoppingActivityRemovedWhenResumed() {
final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
- .setTask(mFullscreenTask).build();
+ .setCreateTask(true).build();
mSupervisor.mStoppingActivities.add(firstActivity);
firstActivity.completeResumeLocked();
@@ -86,7 +76,7 @@
@Test
public void testReportWaitingActivityLaunchedIfNeeded() {
final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
- .setTask(mFullscreenTask).build();
+ .setCreateTask(true).build();
final WaitResult taskToFrontWait = new WaitResult();
mSupervisor.mWaitingActivityLaunched.add(taskToFrontWait);
@@ -153,7 +143,7 @@
@Test
public void testNotifyTaskFocusChanged() {
final ActivityRecord fullScreenActivityA = new ActivityBuilder(mAtm).setCreateTask(true)
- .setParentTask(mFullscreenTask).build();
+ .build();
final Task taskA = fullScreenActivityA.getTask();
final TaskChangeNotificationController taskChangeNotifier =
@@ -166,7 +156,7 @@
reset(taskChangeNotifier);
final ActivityRecord fullScreenActivityB = new ActivityBuilder(mAtm).setCreateTask(true)
- .setParentTask(mFullscreenTask).build();
+ .build();
final Task taskB = fullScreenActivityB.getTask();
mAtm.setResumedActivityUncheckLocked(fullScreenActivityB, "resumeB");
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 87a5985..91b9449 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -53,14 +53,12 @@
@RunWith(WindowTestRunner.class)
public class AppChangeTransitionTests extends WindowTestsBase {
- private Task mStack;
private Task mTask;
private ActivityRecord mActivity;
public void setUpOnDisplay(DisplayContent dc) {
mActivity = createActivityRecord(dc, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
mTask = mActivity.getTask();
- mStack = mTask.getRootTask();
// Set a remote animator with snapshot disabled. Snapshots don't work in wmtests.
RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
@@ -143,7 +141,7 @@
// Reparenting to a display with different windowing mode may trigger
// a change transition internally, but it should be cleaned-up once
// the display change is complete.
- mStack.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true);
+ mTask.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true);
assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index f77454d..d899ebe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -65,7 +65,6 @@
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -82,94 +81,87 @@
@RunWith(WindowTestRunner.class)
public class AppWindowTokenTests extends WindowTestsBase {
- Task mStack;
- Task mTask;
- ActivityRecord mActivity;
-
private final String mPackageName = getInstrumentation().getTargetContext().getPackageName();
- @Before
- public void setUp() throws Exception {
- mStack = createTaskStackOnDisplay(mDisplayContent);
- mTask = createTaskInStack(mStack, 0 /* userId */);
- mActivity = createNonAttachedActivityRecord(mDisplayContent);
-
- mTask.addChild(mActivity, 0);
- }
-
@Test
@Presubmit
public void testAddWindow_Order() {
- assertEquals(0, mActivity.getChildCount());
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ assertEquals(0, activity.getChildCount());
- final WindowState win1 = createWindow(null, TYPE_APPLICATION, mActivity, "win1");
- final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, mActivity,
+ final WindowState win1 = createWindow(null, TYPE_APPLICATION, activity, "win1");
+ final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity,
"startingWin");
- final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, mActivity, "baseWin");
- final WindowState win4 = createWindow(null, TYPE_APPLICATION, mActivity, "win4");
+ final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, activity, "baseWin");
+ final WindowState win4 = createWindow(null, TYPE_APPLICATION, activity, "win4");
// Should not contain the windows that were added above.
- assertEquals(4, mActivity.getChildCount());
- assertTrue(mActivity.mChildren.contains(win1));
- assertTrue(mActivity.mChildren.contains(startingWin));
- assertTrue(mActivity.mChildren.contains(baseWin));
- assertTrue(mActivity.mChildren.contains(win4));
+ assertEquals(4, activity.getChildCount());
+ assertTrue(activity.mChildren.contains(win1));
+ assertTrue(activity.mChildren.contains(startingWin));
+ assertTrue(activity.mChildren.contains(baseWin));
+ assertTrue(activity.mChildren.contains(win4));
// The starting window should be on-top of all other windows.
- assertEquals(startingWin, mActivity.mChildren.peekLast());
+ assertEquals(startingWin, activity.mChildren.peekLast());
// The base application window should be below all other windows.
- assertEquals(baseWin, mActivity.mChildren.peekFirst());
- mActivity.removeImmediately();
+ assertEquals(baseWin, activity.mChildren.peekFirst());
+ activity.removeImmediately();
}
@Test
@Presubmit
public void testFindMainWindow() {
- assertNull(mActivity.findMainWindow());
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ assertNull(activity.findMainWindow());
- final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mActivity, "window1");
- final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, mActivity, "window11");
- final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, mActivity, "window12");
- assertEquals(window1, mActivity.findMainWindow());
+ final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "window1");
+ final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, activity, "window11");
+ final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, activity, "window12");
+ assertEquals(window1, activity.findMainWindow());
window1.mAnimatingExit = true;
- assertEquals(window1, mActivity.findMainWindow());
- final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, mActivity,
+ assertEquals(window1, activity.findMainWindow());
+ final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, activity,
"window2");
- assertEquals(window2, mActivity.findMainWindow());
- mActivity.removeImmediately();
+ assertEquals(window2, activity.findMainWindow());
+ activity.removeImmediately();
}
@Test
@Presubmit
public void testGetTopFullscreenOpaqueWindow() {
- assertNull(mActivity.getTopFullscreenOpaqueWindow());
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ assertNull(activity.getTopFullscreenOpaqueWindow());
- final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mActivity, "window1");
- final WindowState window11 = createWindow(null, TYPE_APPLICATION, mActivity, "window11");
- final WindowState window12 = createWindow(null, TYPE_APPLICATION, mActivity, "window12");
- assertEquals(window12, mActivity.getTopFullscreenOpaqueWindow());
+ final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "window1");
+ final WindowState window11 = createWindow(null, TYPE_APPLICATION, activity, "window11");
+ final WindowState window12 = createWindow(null, TYPE_APPLICATION, activity, "window12");
+ assertEquals(window12, activity.getTopFullscreenOpaqueWindow());
window12.mAttrs.width = 500;
- assertEquals(window11, mActivity.getTopFullscreenOpaqueWindow());
+ assertEquals(window11, activity.getTopFullscreenOpaqueWindow());
window11.mAttrs.width = 500;
- assertEquals(window1, mActivity.getTopFullscreenOpaqueWindow());
+ assertEquals(window1, activity.getTopFullscreenOpaqueWindow());
window1.mAttrs.alpha = 0f;
- assertNull(mActivity.getTopFullscreenOpaqueWindow());
- mActivity.removeImmediately();
+ assertNull(activity.getTopFullscreenOpaqueWindow());
+ activity.removeImmediately();
}
@UseTestDisplay(addWindows = W_ACTIVITY)
@Test
@FlakyTest(bugId = 131005232)
public void testLandscapeSeascapeRotationByApp() {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setDisplay(mDisplayContent).setCreateActivity(true).build();
+ final ActivityRecord activity = task.getTopNonFinishingActivity();
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
attrs.setTitle("AppWindow");
- final TestWindowState appWindow = createWindowState(attrs, mActivity);
- mActivity.addWindow(appWindow);
+ final TestWindowState appWindow = createWindowState(attrs, activity);
+ activity.addWindow(appWindow);
// Set initial orientation and update.
- mActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
mDisplayContent.updateOrientation(
mDisplayContent.getRequestedOverrideConfiguration(),
null /* freezeThisOneIfNeeded */, false /* forceUpdate */);
@@ -177,7 +169,7 @@
appWindow.mResizeReported = false;
// Update the orientation to perform 180 degree rotation and check that resize was reported.
- mActivity.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+ activity.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
mDisplayContent.updateOrientation(
mDisplayContent.getRequestedOverrideConfiguration(),
null /* freezeThisOneIfNeeded */, false /* forceUpdate */);
@@ -192,14 +184,17 @@
@UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testLandscapeSeascapeRotationByPolicy() {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setDisplay(mDisplayContent).setCreateActivity(true).build();
+ final ActivityRecord activity = task.getTopNonFinishingActivity();
// This instance has been spied in {@link TestDisplayContent}.
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
attrs.setTitle("RotationByPolicy");
- final TestWindowState appWindow = createWindowState(attrs, mActivity);
- mActivity.addWindow(appWindow);
+ final TestWindowState appWindow = createWindowState(attrs, activity);
+ activity.addWindow(appWindow);
// Set initial orientation and update.
performRotation(displayRotation, Surface.ROTATION_90);
@@ -220,48 +215,53 @@
@Test
@Presubmit
public void testGetOrientation() {
- mActivity.setVisible(true);
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity.setVisible(true);
- mActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- mActivity.setOccludesParent(false);
+ activity.setOccludesParent(false);
// Can specify orientation if app doesn't occludes parent.
- assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mActivity.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, activity.getOrientation());
- mActivity.setOccludesParent(true);
- mActivity.setVisible(false);
+ activity.setOccludesParent(true);
+ activity.setVisible(false);
// Can not specify orientation if app isn't visible even though it occludes parent.
- assertEquals(SCREEN_ORIENTATION_UNSET, mActivity.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_UNSET, activity.getOrientation());
// Can specify orientation if the current orientation candidate is orientation behind.
assertEquals(SCREEN_ORIENTATION_LANDSCAPE,
- mActivity.getOrientation(SCREEN_ORIENTATION_BEHIND));
+ activity.getOrientation(SCREEN_ORIENTATION_BEHIND));
}
@Test
@Presubmit
public void testKeyguardFlagsDuringRelaunch() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
attrs.flags |= FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD;
attrs.setTitle("AppWindow");
- final TestWindowState appWindow = createWindowState(attrs, mActivity);
+ final TestWindowState appWindow = createWindowState(attrs, activity);
// Add window with show when locked flag
- mActivity.addWindow(appWindow);
- assertTrue(mActivity.containsShowWhenLockedWindow() && mActivity.containsDismissKeyguardWindow());
+ activity.addWindow(appWindow);
+ assertTrue(activity.containsShowWhenLockedWindow()
+ && activity.containsDismissKeyguardWindow());
// Start relaunching
- mActivity.startRelaunching();
- assertTrue(mActivity.containsShowWhenLockedWindow() && mActivity.containsDismissKeyguardWindow());
+ activity.startRelaunching();
+ assertTrue(activity.containsShowWhenLockedWindow()
+ && activity.containsDismissKeyguardWindow());
// Remove window and make sure that we still report back flag
- mActivity.removeChild(appWindow);
- assertTrue(mActivity.containsShowWhenLockedWindow() && mActivity.containsDismissKeyguardWindow());
+ activity.removeChild(appWindow);
+ assertTrue(activity.containsShowWhenLockedWindow()
+ && activity.containsDismissKeyguardWindow());
// Finish relaunching and ensure flag is now not reported
- mActivity.finishRelaunching();
- assertFalse(
- mActivity.containsShowWhenLockedWindow() || mActivity.containsDismissKeyguardWindow());
+ activity.finishRelaunching();
+ assertFalse(activity.containsShowWhenLockedWindow()
+ || activity.containsDismissKeyguardWindow());
}
@Test
@@ -281,17 +281,18 @@
@Test
public void testSetOrientation() {
- mActivity.setVisible(true);
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity.setVisible(true);
// Assert orientation is unspecified to start.
- assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.getOrientation());
- mActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mActivity.getOrientation());
+ activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, activity.getOrientation());
- mDisplayContent.removeAppToken(mActivity.token);
+ mDisplayContent.removeAppToken(activity.token);
// Assert orientation is unset to after container is removed.
- assertEquals(SCREEN_ORIENTATION_UNSET, mActivity.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_UNSET, activity.getOrientation());
// Reset display frozen state
mWm.mDisplayFrozen = false;
@@ -300,14 +301,15 @@
@UseTestDisplay
@Test
public void testRespectTopFullscreenOrientation() {
- final Configuration displayConfig = mActivity.mDisplayContent.getConfiguration();
- final Configuration activityConfig = mActivity.getConfiguration();
- mActivity.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Configuration displayConfig = activity.mDisplayContent.getConfiguration();
+ final Configuration activityConfig = activity.getConfiguration();
+ activity.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
assertEquals(Configuration.ORIENTATION_PORTRAIT, displayConfig.orientation);
assertEquals(Configuration.ORIENTATION_PORTRAIT, activityConfig.orientation);
- final ActivityRecord topActivity = createActivityRecord(mTask);
+ final ActivityRecord topActivity = createActivityRecord(activity.getTask());
topActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
assertEquals(Configuration.ORIENTATION_LANDSCAPE, displayConfig.orientation);
@@ -322,32 +324,36 @@
@UseTestDisplay
@Test
public void testReportOrientationChange() {
- mActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ final Task task = new TaskBuilder(mSupervisor)
+ .setDisplay(mDisplayContent).setCreateActivity(true).build();
+ final ActivityRecord activity = task.getTopNonFinishingActivity();
+ activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
mDisplayContent.getDisplayRotation().setFixedToUserRotation(
IWindowManager.FIXED_TO_USER_ROTATION_ENABLED);
- reset(mTask);
- mActivity.reportDescendantOrientationChangeIfNeeded();
- verify(mTask).onConfigurationChanged(any(Configuration.class));
+ reset(task);
+ activity.reportDescendantOrientationChangeIfNeeded();
+ verify(task).onConfigurationChanged(any(Configuration.class));
}
@Test
@FlakyTest(bugId = 131176283)
public void testCreateRemoveStartingWindow() {
- mActivity.addStartingWindow(mPackageName,
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
false);
waitUntilHandlersIdle();
- assertHasStartingWindow(mActivity);
- mActivity.removeStartingWindow();
+ assertHasStartingWindow(activity);
+ activity.removeStartingWindow();
waitUntilHandlersIdle();
- assertNoStartingWindow(mActivity);
+ assertNoStartingWindow(activity);
}
@Test
public void testAddRemoveRace() {
// There was once a race condition between adding and removing starting windows
- final ActivityRecord appToken = createIsolatedTestActivityRecord();
+ final ActivityRecord appToken = new ActivityBuilder(mAtm).setCreateTask(true).build();
for (int i = 0; i < 1000; i++) {
appToken.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
@@ -360,8 +366,8 @@
@Test
public void testTransferStartingWindow() {
- final ActivityRecord activity1 = createIsolatedTestActivityRecord();
- final ActivityRecord activity2 = createIsolatedTestActivityRecord();
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity1.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
false);
@@ -376,8 +382,8 @@
@Test
public void testTransferStartingWindowWhileCreating() {
- final ActivityRecord activity1 = createIsolatedTestActivityRecord();
- final ActivityRecord activity2 = createIsolatedTestActivityRecord();
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
((TestWindowManagerPolicy) activity1.mWmService.mPolicy).setRunnableWhenAddingSplashScreen(
() -> {
// Surprise, ...! Transfer window in the middle of the creation flow.
@@ -396,8 +402,8 @@
@Test
public void testTransferStartingWindowCanAnimate() {
- final ActivityRecord activity1 = createIsolatedTestActivityRecord();
- final ActivityRecord activity2 = createIsolatedTestActivityRecord();
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity1.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
false);
@@ -419,34 +425,34 @@
@Test
public void testTransferStartingWindowFromFinishingActivity() {
- mActivity.addStartingWindow(mPackageName, android.R.style.Theme, null /* compatInfo */,
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity.getTask();
+ activity.addStartingWindow(mPackageName, android.R.style.Theme, null /* compatInfo */,
"Test", 0 /* labelRes */, 0 /* icon */, 0 /* logo */, 0 /* windowFlags */,
null /* transferFrom */, true /* newTask */, true /* taskSwitch */,
false /* processRunning */, false /* allowTaskSnapshot */,
false /* activityCreate */);
waitUntilHandlersIdle();
- assertHasStartingWindow(mActivity);
- mActivity.mStartingWindowState = ActivityRecord.STARTING_WINDOW_SHOWN;
+ assertHasStartingWindow(activity);
+ activity.mStartingWindowState = ActivityRecord.STARTING_WINDOW_SHOWN;
- doCallRealMethod().when(mStack).startActivityLocked(
+ doCallRealMethod().when(task).startActivityLocked(
any(), any(), anyBoolean(), anyBoolean(), any());
// Make mVisibleSetFromTransferredStartingWindow true.
- final ActivityRecord middle = new ActivityBuilder(mWm.mAtmService)
- .setTask(mTask).build();
- mStack.startActivityLocked(middle, null /* focusedTopActivity */,
+ final ActivityRecord middle = new ActivityBuilder(mAtm).setTask(task).build();
+ task.startActivityLocked(middle, null /* focusedTopActivity */,
false /* newTask */, false /* keepCurTransition */, null /* options */);
middle.makeFinishingLocked();
- assertNull(mActivity.mStartingWindow);
+ assertNull(activity.mStartingWindow);
assertHasStartingWindow(middle);
- final ActivityRecord top = new ActivityBuilder(mWm.mAtmService)
- .setTask(mTask).build();
+ final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).build();
// Expect the visibility should be updated to true when transferring starting window from
// a visible activity.
top.setVisible(false);
// The finishing middle should be able to transfer starting window to top.
- mStack.startActivityLocked(top, null /* focusedTopActivity */,
+ task.startActivityLocked(top, null /* focusedTopActivity */,
false /* newTask */, false /* keepCurTransition */, null /* options */);
assertNull(middle.mStartingWindow);
@@ -459,10 +465,12 @@
@Test
public void testTransferStartingWindowSetFixedRotation() {
- final ActivityRecord topActivity = createTestActivityRecordForGivenTask(mTask);
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity.getTask();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
topActivity.setVisible(false);
- mTask.positionChildAt(topActivity, POSITION_TOP);
- mActivity.addStartingWindow(mPackageName,
+ task.positionChildAt(topActivity, POSITION_TOP);
+ activity.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
false);
waitUntilHandlersIdle();
@@ -470,38 +478,25 @@
// Make activities to have different rotation from it display and set fixed rotation
// transform to activity1.
int rotation = (mDisplayContent.getRotation() + 1) % 4;
- mDisplayContent.setFixedRotationLaunchingApp(mActivity, rotation);
+ mDisplayContent.setFixedRotationLaunchingApp(activity, rotation);
doReturn(rotation).when(mDisplayContent)
.rotationForActivityInDifferentOrientation(topActivity);
// Make sure the fixed rotation transform linked to activity2 when adding starting window
// on activity2.
topActivity.addStartingWindow(mPackageName,
- android.R.style.Theme, null, "Test", 0, 0, 0, 0, mActivity.appToken.asBinder(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity.appToken.asBinder(),
false, false, false, true, false);
waitUntilHandlersIdle();
assertTrue(topActivity.hasFixedRotationTransform());
}
- private ActivityRecord createIsolatedTestActivityRecord() {
- final Task taskStack = createTaskStackOnDisplay(mDisplayContent);
- final Task task = createTaskInStack(taskStack, 0 /* userId */);
- return createTestActivityRecordForGivenTask(task);
- }
-
- private ActivityRecord createTestActivityRecordForGivenTask(Task task) {
- final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
- task.addChild(activity, 0);
- waitUntilHandlersIdle();
- return activity;
- }
-
@Test
public void testTryTransferStartingWindowFromHiddenAboveToken() {
// Add two tasks on top of each other.
- final ActivityRecord activityTop = createIsolatedTestActivityRecord();
- final ActivityRecord activityBottom =
- createTestActivityRecordForGivenTask(activityTop.getTask());
+ final ActivityRecord activityTop = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord activityBottom = new ActivityBuilder(mAtm).build();
+ activityTop.getTask().addChild(activityBottom, 0);
// Add a starting window.
activityTop.addStartingWindow(mPackageName,
@@ -523,53 +518,58 @@
@Test
public void testTransitionAnimationBounds() {
removeGlobalMinSizeRestriction();
+ final Task task = new TaskBuilder(mSupervisor)
+ .setCreateParentTask(true).setCreateActivity(true).build();
+ final Task rootTask = task.getRootTask();
+ final ActivityRecord activity = task.getTopNonFinishingActivity();
final Rect stackBounds = new Rect(0, 0, 1000, 600);
final Rect taskBounds = new Rect(100, 400, 600, 800);
// Set the bounds and windowing mode to window configuration directly, otherwise the
// testing setups may be discarded by configuration resolving.
- mStack.getWindowConfiguration().setBounds(stackBounds);
- mTask.getWindowConfiguration().setBounds(taskBounds);
- mActivity.getWindowConfiguration().setBounds(taskBounds);
+ rootTask.getWindowConfiguration().setBounds(stackBounds);
+ task.getWindowConfiguration().setBounds(taskBounds);
+ activity.getWindowConfiguration().setBounds(taskBounds);
// Check that anim bounds for freeform window match task bounds
- mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_NONE));
+ task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertEquals(task.getBounds(), activity.getAnimationBounds(STACK_CLIP_NONE));
// STACK_CLIP_AFTER_ANIM should use task bounds since they will be clipped by
// bounds animation layer.
- mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
+ task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ assertEquals(task.getBounds(), activity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
// Even the activity is smaller than task and it is not aligned to the top-left corner of
// task, the animation bounds the same as task and position should be zero because in real
// case the letterbox will fill the remaining area in task.
final Rect halfBounds = new Rect(taskBounds);
halfBounds.scale(0.5f);
- mActivity.getWindowConfiguration().setBounds(halfBounds);
+ activity.getWindowConfiguration().setBounds(halfBounds);
final Point animationPosition = new Point();
- mActivity.getAnimationPosition(animationPosition);
+ activity.getAnimationPosition(animationPosition);
- assertEquals(taskBounds, mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
+ assertEquals(taskBounds, activity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
assertEquals(new Point(0, 0), animationPosition);
// STACK_CLIP_BEFORE_ANIM should use stack bounds since it won't be clipped later.
- mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- assertEquals(mStack.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_BEFORE_ANIM));
+ task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ assertEquals(rootTask.getBounds(), activity.getAnimationBounds(STACK_CLIP_BEFORE_ANIM));
}
@Test
public void testHasStartingWindow() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
final WindowManager.LayoutParams attrs =
new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
- final TestWindowState startingWindow = createWindowState(attrs, mActivity);
- mActivity.startingDisplayed = true;
- mActivity.addWindow(startingWindow);
- assertTrue("Starting window should be present", mActivity.hasStartingWindow());
- mActivity.startingDisplayed = false;
- assertTrue("Starting window should be present", mActivity.hasStartingWindow());
+ final TestWindowState startingWindow = createWindowState(attrs, activity);
+ activity.startingDisplayed = true;
+ activity.addWindow(startingWindow);
+ assertTrue("Starting window should be present", activity.hasStartingWindow());
+ activity.startingDisplayed = false;
+ assertTrue("Starting window should be present", activity.hasStartingWindow());
- mActivity.removeChild(startingWindow);
- assertFalse("Starting window should not be present", mActivity.hasStartingWindow());
+ activity.removeChild(startingWindow);
+ assertFalse("Starting window should not be present", activity.hasStartingWindow());
}
private void assertHasStartingWindow(ActivityRecord atoken) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java
index cfe956f..06a6882 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -52,8 +50,6 @@
private DisplayAreaGroup mDisplayAreaGroup;
private TaskDisplayArea mTaskDisplayArea;
- private Task mStack;
- private ActivityRecord mActivity;
@Before
public void setUp() {
@@ -65,9 +61,6 @@
mTaskDisplayArea = new TaskDisplayArea(
mDisplayContent, mWm, "TDA1", FEATURE_VENDOR_FIRST + 1);
mDisplayAreaGroup.addChild(mTaskDisplayArea, POSITION_TOP);
- mStack = mTaskDisplayArea.createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- mActivity = new ActivityBuilder(mAtm).setTask(mStack).build();
mDisplayContent.setLastFocusedTaskDisplayArea(mTaskDisplayArea);
}
@@ -91,28 +84,31 @@
@Test
public void testGetRequestedOrientationForDisplay() {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setTaskDisplayArea(mTaskDisplayArea).setCreateActivity(true).build();
+ final ActivityRecord activity = task.getTopNonFinishingActivity();
doReturn(true).when(mDisplayContent).onDescendantOrientationChanged(any(), any());
- mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+ activity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
// Display is portrait, DisplayAreaGroup inherits that
mDisplayContent.setBounds(0, 0, 600, 900);
assertThat(mDisplayAreaGroup.getOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
- assertThat(mActivity.getRequestedConfigurationOrientation(true /* forDisplay */))
+ assertThat(activity.getRequestedConfigurationOrientation(true /* forDisplay */))
.isEqualTo(ORIENTATION_PORTRAIT);
// DisplayAreaGroup is landscape, different from Display
mDisplayAreaGroup.setBounds(0, 0, 600, 450);
assertThat(mDisplayAreaGroup.getOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
- assertThat(mActivity.getRequestedConfigurationOrientation(true /* forDisplay */))
+ assertThat(activity.getRequestedConfigurationOrientation(true /* forDisplay */))
.isEqualTo(ORIENTATION_LANDSCAPE);
// DisplayAreaGroup is portrait, same as Display
mDisplayAreaGroup.setBounds(0, 0, 300, 900);
assertThat(mDisplayAreaGroup.getOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
- assertThat(mActivity.getRequestedConfigurationOrientation(true /* forDisplay */))
+ assertThat(activity.getRequestedConfigurationOrientation(true /* forDisplay */))
.isEqualTo(ORIENTATION_PORTRAIT);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 62aa02f..42193c8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -17,11 +17,6 @@
package com.android.server.wm;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -35,7 +30,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import android.app.WindowConfiguration;
import android.content.ComponentName;
@@ -58,57 +52,6 @@
@RunWith(WindowTestRunner.class)
public class RootWindowContainerTests extends WindowTestsBase {
- private static final int FAKE_CALLING_UID = 667;
-
- @Test
- public void testIsAnyNonToastWindowVisibleForUid_oneToastOneAppStartOneNonToastBothVisible() {
- final WindowState toastyToast = createWindow(null, TYPE_TOAST, "toast", FAKE_CALLING_UID);
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app", FAKE_CALLING_UID);
- final WindowState appStart = createWindow(null, TYPE_APPLICATION_STARTING, "appStarting",
- FAKE_CALLING_UID);
- toastyToast.mHasSurface = true;
- app.mHasSurface = true;
- appStart.mHasSurface = true;
-
- assertTrue(toastyToast.isVisibleNow());
- assertTrue(app.isVisibleNow());
- assertTrue(appStart.isVisibleNow());
- assertTrue(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
- }
-
- @Test
- public void testIsAnyNonToastWindowVisibleForUid_onlyToastVisible() {
- final WindowState toastyToast = createWindow(null, TYPE_TOAST, "toast", FAKE_CALLING_UID);
- toastyToast.mHasSurface = true;
-
- assertTrue(toastyToast.isVisibleNow());
- assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
- }
-
- @Test
- public void testIsAnyNonToastWindowVisibleForUid_onlyAppStartingVisible() {
- final WindowState appStart = createWindow(null, TYPE_APPLICATION_STARTING, "appStarting",
- FAKE_CALLING_UID);
- appStart.mHasSurface = true;
-
- assertTrue(appStart.isVisibleNow());
- assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
- }
-
- @Test
- public void testIsAnyNonToastWindowVisibleForUid_aFewNonToastButNoneVisible() {
- final WindowState statusBar =
- createWindow(null, TYPE_STATUS_BAR, "statusBar", FAKE_CALLING_UID);
- final WindowState notificationShade = createWindow(null, TYPE_NOTIFICATION_SHADE,
- "notificationShade", FAKE_CALLING_UID);
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app", FAKE_CALLING_UID);
-
- assertFalse(statusBar.isVisibleNow());
- assertFalse(notificationShade.isVisibleNow());
- assertFalse(app.isVisibleNow());
- assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
- }
-
@Test
public void testUpdateDefaultDisplayWindowingModeOnSettingsRetrieved() {
assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 2326237..a4bf594 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -72,13 +72,11 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class SizeCompatTests extends WindowTestsBase {
- private Task mStack;
private Task mTask;
private ActivityRecord mActivity;
private void setUpApp(DisplayContent display) {
- mStack = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true).build();
- mTask = mStack.getBottomMostTask();
+ mTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true).build();
mActivity = mTask.getTopNonFinishingActivity();
}
@@ -97,7 +95,7 @@
prepareUnresizable(1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
- resizeDisplay(mStack.mDisplayContent, 600, 1200);
+ resizeDisplay(mTask.mDisplayContent, 600, 1200);
// The visible activity should recompute configuration according to the last parent bounds.
mAtm.restartActivityProcessIfVisible(mActivity.appToken);
@@ -196,7 +194,7 @@
final int originalDpi = mActivity.getConfiguration().densityDpi;
// Move the non-resizable activity to the new display.
- mStack.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
+ mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
assertEquals(originalBounds.width(), mActivity.getBounds().width());
assertEquals(originalBounds.height(), mActivity.getBounds().height());
@@ -321,7 +319,7 @@
.setCanRotate(false).setNotch(notchHeight).build();
// Move the non-resizable activity to the new display.
- mStack.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
+ mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
// The configuration bounds [820, 0 - 1820, 2500] should keep the same.
assertEquals(origWidth, configBounds.width());
assertEquals(origHeight, configBounds.height());
@@ -363,7 +361,7 @@
// Although the activity is fixed orientation, force rotate the display.
rotateDisplay(mActivity.mDisplayContent, ROTATION_270);
- assertEquals(ROTATION_270, mStack.getWindowConfiguration().getRotation());
+ assertEquals(ROTATION_270, mTask.getWindowConfiguration().getRotation());
assertEquals(origBounds.width(), currentBounds.width());
// The notch is on horizontal side, so current height changes from 1460 to 1400.
@@ -436,7 +434,7 @@
public void testResetNonVisibleActivity() {
setUpDisplaySizeWithApp(1000, 2500);
prepareUnresizable(1.5f, SCREEN_ORIENTATION_UNSPECIFIED);
- final DisplayContent display = mStack.mDisplayContent;
+ final DisplayContent display = mTask.mDisplayContent;
// Resize the display so the activity is in size compatibility mode.
resizeDisplay(display, 900, 1800);
@@ -488,7 +486,7 @@
});
// Resize the display so that the activity exercises size-compat mode.
- resizeDisplay(mStack.mDisplayContent, 1000, 2500);
+ resizeDisplay(mTask.mDisplayContent, 1000, 2500);
// Expect the exact token when the activity is in size compatibility mode.
assertEquals(1, compatTokens.size());
@@ -501,7 +499,7 @@
activity.restartProcessIfVisible();
// The full lifecycle isn't hooked up so manually set state to resumed
activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
- mStack.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity);
+ mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity);
// Expect null token when switching to non-size-compat mode activity.
assertEquals(1, compatTokens.size());
@@ -525,13 +523,13 @@
// The non-resizable activity should not be size compat because it is on a resizable task
// in multi-window mode.
- mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
assertFalse(activity.shouldUseSizeCompatMode());
// The non-resizable activity should not be size compat because the display support
// changing windowing mode from fullscreen to freeform.
- mStack.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
- mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+ mTask.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
assertFalse(activity.shouldUseSizeCompatMode());
}
@@ -783,7 +781,7 @@
assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity);
final Rect activityBounds = new Rect(mActivity.getBounds());
- mStack.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
+ mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
// App still in size compat, and the bounds don't change.
verify(mActivity, never()).clearSizeCompatMode();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 4bd8edd..28ba710 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -70,23 +70,22 @@
@RunWith(WindowTestRunner.class)
public class TaskDisplayAreaTests extends WindowTestsBase {
- private Task mPinnedStack;
+ private Task mPinnedTask;
@Before
public void setUp() throws Exception {
- mPinnedStack = createTaskStackOnDisplay(
+ mPinnedTask = createTaskStackOnDisplay(
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
// Stack should contain visible app window to be considered visible.
- final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
- assertFalse(mPinnedStack.isVisible());
+ assertFalse(mPinnedTask.isVisible());
final ActivityRecord pinnedApp = createNonAttachedActivityRecord(mDisplayContent);
- pinnedTask.addChild(pinnedApp, 0 /* addPos */);
- assertTrue(mPinnedStack.isVisible());
+ mPinnedTask.addChild(pinnedApp, 0 /* addPos */);
+ assertTrue(mPinnedTask.isVisible());
}
@After
public void tearDown() throws Exception {
- mPinnedStack.removeImmediately();
+ mPinnedTask.removeImmediately();
}
@Test
@@ -118,19 +117,19 @@
final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1);
final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2);
- final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
+ final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedTask);
assertThat(pinnedStackPos).isGreaterThan(stack2Pos);
assertThat(stack2Pos).isGreaterThan(stack1Pos);
- taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedStack, false);
+ taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedTask, false);
assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+ assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
- taskStackContainer.positionChildAt(1, mPinnedStack, false);
+ taskStackContainer.positionChildAt(1, mPinnedTask, false);
assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+ assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
}
@Test
@@ -141,16 +140,16 @@
final WindowContainer taskStackContainer = stack1.getParent();
final int stackPos = taskStackContainer.mChildren.indexOf(stack1);
- final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
+ final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedTask);
assertThat(pinnedStackPos).isGreaterThan(stackPos);
taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false);
assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+ assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false);
assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+ assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 75d2c51..1d498bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -35,6 +35,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -731,6 +732,39 @@
handle.inputFeatures);
}
+ @Test
+ public void testHasActiveVisibleWindow() {
+ final int uid = ActivityBuilder.DEFAULT_FAKE_UID;
+ mAtm.mActiveUids.onUidActive(uid, 0 /* any proc state */);
+
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app", uid);
+ app.mActivityRecord.setVisible(false);
+ app.mActivityRecord.setVisibility(false /* visible */, false /* deferHidingClient */);
+ assertFalse(mAtm.hasActiveVisibleWindow(uid));
+
+ app.mActivityRecord.setVisibility(true /* visible */, false /* deferHidingClient */);
+ assertTrue(mAtm.hasActiveVisibleWindow(uid));
+
+ // Make the activity invisible and add a visible toast. The uid should have no active
+ // visible window because toast can be misused by legacy app to bypass background check.
+ app.mActivityRecord.setVisibility(false /* visible */, false /* deferHidingClient */);
+ final WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay", uid);
+ final WindowState toast = createWindow(null, TYPE_TOAST, app.mToken, "toast", uid);
+ toast.onSurfaceShownChanged(true);
+ assertFalse(mAtm.hasActiveVisibleWindow(uid));
+
+ // Though starting window should belong to system. Make sure it is ignored to avoid being
+ // allow-list unexpectedly, see b/129563343.
+ final WindowState starting =
+ createWindow(null, TYPE_APPLICATION_STARTING, app.mToken, "starting", uid);
+ starting.onSurfaceShownChanged(true);
+ assertFalse(mAtm.hasActiveVisibleWindow(uid));
+
+ // Make the application overlay window visible. It should be a valid active visible window.
+ overlay.onSurfaceShownChanged(true);
+ assertTrue(mAtm.hasActiveVisibleWindow(uid));
+ }
+
@UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testNeedsRelativeLayeringToIme_notAttached() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ee1034c..8305381 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -637,6 +637,7 @@
* Builder for creating new activities.
*/
protected static class ActivityBuilder {
+ static final int DEFAULT_FAKE_UID = 12345;
// An id appended to the end of the component name to make it unique
private static int sCurrentActivityId = 0;
@@ -647,7 +648,7 @@
private Task mTask;
private String mProcessName = "name";
private String mAffinity;
- private int mUid = 12345;
+ private int mUid = DEFAULT_FAKE_UID;
private boolean mCreateTask = false;
private Task mParentTask;
private int mActivityFlags;
@@ -840,10 +841,13 @@
// to set it somewhere else since we can't mock resources.
doReturn(true).when(activity).occludesParent();
doReturn(true).when(activity).fillsParent();
+ mTask.addChild(activity);
if (mOnTop) {
+ // Move the task to front after activity added.
+ // Or {@link TaskDisplayArea#mPreferredTopFocusableStack} could be other stacks
+ // (e.g. home stack).
mTask.moveToFront("createActivity");
}
- mTask.addChild(activity);
// Make visible by default...
activity.setVisible(true);
}
@@ -854,7 +858,7 @@
} else {
wpc = new WindowProcessController(mService,
aInfo.applicationInfo, mProcessName, mUid,
- UserHandle.getUserId(12345), mock(Object.class),
+ UserHandle.getUserId(mUid), mock(Object.class),
mock(WindowProcessListener.class));
wpc.setThread(mock(IApplicationThread.class));
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 632ad4c..4bf93a2 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -60,7 +60,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
-import java.util.function.BiFunction;
/**
* Helper for {@link SoundTrigger} APIs. Supports two types of models:
@@ -118,8 +117,7 @@
private PowerSaveModeListener mPowerSaveModeListener;
- private final BiFunction<Integer, SoundTrigger.StatusListener, SoundTriggerModule>
- mModuleProvider;
+ private final SoundTriggerModuleProvider mModuleProvider;
// Handler to process call state changes will delay to allow time for the audio
// and sound trigger HALs to process the end of call notifications
@@ -128,12 +126,32 @@
private static final int MSG_CALL_STATE_CHANGED = 0;
private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000;
- SoundTriggerHelper(Context context,
- @NonNull BiFunction<Integer, SoundTrigger.StatusListener,
- SoundTriggerModule> moduleProvider) {
+ /**
+ * Provider interface for retrieving SoundTriggerModule instances
+ */
+ public interface SoundTriggerModuleProvider {
+ /**
+ * Populate module properties for all available modules
+ *
+ * @param modules List of ModuleProperties to be populated
+ * @return Status int 0 on success.
+ */
+ int listModuleProperties(@NonNull ArrayList<SoundTrigger.ModuleProperties> modules);
+
+ /**
+ * Get SoundTriggerModule based on {@link SoundTrigger.ModuleProperties#getId()}
+ *
+ * @param moduleId Module ID
+ * @param statusListener Client listener to be associated with the returned module
+ * @return Module associated with moduleId
+ */
+ SoundTriggerModule getModule(int moduleId, SoundTrigger.StatusListener statusListener);
+ }
+
+ SoundTriggerHelper(Context context, SoundTriggerModuleProvider moduleProvider) {
ArrayList <ModuleProperties> modules = new ArrayList<>();
mModuleProvider = moduleProvider;
- int status = SoundTrigger.listModules(modules);
+ int status = mModuleProvider.listModuleProperties(modules);
mContext = context;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -272,7 +290,7 @@
private int prepareForRecognition(ModelData modelData) {
if (mModule == null) {
- mModule = mModuleProvider.apply(mModuleProperties.getId(), this);
+ mModule = mModuleProvider.getModule(mModuleProperties.getId(), this);
if (mModule == null) {
Slog.w(TAG, "prepareForRecognition: cannot attach to sound trigger module");
return STATUS_ERROR;
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 2a5bfce..bd678fd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -51,6 +51,7 @@
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.hardware.soundtrigger.SoundTrigger.SoundModel;
+import android.hardware.soundtrigger.SoundTriggerModule;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioRecord;
@@ -219,9 +220,20 @@
Identity originatorIdentity = IdentityContext.getNonNull();
return new SoundTriggerHelper(mContext,
- (moduleId, listener) -> SoundTrigger.attachModuleAsMiddleman(moduleId, listener,
- null,
- middlemanIdentity, originatorIdentity));
+ new SoundTriggerHelper.SoundTriggerModuleProvider() {
+ @Override
+ public int listModuleProperties(ArrayList<ModuleProperties> modules) {
+ return SoundTrigger.listModulesAsMiddleman(modules, middlemanIdentity,
+ originatorIdentity);
+ }
+
+ @Override
+ public SoundTriggerModule getModule(int moduleId,
+ SoundTrigger.StatusListener statusListener) {
+ return SoundTrigger.attachModuleAsMiddleman(moduleId, statusListener, null,
+ middlemanIdentity, originatorIdentity);
+ }
+ });
}
class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java
index 08b6eda..b785037 100644
--- a/telephony/java/android/telephony/PhoneCapability.java
+++ b/telephony/java/android/telephony/PhoneCapability.java
@@ -29,6 +29,7 @@
* Define capability of a modem group. That is, the capabilities
* are shared between those modems defined by list of modem IDs.
*
+ * @hide
*/
public final class PhoneCapability implements Parcelable {
// Hardcoded default DSDS capability.
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java
index 95c69ba..8d49e15 100644
--- a/telephony/java/android/telephony/PhysicalChannelConfig.java
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.java
@@ -29,6 +29,10 @@
import java.util.Arrays;
import java.util.Objects;
+/**
+ * @hide
+ */
+@SystemApi
public final class PhysicalChannelConfig implements Parcelable {
// TODO(b/72993578) consolidate these enums in a central location.
@@ -82,7 +86,7 @@
private int mFrequencyRange;
/**
- * The absolute radio frequency channel number, {@link #CHANNEL_NUMBER_UNKNOWN} if unknown.
+ * The absolute radio frequency channel number, {@link CHANNEL_NUMBER_UNKNOWN} if unknown.
*/
private int mChannelNumber;
@@ -93,7 +97,7 @@
private int[] mContextIds;
/**
- * The physical cell identifier for this cell - PCI, PSC, {@link #PHYSICAL_CELL_ID_UNKNOWN}
+ * The physical cell identifier for this cell - PCI, PSC, {@link PHYSICAL_CELL_ID_UNKNOWN}
* if unknown.
*/
private int mPhysicalCellId;
@@ -149,7 +153,7 @@
/**
* @return the absolute radio frequency channel number for this physical channel,
- * {@link #CHANNEL_NUMBER_UNKNOWN} if unknown.
+ * {@link CHANNEL_NUMBER_UNKNOWN} if unknown.
*/
public int getChannelNumber() {
return mChannelNumber;
@@ -165,7 +169,7 @@
* In 5G RAN, this value is physical layer cell identity. The range is [0, 1007].
* Reference: 3GPP TS 38.211 section 7.4.2.1.
*
- * @return the physical cell identifier for this cell, {@link #PHYSICAL_CELL_ID_UNKNOWN}
+ * @return the physical cell identifier for this cell, {@link PHYSICAL_CELL_ID_UNKNOWN}
* if {@link android.telephony.CellInfo#UNAVAILABLE}.
*/
@IntRange(from = 0, to = 1007)
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 964cf76..572aed3 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -447,6 +447,9 @@
* <code>RESULT_RIL_NO_RESOURCES</code><br>
* <code>RESULT_RIL_CANCELLED</code><br>
* <code>RESULT_RIL_SIM_ABSENT</code><br>
+ * <code>RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+ * <code>RESULT_RIL_ACCESS_BARRED</code><br>
+ * <code>RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
* the sentIntent may include the extra "errorCode" containing a radio technology specific
* value, generally only useful for troubleshooting.<br>
@@ -561,6 +564,9 @@
* <code>RESULT_RIL_NO_RESOURCES</code><br>
* <code>RESULT_RIL_CANCELLED</code><br>
* <code>RESULT_RIL_SIM_ABSENT</code><br>
+ * <code>RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+ * <code>RESULT_RIL_ACCESS_BARRED</code><br>
+ * <code>RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
* the sentIntent may include the extra "errorCode" containing a radio technology specific
* value, generally only useful for troubleshooting.<br>
@@ -962,6 +968,9 @@
* <code>RESULT_RIL_NO_RESOURCES</code><br>
* <code>RESULT_RIL_CANCELLED</code><br>
* <code>RESULT_RIL_SIM_ABSENT</code><br>
+ * <code>RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+ * <code>RESULT_RIL_ACCESS_BARRED</code><br>
+ * <code>RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
* the sentIntent may include the extra "errorCode" containing a radio technology specific
* value, generally only useful for troubleshooting.<br>
@@ -1220,6 +1229,9 @@
* <code>RESULT_RIL_NO_RESOURCES</code><br>
* <code>RESULT_RIL_CANCELLED</code><br>
* <code>RESULT_RIL_SIM_ABSENT</code><br>
+ * <code>RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+ * <code>RESULT_RIL_ACCESS_BARRED</code><br>
+ * <code>RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
* the sentIntent may include the extra "errorCode" containing a radio technology specific
* value, generally only useful for troubleshooting.<br>
@@ -1419,6 +1431,9 @@
* <code>RESULT_RIL_NO_RESOURCES</code><br>
* <code>RESULT_RIL_CANCELLED</code><br>
* <code>RESULT_RIL_SIM_ABSENT</code><br>
+ * <code>RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+ * <code>RESULT_RIL_ACCESS_BARRED</code><br>
+ * <code>RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
* the sentIntent may include the extra "errorCode" containing a radio technology specific
* value, generally only useful for troubleshooting.<br>
@@ -2298,7 +2313,10 @@
RESULT_RIL_OPERATION_NOT_ALLOWED,
RESULT_RIL_NO_RESOURCES,
RESULT_RIL_CANCELLED,
- RESULT_RIL_SIM_ABSENT
+ RESULT_RIL_SIM_ABSENT,
+ RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED,
+ RESULT_RIL_ACCESS_BARRED,
+ RESULT_RIL_BLOCKED_DUE_TO_CALL
})
@Retention(RetentionPolicy.SOURCE)
public @interface Result {}
@@ -2563,6 +2581,21 @@
*/
public static final int RESULT_RIL_SIM_ABSENT = 120;
+ /**
+ * 1X voice and SMS are not allowed simulteneously.
+ */
+ public static final int RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED = 121;
+
+ /**
+ * Access is barred.
+ */
+ public static final int RESULT_RIL_ACCESS_BARRED = 122;
+
+ /**
+ * SMS is blocked due to call control, e.g., resource unavailable in the SMR entity.
+ */
+ public static final int RESULT_RIL_BLOCKED_DUE_TO_CALL = 123;
+
// SMS receiving results sent as a "result" extra in {@link Intents.SMS_REJECTED_ACTION}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 683f200..ae2d1ad 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -123,7 +123,6 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -155,7 +154,6 @@
public class TelephonyManager {
private static final String TAG = "TelephonyManager";
- private TelephonyRegistryManager mTelephonyRegistryMgr;
/**
* To expand the error codes for {@link TelephonyManager#updateAvailableNetworks} and
* {@link TelephonyManager#setPreferredOpportunisticDataSubscription}.
@@ -5595,22 +5593,12 @@
* @param events The telephony state(s) of interest to the listener,
* as a bitwise-OR combination of {@link PhoneStateListener}
* LISTEN_ flags.
- * @deprecated Use {@link #registerPhoneStateListener(Executor, PhoneStateListener)}.
+ * @deprecated use {@link #listen(long, PhoneStateListener) instead due to the event number
+ * limit increased to 64.
*/
@Deprecated
public void listen(PhoneStateListener listener, int events) {
- boolean notifyNow = getITelephony() != null;
- mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
- if (mTelephonyRegistryMgr != null) {
- if (events != PhoneStateListener.LISTEN_NONE) {
- mTelephonyRegistryMgr.registerPhoneStateListenerWithEvents(getSubId(),
- getOpPackageName(), getAttributionTag(), listener, events, notifyNow);
- } else {
- unregisterPhoneStateListener(listener);
- }
- } else {
- throw new IllegalStateException("telephony service is null.");
- }
+ listen(events, listener);
}
/**
@@ -5646,21 +5634,18 @@
* LISTEN_ flags.
* @param listener The {@link PhoneStateListener} object to register
* (or unregister)
- * @deprecated Use {@link #registerPhoneStateListener(Executor, PhoneStateListener)}.
*/
- @Deprecated
public void listen(long events, @NonNull PhoneStateListener listener) {
- mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
- if (mTelephonyRegistryMgr != null) {
- if (events != PhoneStateListener.LISTEN_NONE) {
- mTelephonyRegistryMgr.registerPhoneStateListenerWithEvents(getSubId(),
- getOpPackageName(), getAttributionTag(), listener,
- Long.valueOf(events).intValue(), getITelephony() != null);
- } else {
- unregisterPhoneStateListener(listener);
- }
+ if (mContext == null) return;
+ boolean notifyNow = (getITelephony() != null);
+ TelephonyRegistryManager telephonyRegistry =
+ (TelephonyRegistryManager)
+ mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
+ if (telephonyRegistry != null) {
+ telephonyRegistry.listenForSubscriber(mSubId, getOpPackageName(), getAttributionTag(),
+ listener, events, notifyNow);
} else {
- throw new IllegalStateException("telephony service is null.");
+ Rlog.w(TAG, "telephony registry not ready.");
}
}
@@ -13215,6 +13200,37 @@
}
/**
+ * Get which bands the modem's background scan is acting on, specified by
+ * {@link #setSystemSelectionChannels}.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ *
+ * @return a list of {@link RadioAccessSpecifier}, or an empty list if no bands are specified.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @NonNull List<RadioAccessSpecifier> getSystemSelectionChannels() {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.getSystemSelectionChannels(getSubId());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ if (!isSystemProcess()) {
+ ex.rethrowAsRuntimeException();
+ }
+ }
+ return new ArrayList<>();
+ }
+
+ /**
* Verifies whether the input MCC/MNC and MVNO correspond to the current carrier.
*
* @param mccmnc the carrier's mccmnc that you want to match
@@ -14193,70 +14209,4 @@
return Collections.emptyList();
}
-
- /**
- * Registers a listener object to receive notification of changes
- * in specified telephony states.
- * <p>
- * To register a listener, pass a {@link PhoneStateListener} which implements
- * interfaces of events. For example,
- * FakeServiceStateChangedListener extends {@link PhoneStateListener} implements
- * {@link PhoneStateListener.ServiceStateChangedListener}.
- *
- * At registration, and when a specified telephony state changes, the telephony manager invokes
- * the appropriate callback method on the listener object and passes the current (updated)
- * values.
- * <p>
- *
- * If this TelephonyManager object has been created with {@link #createForSubscriptionId},
- * applies to the given subId. Otherwise, applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}. To listen events for multiple subIds,
- * pass a separate listener object to each TelephonyManager object created with
- * {@link #createForSubscriptionId}.
- *
- * Note: if you call this method while in the middle of a binder transaction, you <b>must</b>
- * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A
- * {@link SecurityException} will be thrown otherwise.
- *
- * This API should be used sparingly -- large numbers of listeners will cause system
- * instability. If a process has registered too many listeners without unregistering them, it
- * may encounter an {@link IllegalStateException} when trying to register more listeners.
- *
- * @param executor The executor of where the callback will execute.
- * @param listener The {@link PhoneStateListener} object to register.
- */
- public void registerPhoneStateListener(@NonNull @CallbackExecutor Executor executor,
- @NonNull PhoneStateListener listener) {
- if (executor == null || listener == null) {
- throw new IllegalStateException("telephony service is null.");
- }
- mTelephonyRegistryMgr = (TelephonyRegistryManager)
- mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
- if (mTelephonyRegistryMgr != null) {
- mTelephonyRegistryMgr.registerPhoneStateListener(executor, getSubId(),
- getOpPackageName(), getAttributionTag(), listener, getITelephony() != null);
- } else {
- throw new IllegalStateException("telephony service is null.");
- }
- }
-
- /**
- * Unregister an existing {@link PhoneStateListener}.
- *
- * @param listener The {@link PhoneStateListener} object to unregister.
- */
- public void unregisterPhoneStateListener(@NonNull PhoneStateListener listener) {
-
- if (mContext == null) {
- throw new IllegalStateException("telephony service is null.");
- }
-
- mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
- if (mTelephonyRegistryMgr != null) {
- mTelephonyRegistryMgr.unregisterPhoneStateListener(getSubId(), getOpPackageName(),
- getAttributionTag(), listener, getITelephony() != null);
- } else {
- throw new IllegalStateException("telephony service is null.");
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java b/telephony/java/android/telephony/data/ApnThrottleStatus.aidl
similarity index 66%
rename from packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
rename to telephony/java/android/telephony/data/ApnThrottleStatus.aidl
index d7a3af0..46bc4ab 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
+++ b/telephony/java/android/telephony/data/ApnThrottleStatus.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -11,13 +11,10 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.classifier;
+/** @hide */
+package android.telephony.data;
-public class PointerCountEvaluator {
- public static float evaluate(int value) {
- return (value - 1) * (value - 1);
- }
-}
+parcelable ApnThrottleStatus;
diff --git a/telephony/java/android/telephony/data/ApnThrottleStatus.java b/telephony/java/android/telephony/data/ApnThrottleStatus.java
new file mode 100644
index 0000000..51461d1
--- /dev/null
+++ b/telephony/java/android/telephony/data/ApnThrottleStatus.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.data;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.Annotation;
+
+import java.util.Objects;
+
+/**
+ * Status information regarding the throttle status of an APN type.
+ *
+ * @hide
+ */
+@SystemApi
+public final class ApnThrottleStatus implements Parcelable {
+ /**
+ * The APN type is not throttled.
+ */
+ public static final int THROTTLE_TYPE_NONE = 1;
+
+ /**
+ * The APN type is throttled until {@link android.os.SystemClock#elapsedRealtime()}
+ * has reached {@link ApnThrottleStatus#getThrottleExpiryTimeMillis}
+ */
+ public static final int THROTTLE_TYPE_ELAPSED_TIME = 2;
+
+ /** {@hide} */
+ @IntDef(flag = true, prefix = {"THROTTLE_TYPE_"}, value = {
+ ApnThrottleStatus.THROTTLE_TYPE_NONE,
+ ApnThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME,
+ })
+ public @interface ThrottleType {
+ }
+
+ /**
+ * The framework will not retry the APN type.
+ */
+ public static final int RETRY_TYPE_NONE = 1;
+
+ /**
+ * The next time the framework retries, it will attempt to establish a new connection.
+ */
+ public static final int RETRY_TYPE_NEW_CONNECTION = 2;
+
+ /**
+ * The next time the framework retires, it will retry to handover.
+ */
+ public static final int RETRY_TYPE_HANDOVER = 3;
+
+ /** {@hide} */
+ @IntDef(flag = true, prefix = {"RETRY_TYPE_"}, value = {
+ ApnThrottleStatus.RETRY_TYPE_NONE,
+ ApnThrottleStatus.RETRY_TYPE_NEW_CONNECTION,
+ ApnThrottleStatus.RETRY_TYPE_HANDOVER,
+ })
+ public @interface RetryType {
+ }
+
+ private final int mSlotIndex;
+ private final @AccessNetworkConstants.TransportType int mTransportType;
+ private final @Annotation.ApnType int mApnType;
+ private final long mThrottleExpiryTimeMillis;
+ private final @RetryType int mRetryType;
+ private final @ThrottleType int mThrottleType;
+
+ /**
+ * The slot index that the status applies to.
+ *
+ * @return the slot index
+ */
+ public int getSlotIndex() {
+ return mSlotIndex;
+ }
+
+ /**
+ * The type of transport that the status applies to.
+ *
+ * @return the transport type
+ */
+ @AccessNetworkConstants.TransportType
+ public int getTransportType() {
+ return mTransportType;
+ }
+
+ /**
+ * The APN type that the status applies to.
+ *
+ * @return the apn type
+ */
+ @Annotation.ApnType
+ public int getApnType() {
+ return mApnType;
+ }
+
+ /**
+ * The type of throttle applied to the APN type.
+ *
+ * @return the throttle type
+ */
+ @ThrottleType
+ public int getThrottleType() {
+ return mThrottleType;
+ }
+
+ /**
+ * Indicates the type of request that the framework will make the next time it retries
+ * to call {@link IDataService#setupDataCall}.
+ *
+ * @return the retry type
+ */
+ @RetryType
+ public int getRetryType() {
+ return mRetryType;
+ }
+
+ /**
+ * Gets the time at which the throttle expires. The value is based off of
+ * {@link SystemClock#elapsedRealtime}.
+ *
+ * This value only applies when the throttle type is set to
+ * {@link ApnThrottleStatus#THROTTLE_TYPE_ELAPSED_TIME}.
+ *
+ * A value of {@link Long#MAX_VALUE} implies that the APN type is throttled indefinitely.
+ *
+ * @return the time at which the throttle expires
+ */
+ @ElapsedRealtimeLong
+ public long getThrottleExpiryTimeMillis() {
+ return mThrottleExpiryTimeMillis;
+ }
+
+ private ApnThrottleStatus(int slotIndex,
+ @AccessNetworkConstants.TransportType int transportType,
+ @Annotation.ApnType int apnTypes,
+ @ThrottleType int throttleType,
+ long throttleExpiryTimeMillis,
+ @RetryType int retryType) {
+ mSlotIndex = slotIndex;
+ mTransportType = transportType;
+ mApnType = apnTypes;
+ mThrottleType = throttleType;
+ mThrottleExpiryTimeMillis = throttleExpiryTimeMillis;
+ mRetryType = retryType;
+ }
+
+ private ApnThrottleStatus(@NonNull Parcel source) {
+ mSlotIndex = source.readInt();
+ mTransportType = source.readInt();
+ mApnType = source.readInt();
+ mThrottleExpiryTimeMillis = source.readLong();
+ mRetryType = source.readInt();
+ mThrottleType = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSlotIndex);
+ dest.writeInt(mTransportType);
+ dest.writeInt(mApnType);
+ dest.writeLong(mThrottleExpiryTimeMillis);
+ dest.writeInt(mRetryType);
+ dest.writeInt(mThrottleType);
+ }
+
+ public static final @NonNull Parcelable.Creator<ApnThrottleStatus> CREATOR =
+ new Parcelable.Creator<ApnThrottleStatus>() {
+ @Override
+ public ApnThrottleStatus createFromParcel(@NonNull Parcel source) {
+ return new ApnThrottleStatus(source);
+ }
+
+ @Override
+ public ApnThrottleStatus[] newArray(int size) {
+ return new ApnThrottleStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSlotIndex, mApnType, mRetryType, mThrottleType,
+ mThrottleExpiryTimeMillis, mTransportType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ } else if (obj instanceof ApnThrottleStatus) {
+ ApnThrottleStatus other = (ApnThrottleStatus) obj;
+ return this.mSlotIndex == other.mSlotIndex
+ && this.mApnType == other.mApnType
+ && this.mRetryType == other.mRetryType
+ && this.mThrottleType == other.mThrottleType
+ && this.mThrottleExpiryTimeMillis == other.mThrottleExpiryTimeMillis
+ && this.mTransportType == other.mTransportType;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ApnThrottleStatus{"
+ + "mSlotIndex=" + mSlotIndex
+ + ", mTransportType=" + mTransportType
+ + ", mApnType=" + ApnSetting.getApnTypeString(mApnType)
+ + ", mThrottleExpiryTimeMillis=" + mThrottleExpiryTimeMillis
+ + ", mRetryType=" + mRetryType
+ + ", mThrottleType=" + mThrottleType
+ + '}';
+ }
+
+ /**
+ * Provides a convenient way to set the fields of an {@link ApnThrottleStatus} when creating a
+ * new instance.
+ *
+ * <p>The example below shows how you might create a new {@code ApnThrottleStatus}:
+ *
+ * <pre><code>
+ *
+ * DataCallResponseApnThrottleStatus = new ApnThrottleStatus.Builder()
+ * .setSlotIndex(1)
+ * .setApnType({@link ApnSetting#TYPE_EMERGENCY})
+ * .setNoThrottle()
+ * .setRetryType({@link ApnThrottleStatus#RETRY_TYPE_NEW_CONNECTION})
+ * .build();
+ * </code></pre>
+ */
+ public static final class Builder {
+ private int mSlotIndex;
+ private @AccessNetworkConstants.TransportType int mTransportType;
+ private @Annotation.ApnType int mApnType;
+ private long mThrottleExpiryTimeMillis;
+ private @RetryType int mRetryType;
+ private @ThrottleType int mThrottleType;
+ public static final long NO_THROTTLE_EXPIRY_TIME =
+ DataCallResponse.RETRY_DURATION_UNDEFINED;
+
+ /**
+ * Default constructor for the Builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Set the slot index.
+ *
+ * @param slotIndex the slot index.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setSlotIndex(int slotIndex) {
+ this.mSlotIndex = slotIndex;
+ return this;
+ }
+
+ /**
+ * Set the transport type.
+ *
+ * @param transportType the transport type.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setTransportType(@AccessNetworkConstants.TransportType
+ int transportType) {
+ this.mTransportType = transportType;
+ return this;
+ }
+
+ /**
+ * Set the APN type.
+ *
+ * @param apnType the APN type.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setApnType(@Annotation.ApnType int apnType) {
+ this.mApnType = apnType;
+ return this;
+ }
+
+ /**
+ * Sets the time at which the throttle will expire. The value is based off of
+ * {@link SystemClock#elapsedRealtime}.
+ *
+ * When setting this value, the throttle type is set to
+ * {@link ApnThrottleStatus#THROTTLE_TYPE_ELAPSED_TIME}.
+ *
+ * A value of {@link Long#MAX_VALUE} implies that the APN type is throttled indefinitely.
+ *
+ * @param throttleExpiryTimeMillis The elapsed time at which the throttle expires.
+ * Throws {@link IllegalArgumentException} for values less
+ * than 0.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setThrottleExpiryTimeMillis(
+ @ElapsedRealtimeLong long throttleExpiryTimeMillis) {
+ if (throttleExpiryTimeMillis >= 0) {
+ this.mThrottleExpiryTimeMillis = throttleExpiryTimeMillis;
+ this.mThrottleType = THROTTLE_TYPE_ELAPSED_TIME;
+ } else {
+ throw new IllegalArgumentException("throttleExpiryTimeMillis must be greater than "
+ + "or equal to 0");
+ }
+ return this;
+ }
+
+ /**
+ * Sets the status of the APN type as not being throttled.
+ *
+ * When setting this value, the throttle type is set to
+ * {@link ApnThrottleStatus#THROTTLE_TYPE_NONE} and the expiry time is set to
+ * {@link Builder#NO_THROTTLE_EXPIRY_TIME}.
+ *
+ * @return The same instance of the builder.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public Builder setNoThrottle() {
+ mThrottleType = THROTTLE_TYPE_NONE;
+ mThrottleExpiryTimeMillis = NO_THROTTLE_EXPIRY_TIME;
+ return this;
+ }
+
+ /**
+ * Set the type of request that the framework will make the next time it retries
+ * to call {@link IDataService#setupDataCall}.
+ *
+ * @param retryType the type of request
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setRetryType(@RetryType int retryType) {
+ this.mRetryType = retryType;
+ return this;
+ }
+
+ /**
+ * Build the {@link ApnThrottleStatus}
+ *
+ * @return the {@link ApnThrottleStatus} object
+ */
+ @NonNull
+ public ApnThrottleStatus build() {
+ return new ApnThrottleStatus(
+ mSlotIndex,
+ mTransportType,
+ mApnType,
+ mThrottleType,
+ mThrottleExpiryTimeMillis,
+ mRetryType);
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index aae7713..090c970 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -135,6 +135,8 @@
private final int mMtuV6;
private final @HandoverFailureMode int mHandoverFailureMode;
private final int mPduSessionId;
+ private final Qos mDefaultQos;
+ private final List<QosSession> mQosSessions;
/**
* @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error.
@@ -183,6 +185,8 @@
mMtu = mMtuV4 = mMtuV6 = mtu;
mHandoverFailureMode = HANDOVER_FAILURE_MODE_LEGACY;
mPduSessionId = PDU_SESSION_ID_NOT_SET;
+ mDefaultQos = null;
+ mQosSessions = new ArrayList<>();
}
private DataCallResponse(@DataFailureCause int cause, long suggestedRetryTime, int id,
@@ -190,7 +194,8 @@
@Nullable String interfaceName, @Nullable List<LinkAddress> addresses,
@Nullable List<InetAddress> dnsAddresses, @Nullable List<InetAddress> gatewayAddresses,
@Nullable List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6,
- @HandoverFailureMode int handoverFailureMode, int pduSessionId) {
+ @HandoverFailureMode int handoverFailureMode, int pduSessionId,
+ @Nullable Qos defaultQos, @Nullable List<QosSession> qosSessions) {
mCause = cause;
mSuggestedRetryTime = suggestedRetryTime;
mId = id;
@@ -210,6 +215,8 @@
mMtuV6 = mtuV6;
mHandoverFailureMode = handoverFailureMode;
mPduSessionId = pduSessionId;
+ mDefaultQos = defaultQos;
+ mQosSessions = qosSessions;
}
/** @hide */
@@ -234,6 +241,9 @@
mMtuV6 = source.readInt();
mHandoverFailureMode = source.readInt();
mPduSessionId = source.readInt();
+ mDefaultQos = source.readParcelable(Qos.class.getClassLoader());
+ mQosSessions = new ArrayList<>();
+ source.readList(mQosSessions, QosSession.class.getClassLoader());
}
/**
@@ -357,6 +367,28 @@
return mPduSessionId;
}
+ /**
+ * @return default QOS of the data call received from the network
+ *
+ * @hide
+ */
+
+ @Nullable
+ public Qos getDefaultQos() {
+ return mDefaultQos;
+ }
+
+ /**
+ * @return All the dedicated bearer QOS sessions of the data call received from the network
+ *
+ * @hide
+ */
+
+ @NonNull
+ public List<QosSession> getQosSessions() {
+ return mQosSessions;
+ }
+
@NonNull
@Override
public String toString() {
@@ -377,6 +409,8 @@
.append(" mtuV6=").append(getMtuV6())
.append(" handoverFailureMode=").append(getHandoverFailureMode())
.append(" pduSessionId=").append(getPduSessionId())
+ .append(" defaultQos=").append(mDefaultQos)
+ .append(" qosSessions=").append(mQosSessions)
.append("}");
return sb.toString();
}
@@ -390,12 +424,22 @@
}
DataCallResponse other = (DataCallResponse) o;
- return this.mCause == other.mCause
- && this.mSuggestedRetryTime == other.mSuggestedRetryTime
- && this.mId == other.mId
- && this.mLinkStatus == other.mLinkStatus
- && this.mProtocolType == other.mProtocolType
- && this.mInterfaceName.equals(other.mInterfaceName)
+
+ final boolean isQosSame = (mDefaultQos == null || other.mDefaultQos == null) ?
+ mDefaultQos == other.mDefaultQos :
+ mDefaultQos.equals(other.mDefaultQos);
+
+ final boolean isQosSessionsSame = (mQosSessions == null || mQosSessions == null) ?
+ mQosSessions == other.mQosSessions :
+ mQosSessions.size() == other.mQosSessions.size()
+ && mQosSessions.containsAll(other.mQosSessions);
+
+ return mCause == other.mCause
+ && mSuggestedRetryTime == other.mSuggestedRetryTime
+ && mId == other.mId
+ && mLinkStatus == other.mLinkStatus
+ && mProtocolType == other.mProtocolType
+ && mInterfaceName.equals(other.mInterfaceName)
&& mAddresses.size() == other.mAddresses.size()
&& mAddresses.containsAll(other.mAddresses)
&& mDnsAddresses.size() == other.mDnsAddresses.size()
@@ -408,14 +452,17 @@
&& mMtuV4 == other.mMtuV4
&& mMtuV6 == other.mMtuV6
&& mHandoverFailureMode == other.mHandoverFailureMode
- && mPduSessionId == other.mPduSessionId;
+ && mPduSessionId == other.mPduSessionId
+ && isQosSame
+ && isQosSessionsSame;
}
@Override
public int hashCode() {
return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType,
mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses,
- mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId);
+ mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, mDefaultQos,
+ mQosSessions);
}
@Override
@@ -440,6 +487,16 @@
dest.writeInt(mMtuV6);
dest.writeInt(mHandoverFailureMode);
dest.writeInt(mPduSessionId);
+ if (mDefaultQos != null) {
+ if (mDefaultQos.getType() == Qos.QOS_TYPE_EPS) {
+ dest.writeParcelable((EpsQos) mDefaultQos, flags);
+ } else {
+ dest.writeParcelable((NrQos) mDefaultQos, flags);
+ }
+ } else {
+ dest.writeParcelable(null, flags);
+ }
+ dest.writeList(mQosSessions);
}
public static final @android.annotation.NonNull Parcelable.Creator<DataCallResponse> CREATOR =
@@ -519,6 +576,10 @@
private int mPduSessionId = PDU_SESSION_ID_NOT_SET;
+ private Qos mDefaultQos;
+
+ private List<QosSession> mQosSessions = new ArrayList<>();
+
/**
* Default constructor for Builder.
*/
@@ -713,6 +774,35 @@
}
/**
+ * Set the default QOS for this data connection.
+ *
+ * @param defaultQos QOS (Quality Of Service) received from network.
+ *
+ * @return The same instance of the builder.
+ *
+ * @hide
+ */
+ public @NonNull Builder setDefaultQos(@Nullable Qos defaultQos) {
+ mDefaultQos = defaultQos;
+ return this;
+ }
+
+ /**
+ * Set the dedicated bearer QOS sessions for this data connection.
+ *
+ * @param qosSessions Dedicated bearer QOS (Quality Of Service) sessions received
+ * from network.
+ *
+ * @return The same instance of the builder.
+ *
+ * @hide
+ */
+ public @NonNull Builder setQosSessions(@NonNull List<QosSession> qosSessions) {
+ mQosSessions = qosSessions;
+ return this;
+ }
+
+ /**
* Build the DataCallResponse.
*
* @return the DataCallResponse object.
@@ -720,7 +810,8 @@
public @NonNull DataCallResponse build() {
return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus,
mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses,
- mPcscfAddresses, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId);
+ mPcscfAddresses, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId,
+ mDefaultQos, mQosSessions);
}
}
}
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 7768597..2ec9651 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -107,6 +107,9 @@
private static final int DATA_SERVICE_INDICATION_DATA_CALL_LIST_CHANGED = 11;
private static final int DATA_SERVICE_REQUEST_START_HANDOVER = 12;
private static final int DATA_SERVICE_REQUEST_CANCEL_HANDOVER = 13;
+ private static final int DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED = 14;
+ private static final int DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED = 15;
+ private static final int DATA_SERVICE_INDICATION_APN_UNTHROTTLED = 16;
private final HandlerThread mHandlerThread;
@@ -129,6 +132,8 @@
private final List<IDataServiceCallback> mDataCallListChangedCallbacks = new ArrayList<>();
+ private final List<IDataServiceCallback> mApnUnthrottledCallbacks = new ArrayList<>();
+
/**
* Constructor
* @param slotIndex SIM slot index the data service provider associated with.
@@ -326,6 +331,19 @@
}
}
+ private void registerForApnUnthrottled(IDataServiceCallback callback) {
+ synchronized (mApnUnthrottledCallbacks) {
+ mApnUnthrottledCallbacks.add(callback);
+ }
+ }
+
+ private void unregisterForApnUnthrottled(IDataServiceCallback callback) {
+ synchronized (mApnUnthrottledCallbacks) {
+ mApnUnthrottledCallbacks.remove(callback);
+ }
+ }
+
+
/**
* Notify the system that current data call list changed. Data service must invoke this
* method whenever there is any data call status changed.
@@ -343,6 +361,21 @@
}
/**
+ * Notify the system that a given APN was unthrottled.
+ *
+ * @param apn Access Point Name defined by the carrier.
+ */
+ public final void notifyApnUnthrottled(@NonNull String apn) {
+ synchronized (mApnUnthrottledCallbacks) {
+ for (IDataServiceCallback callback : mApnUnthrottledCallbacks) {
+ mHandler.obtainMessage(DATA_SERVICE_INDICATION_APN_UNTHROTTLED,
+ mSlotIndex, 0, new ApnUnthrottledIndication(apn,
+ callback)).sendToTarget();
+ }
+ }
+ }
+
+ /**
* Called when the instance of data service is destroyed (e.g. got unbind or binder died)
* or when the data service provider is removed. The extended class should implement this
* method to perform cleanup works.
@@ -429,6 +462,16 @@
}
}
+ private static final class ApnUnthrottledIndication {
+ public final String apn;
+ public final IDataServiceCallback callback;
+ ApnUnthrottledIndication(String apn,
+ IDataServiceCallback callback) {
+ this.apn = apn;
+ this.callback = callback;
+ }
+ }
+
private class DataServiceHandler extends Handler {
DataServiceHandler(Looper looper) {
@@ -544,6 +587,26 @@
(cReq.callback != null)
? new DataServiceCallback(cReq.callback) : null);
break;
+ case DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED:
+ if (serviceProvider == null) break;
+ serviceProvider.registerForApnUnthrottled((IDataServiceCallback) message.obj);
+ break;
+ case DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED:
+ if (serviceProvider == null) break;
+ callback = (IDataServiceCallback) message.obj;
+ serviceProvider.unregisterForApnUnthrottled(callback);
+ break;
+ case DATA_SERVICE_INDICATION_APN_UNTHROTTLED:
+ if (serviceProvider == null) break;
+ ApnUnthrottledIndication apnUnthrottledIndication =
+ (ApnUnthrottledIndication) message.obj;
+ try {
+ apnUnthrottledIndication.callback
+ .onApnUnthrottled(apnUnthrottledIndication.apn);
+ } catch (RemoteException e) {
+ loge("Failed to call onApnUnthrottled. " + e);
+ }
+ break;
}
}
}
@@ -695,6 +758,26 @@
mHandler.obtainMessage(DATA_SERVICE_REQUEST_CANCEL_HANDOVER,
slotIndex, 0, req).sendToTarget();
}
+
+ @Override
+ public void registerForUnthrottleApn(int slotIndex, IDataServiceCallback callback) {
+ if (callback == null) {
+ loge("registerForUnthrottleApn: callback is null");
+ return;
+ }
+ mHandler.obtainMessage(DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED, slotIndex,
+ 0, callback).sendToTarget();
+ }
+
+ @Override
+ public void unregisterForUnthrottleApn(int slotIndex, IDataServiceCallback callback) {
+ if (callback == null) {
+ loge("uregisterForUnthrottleApn: callback is null");
+ return;
+ }
+ mHandler.obtainMessage(DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED,
+ slotIndex, 0, callback).sendToTarget();
+ }
}
private void log(String s) {
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index eef0e01..52bf15f 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -233,7 +233,7 @@
*/
@NonNull
public static String resultCodeToString(@DataServiceCallback.ResultCode int resultCode) {
- switch(resultCode) {
+ switch (resultCode) {
case RESULT_SUCCESS:
return "RESULT_SUCCESS";
case RESULT_ERROR_UNSUPPORTED:
@@ -248,4 +248,22 @@
return "Missing case for result code=" + resultCode;
}
}
+
+ /**
+ * Indicates that the specified APN is no longer throttled.
+ *
+ * @param apn Access Point Name defined by the carrier.
+ */
+ public void onApnUnthrottled(@NonNull String apn) {
+ if (mCallback != null) {
+ try {
+ if (DBG) Rlog.d(TAG, "onApnUnthrottled");
+ mCallback.onApnUnthrottled(apn);
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "onApnUnthrottled: remote exception", e);
+ }
+ } else {
+ Rlog.e(TAG, "onApnUnthrottled: callback is null!");
+ }
+ }
}
diff --git a/telephony/java/android/telephony/data/EpsQos.java b/telephony/java/android/telephony/data/EpsQos.java
new file mode 100644
index 0000000..ad43068
--- /dev/null
+++ b/telephony/java/android/telephony/data/EpsQos.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.data;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+
+/**
+ * Class that stores information specific to NR QOS.
+ *
+ * @hide
+ */
+public final class EpsQos extends Qos implements Parcelable {
+
+ int qosClassId;
+
+ public EpsQos() {
+ super(Qos.QOS_TYPE_EPS,
+ new android.hardware.radio.V1_6.QosBandwidth(),
+ new android.hardware.radio.V1_6.QosBandwidth());
+ }
+
+ public EpsQos(@NonNull android.hardware.radio.V1_6.EpsQos qos) {
+ super(Qos.QOS_TYPE_EPS, qos.downlink, qos.uplink);
+ qosClassId = qos.qci;
+ }
+
+ private EpsQos(Parcel source) {
+ super(source);
+ qosClassId = source.readInt();
+ }
+
+ public static @NonNull EpsQos createFromParcelBody(@NonNull Parcel in) {
+ return new EpsQos(in);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(Qos.QOS_TYPE_EPS, dest, flags);
+ dest.writeInt(qosClassId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), qosClassId);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o == null || !(o instanceof EpsQos)) {
+ return false;
+ }
+
+ EpsQos other = (EpsQos) o;
+
+ return this.qosClassId == other.qosClassId
+ && super.equals(other);
+ }
+
+ @Override
+ public String toString() {
+ return "EpsQos {"
+ + " qosClassId=" + qosClassId
+ + " downlink=" + downlink
+ + " uplink=" + uplink + "}";
+ }
+
+ public static final @NonNull Parcelable.Creator<EpsQos> CREATOR =
+ new Parcelable.Creator<EpsQos>() {
+ @Override
+ public EpsQos createFromParcel(Parcel source) {
+ return new EpsQos(source);
+ }
+
+ @Override
+ public EpsQos[] newArray(int size) {
+ return new EpsQos[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/data/IDataService.aidl b/telephony/java/android/telephony/data/IDataService.aidl
index 33226fe..3f1f033 100644
--- a/telephony/java/android/telephony/data/IDataService.aidl
+++ b/telephony/java/android/telephony/data/IDataService.aidl
@@ -40,4 +40,6 @@
void unregisterForDataCallListChanged(int slotId, IDataServiceCallback callback);
void startHandover(int slotId, int cid, IDataServiceCallback callback);
void cancelHandover(int slotId, int cid, IDataServiceCallback callback);
+ void registerForUnthrottleApn(int slotIndex, IDataServiceCallback callback);
+ void unregisterForUnthrottleApn(int slotIndex, IDataServiceCallback callback);
}
diff --git a/telephony/java/android/telephony/data/IDataServiceCallback.aidl b/telephony/java/android/telephony/data/IDataServiceCallback.aidl
index d296e7b..9cc2fea 100644
--- a/telephony/java/android/telephony/data/IDataServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IDataServiceCallback.aidl
@@ -32,4 +32,5 @@
void onDataCallListChanged(in List<DataCallResponse> dataCallList);
void onHandoverStarted(int result);
void onHandoverCancelled(int result);
+ void onApnUnthrottled(in String apn);
}
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
index 3bf09bc..2904082 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
@@ -17,6 +17,7 @@
package android.telephony.data;
import android.telephony.data.IQualifiedNetworksServiceCallback;
+import android.telephony.data.ApnThrottleStatus;
/**
* {@hide}
@@ -25,4 +26,5 @@
{
oneway void createNetworkAvailabilityProvider(int slotId, IQualifiedNetworksServiceCallback callback);
oneway void removeNetworkAvailabilityProvider(int slotId);
+ oneway void reportApnThrottleStatusChanged(int slotId, in List<ApnThrottleStatus> statuses);
}
diff --git a/telephony/java/android/telephony/data/NrQos.java b/telephony/java/android/telephony/data/NrQos.java
new file mode 100644
index 0000000..2011eed
--- /dev/null
+++ b/telephony/java/android/telephony/data/NrQos.java
@@ -0,0 +1,112 @@
+/**
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.data;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Class that stores information specific to NR QOS.
+ *
+ * @hide
+ */
+public final class NrQos extends Qos implements Parcelable {
+ int qosFlowId;
+ int fiveQi;
+ int averagingWindowMs;
+
+ public NrQos(@NonNull android.hardware.radio.V1_6.NrQos qos) {
+ super(Qos.QOS_TYPE_NR, qos.downlink, qos.uplink);
+ fiveQi = qos.fiveQi;
+ qosFlowId = qos.qfi;
+ averagingWindowMs = qos.averagingWindowMs;
+ }
+
+ private NrQos(Parcel source) {
+ super(source);
+ this.qosFlowId = source.readInt();
+ this.fiveQi = source.readInt();
+ this.averagingWindowMs = source.readInt();
+ }
+
+ public static @NonNull NrQos createFromParcelBody(@NonNull Parcel in) {
+ return new NrQos(in);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(Qos.QOS_TYPE_NR, dest, flags);
+ dest.writeInt(qosFlowId);
+ dest.writeInt(fiveQi);
+ dest.writeInt(averagingWindowMs);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), qosFlowId, fiveQi, averagingWindowMs);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o == null || !(o instanceof NrQos)) {
+ return false;
+ }
+
+ NrQos other = (NrQos) o;
+
+ if (!super.equals(other)) {
+ return false;
+ }
+
+ return this.qosFlowId == other.qosFlowId
+ && this.fiveQi == other.fiveQi
+ && this.averagingWindowMs == other.averagingWindowMs;
+ }
+
+ @Override
+ public String toString() {
+ return "NrQos {"
+ + " fiveQi=" + fiveQi
+ + " downlink=" + downlink
+ + " uplink=" + uplink
+ + " qosFlowId=" + qosFlowId
+ + " averagingWindowMs=" + averagingWindowMs + "}";
+ }
+
+ public static final @NonNull Parcelable.Creator<NrQos> CREATOR =
+ new Parcelable.Creator<NrQos>() {
+ @Override
+ public NrQos createFromParcel(Parcel source) {
+ return new NrQos(source);
+ }
+
+ @Override
+ public NrQos[] newArray(int size) {
+ return new NrQos[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/data/Qos.java b/telephony/java/android/telephony/data/Qos.java
new file mode 100644
index 0000000..c8bb91e
--- /dev/null
+++ b/telephony/java/android/telephony/data/Qos.java
@@ -0,0 +1,175 @@
+/**
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.data;
+
+import android.annotation.CallSuper;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Class that stores information specific to QOS.
+ *
+ * @hide
+ */
+public abstract class Qos {
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "QOS_TYPE_",
+ value = {QOS_TYPE_EPS, QOS_TYPE_NR})
+ public @interface QosType {}
+
+ @QosType
+ final int type;
+
+ static final int QOS_TYPE_EPS = 1;
+ static final int QOS_TYPE_NR = 2;
+
+ final QosBandwidth downlink;
+ final QosBandwidth uplink;
+
+ Qos(int type,
+ @NonNull android.hardware.radio.V1_6.QosBandwidth downlink,
+ @NonNull android.hardware.radio.V1_6.QosBandwidth uplink) {
+ this.type = type;
+ this.downlink = new QosBandwidth(downlink.maxBitrateKbps, downlink.guaranteedBitrateKbps);
+ this.uplink = new QosBandwidth(uplink.maxBitrateKbps, uplink.guaranteedBitrateKbps);
+ }
+
+ static class QosBandwidth implements Parcelable {
+ int maxBitrateKbps;
+ int guaranteedBitrateKbps;
+
+ QosBandwidth() {
+ }
+
+ QosBandwidth(int maxBitrateKbps, int guaranteedBitrateKbps) {
+ this.maxBitrateKbps = maxBitrateKbps;
+ this.guaranteedBitrateKbps = guaranteedBitrateKbps;
+ }
+
+ private QosBandwidth(Parcel source) {
+ maxBitrateKbps = source.readInt();
+ guaranteedBitrateKbps = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(maxBitrateKbps);
+ dest.writeInt(guaranteedBitrateKbps);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(maxBitrateKbps, guaranteedBitrateKbps);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o == null || !(o instanceof QosBandwidth)) {
+ return false;
+ }
+
+ QosBandwidth other = (QosBandwidth) o;
+ return maxBitrateKbps == other.maxBitrateKbps
+ && guaranteedBitrateKbps == other.guaranteedBitrateKbps;
+ }
+
+ @Override
+ public String toString() {
+ return "Bandwidth {"
+ + " maxBitrateKbps=" + maxBitrateKbps
+ + " guaranteedBitrateKbps=" + guaranteedBitrateKbps + "}";
+ }
+
+ public static final @NonNull Parcelable.Creator<QosBandwidth> CREATOR =
+ new Parcelable.Creator<QosBandwidth>() {
+ @Override
+ public QosBandwidth createFromParcel(Parcel source) {
+ return new QosBandwidth(source);
+ }
+
+ @Override
+ public QosBandwidth[] newArray(int size) {
+ return new QosBandwidth[size];
+ }
+ };
+ };
+
+ protected Qos(@NonNull Parcel source) {
+ type = source.readInt();
+ downlink = source.readParcelable(QosBandwidth.class.getClassLoader());
+ uplink = source.readParcelable(QosBandwidth.class.getClassLoader());
+ }
+
+ /**
+ * Used by child classes for parceling.
+ *
+ * @hide
+ */
+ @CallSuper
+ public void writeToParcel(@QosType int type, Parcel dest, int flags) {
+ dest.writeInt(type);
+ dest.writeParcelable(downlink, flags);
+ dest.writeParcelable(uplink, flags);
+ }
+
+ /** @hide */
+ public static @NonNull Qos create(@NonNull android.hardware.radio.V1_6.Qos qos) {
+ switch (qos.getDiscriminator()) {
+ case android.hardware.radio.V1_6.Qos.hidl_discriminator.eps:
+ return new EpsQos(qos.eps());
+ case android.hardware.radio.V1_6.Qos.hidl_discriminator.nr:
+ return new NrQos(qos.nr());
+ default:
+ return null;
+ }
+ }
+
+ /** @hide */
+ public @QosType int getType() {
+ return type;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(downlink, uplink);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ Qos other = (Qos) o;
+ return type == other.type
+ && downlink.equals(other.downlink)
+ && uplink.equals(other.uplink);
+ }
+}
diff --git a/telephony/java/android/telephony/data/QosFilter.java b/telephony/java/android/telephony/data/QosFilter.java
new file mode 100644
index 0000000..6927744
--- /dev/null
+++ b/telephony/java/android/telephony/data/QosFilter.java
@@ -0,0 +1,373 @@
+/**
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.data;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.net.InetAddresses;
+import android.net.LinkAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+
+/**
+ * Class that stores QOS filter parameters as defined in
+ * 3gpp 24.008 10.5.6.12 and 3gpp 24.501 9.11.4.13.
+ *
+ * @hide
+ */
+public final class QosFilter implements Parcelable {
+
+ private List<LinkAddress> localAddresses;
+ private List<LinkAddress> remoteAddresses;
+ private PortRange localPort;
+ private PortRange remotePort;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "QOS_PROTOCOL_",
+ value = {QOS_PROTOCOL_UNSPECIFIED, QOS_PROTOCOL_TCP, QOS_PROTOCOL_UDP,
+ QOS_PROTOCOL_ESP, QOS_PROTOCOL_AH})
+ public @interface QosProtocol {}
+
+ public static final int QOS_PROTOCOL_UNSPECIFIED =
+ android.hardware.radio.V1_6.QosProtocol.UNSPECIFIED;
+ public static final int QOS_PROTOCOL_TCP = android.hardware.radio.V1_6.QosProtocol.TCP;
+ public static final int QOS_PROTOCOL_UDP = android.hardware.radio.V1_6.QosProtocol.UDP;
+ public static final int QOS_PROTOCOL_ESP = android.hardware.radio.V1_6.QosProtocol.ESP;
+ public static final int QOS_PROTOCOL_AH = android.hardware.radio.V1_6.QosProtocol.AH;
+
+ @QosProtocol
+ private int protocol;
+
+ private int typeOfServiceMask;
+
+ private long flowLabel;
+
+ /** IPSec security parameter index */
+ private long securityParameterIndex;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "QOS_FILTER_DIRECTION_",
+ value = {QOS_FILTER_DIRECTION_DOWNLINK, QOS_FILTER_DIRECTION_UPLINK,
+ QOS_FILTER_DIRECTION_BIDIRECTIONAL})
+ public @interface QosFilterDirection {}
+
+ public static final int QOS_FILTER_DIRECTION_DOWNLINK =
+ android.hardware.radio.V1_6.QosFilterDirection.DOWNLINK;
+ public static final int QOS_FILTER_DIRECTION_UPLINK =
+ android.hardware.radio.V1_6.QosFilterDirection.UPLINK;
+ public static final int QOS_FILTER_DIRECTION_BIDIRECTIONAL =
+ android.hardware.radio.V1_6.QosFilterDirection.BIDIRECTIONAL;
+
+ @QosFilterDirection
+ private int filterDirection;
+
+ /**
+ * Specified the order in which the filter needs to be matched.
+ * A Lower numerical value has a higher precedence.
+ */
+ private int precedence;
+
+ QosFilter() {
+ localAddresses = new ArrayList<>();
+ remoteAddresses = new ArrayList<>();
+ localPort = new PortRange();
+ remotePort = new PortRange();
+ protocol = QOS_PROTOCOL_UNSPECIFIED;
+ filterDirection = QOS_FILTER_DIRECTION_BIDIRECTIONAL;
+ }
+
+ public QosFilter(List<LinkAddress> localAddresses, List<LinkAddress> remoteAddresses,
+ PortRange localPort, PortRange remotePort, int protocol, int tos,
+ long flowLabel, long spi, int direction, int precedence) {
+ this.localAddresses = localAddresses;
+ this.remoteAddresses = remoteAddresses;
+ this.localPort = localPort;
+ this.remotePort = remotePort;
+ this.protocol = protocol;
+ this.typeOfServiceMask = tos;
+ this.flowLabel = flowLabel;
+ this.securityParameterIndex = spi;
+ this.filterDirection = direction;
+ this.precedence = precedence;
+ }
+
+ /** @hide */
+ public static @NonNull QosFilter create(
+ @NonNull android.hardware.radio.V1_6.QosFilter qosFilter) {
+ QosFilter ret = new QosFilter();
+
+ String[] localAddresses = qosFilter.localAddresses.stream().toArray(String[]::new);
+ if (localAddresses != null) {
+ for (String address : localAddresses) {
+ ret.localAddresses.add(createLinkAddressFromString(address));
+ }
+ }
+
+ String[] remoteAddresses = qosFilter.remoteAddresses.stream().toArray(String[]::new);
+ if (remoteAddresses != null) {
+ for (String address : remoteAddresses) {
+ ret.remoteAddresses.add(createLinkAddressFromString(address));
+ }
+ }
+
+ if (qosFilter.localPort != null) {
+ if (qosFilter.localPort.getDiscriminator()
+ == android.hardware.radio.V1_6.MaybePort.hidl_discriminator.range) {
+ final android.hardware.radio.V1_6.PortRange portRange = qosFilter.localPort.range();
+ ret.localPort.start = portRange.start;
+ ret.localPort.end = portRange.end;
+ }
+ }
+
+ if (qosFilter.remotePort != null) {
+ if (qosFilter.remotePort.getDiscriminator()
+ == android.hardware.radio.V1_6.MaybePort.hidl_discriminator.range) {
+ final android.hardware.radio.V1_6.PortRange portRange
+ = qosFilter.remotePort.range();
+ ret.remotePort.start = portRange.start;
+ ret.remotePort.end = portRange.end;
+ }
+ }
+
+ ret.protocol = qosFilter.protocol;
+
+ if (qosFilter.tos != null) {
+ if (qosFilter.tos.getDiscriminator()
+ == android.hardware.radio.V1_6.QosFilter.TypeOfService.hidl_discriminator.value) {
+ ret.typeOfServiceMask = qosFilter.tos.value();
+ }
+ }
+
+ if (qosFilter.flowLabel != null) {
+ if (qosFilter.flowLabel.getDiscriminator()
+ == android.hardware.radio.V1_6.QosFilter.Ipv6FlowLabel.hidl_discriminator.value) {
+ ret.flowLabel = qosFilter.flowLabel.value();
+ }
+ }
+
+ if (qosFilter.spi != null) {
+ if (qosFilter.spi.getDiscriminator()
+ == android.hardware.radio.V1_6.QosFilter.IpsecSpi.hidl_discriminator.value) {
+ ret.securityParameterIndex = qosFilter.spi.value();
+ }
+ }
+
+ ret.filterDirection = qosFilter.direction;
+ ret.precedence = qosFilter.precedence;
+
+ return ret;
+ }
+
+ public static class PortRange implements Parcelable {
+ int start;
+ int end;
+
+ PortRange() {
+ start = -1;
+ end = -1;
+ }
+
+ private PortRange(Parcel source) {
+ start = source.readInt();
+ end = source.readInt();
+ }
+
+ public PortRange(int start, int end) {
+ this.start = start;
+ this.end = end;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(start);
+ dest.writeInt(end);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Parcelable.Creator<PortRange> CREATOR =
+ new Parcelable.Creator<PortRange>() {
+ @Override
+ public PortRange createFromParcel(Parcel source) {
+ return new PortRange(source);
+ }
+
+ @Override
+ public PortRange[] newArray(int size) {
+ return new PortRange[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "PortRange {"
+ + " start=" + start
+ + " end=" + end + "}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o == null || !(o instanceof PortRange)) {
+ return false;
+ }
+
+ PortRange other = (PortRange) o;
+ return start == other.start
+ && end == other.end;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(start, end);
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "QosFilter {"
+ + " localAddresses=" + localAddresses
+ + " remoteAddresses=" + remoteAddresses
+ + " localPort=" + localPort
+ + " remotePort=" + remotePort
+ + " protocol=" + protocol
+ + " typeOfServiceMask=" + typeOfServiceMask
+ + " flowLabel=" + flowLabel
+ + " securityParameterIndex=" + securityParameterIndex
+ + " filterDirection=" + filterDirection
+ + " precedence=" + precedence + "}";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(localAddresses, remoteAddresses, localPort,
+ remotePort, protocol, typeOfServiceMask, flowLabel,
+ securityParameterIndex, filterDirection, precedence);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o == null || !(o instanceof QosFilter)) {
+ return false;
+ }
+
+ QosFilter other = (QosFilter) o;
+
+ return localAddresses.size() == other.localAddresses.size()
+ && localAddresses.containsAll(other.localAddresses)
+ && remoteAddresses.size() == other.remoteAddresses.size()
+ && remoteAddresses.containsAll(other.remoteAddresses)
+ && localPort.equals(other.localPort)
+ && remotePort.equals(other.remotePort)
+ && protocol == other.protocol
+ && typeOfServiceMask == other.typeOfServiceMask
+ && flowLabel == other.flowLabel
+ && securityParameterIndex == other.securityParameterIndex
+ && filterDirection == other.filterDirection
+ && precedence == other.precedence;
+ }
+
+ private static LinkAddress createLinkAddressFromString(String addressString) {
+ addressString = addressString.trim();
+ InetAddress address = null;
+ int prefixLength = -1;
+ try {
+ String[] pieces = addressString.split("/", 2);
+ address = InetAddresses.parseNumericAddress(pieces[0]);
+ if (pieces.length == 1) {
+ prefixLength = (address instanceof Inet4Address) ? 32 : 128;
+ } else if (pieces.length == 2) {
+ prefixLength = Integer.parseInt(pieces[1]);
+ }
+ } catch (NullPointerException e) { // Null string.
+ } catch (ArrayIndexOutOfBoundsException e) { // No prefix length.
+ } catch (NumberFormatException e) { // Non-numeric prefix.
+ } catch (IllegalArgumentException e) { // Invalid IP address.
+ }
+
+ if (address == null || prefixLength == -1) {
+ throw new IllegalArgumentException("Invalid link address " + addressString);
+ }
+
+ return new LinkAddress(address, prefixLength, 0, 0,
+ LinkAddress.LIFETIME_UNKNOWN, LinkAddress.LIFETIME_UNKNOWN);
+ }
+
+ private QosFilter(Parcel source) {
+ localAddresses = new ArrayList<>();
+ source.readList(localAddresses, LinkAddress.class.getClassLoader());
+ remoteAddresses = new ArrayList<>();
+ source.readList(remoteAddresses, LinkAddress.class.getClassLoader());
+ localPort = source.readParcelable(PortRange.class.getClassLoader());
+ remotePort = source.readParcelable(PortRange.class.getClassLoader());
+ protocol = source.readInt();
+ typeOfServiceMask = source.readInt();
+ flowLabel = source.readLong();
+ securityParameterIndex = source.readLong();
+ filterDirection = source.readInt();
+ precedence = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeList(localAddresses);
+ dest.writeList(remoteAddresses);
+ dest.writeParcelable(localPort, flags);
+ dest.writeParcelable(remotePort, flags);
+ dest.writeInt(protocol);
+ dest.writeInt(typeOfServiceMask);
+ dest.writeLong(flowLabel);
+ dest.writeLong(securityParameterIndex);
+ dest.writeInt(filterDirection);
+ dest.writeInt(precedence);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Parcelable.Creator<QosFilter> CREATOR =
+ new Parcelable.Creator<QosFilter>() {
+ @Override
+ public QosFilter createFromParcel(Parcel source) {
+ return new QosFilter(source);
+ }
+
+ @Override
+ public QosFilter[] newArray(int size) {
+ return new QosFilter[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/data/QosSession.java b/telephony/java/android/telephony/data/QosSession.java
new file mode 100644
index 0000000..f07b6a9
--- /dev/null
+++ b/telephony/java/android/telephony/data/QosSession.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.data;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+
+/**
+ * Class that stores information specific to QOS session.
+ *
+ * @hide
+ */
+public final class QosSession implements Parcelable{
+
+ final int qosSessionId;
+ final Qos qos;
+ final List<QosFilter> qosFilterList;
+
+ public QosSession(int qosSessionId, @NonNull Qos qos, @NonNull List<QosFilter> qosFilterList) {
+ this.qosSessionId = qosSessionId;
+ this.qos = qos;
+ this.qosFilterList = qosFilterList;
+ }
+
+ private QosSession(Parcel source) {
+ qosSessionId = source.readInt();
+ qos = source.readParcelable(Qos.class.getClassLoader());
+ qosFilterList = new ArrayList<>();
+ source.readList(qosFilterList, QosFilter.class.getClassLoader());
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(qosSessionId);
+ if (qos.getType() == Qos.QOS_TYPE_EPS) {
+ dest.writeParcelable((EpsQos)qos, flags);
+ } else {
+ dest.writeParcelable((NrQos)qos, flags);
+ }
+ dest.writeList(qosFilterList);
+ }
+
+ public static @NonNull QosSession create(
+ @NonNull android.hardware.radio.V1_6.QosSession qosSession) {
+ List<QosFilter> qosFilters = new ArrayList<>();
+
+ if (qosSession.qosFilters != null) {
+ for (android.hardware.radio.V1_6.QosFilter filter : qosSession.qosFilters) {
+ qosFilters.add(QosFilter.create(filter));
+ }
+ }
+
+ return new QosSession(
+ qosSession.qosSessionId,
+ Qos.create(qosSession.qos),
+ qosFilters);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "QosSession {"
+ + " qosSessionId=" + qosSessionId
+ + " qos=" + qos
+ + " qosFilterList=" + qosFilterList + "}";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(qosSessionId, qos, qosFilterList);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o == null || !(o instanceof QosSession)) {
+ return false;
+ }
+
+ QosSession other = (QosSession) o;
+ return this.qosSessionId == other.qosSessionId
+ && this.qos.equals(other.qos)
+ && this.qosFilterList.size() == other.qosFilterList.size()
+ && this.qosFilterList.containsAll(other.qosFilterList);
+ }
+
+
+ public static final @NonNull Parcelable.Creator<QosSession> CREATOR =
+ new Parcelable.Creator<QosSession>() {
+ @Override
+ public QosSession createFromParcel(Parcel source) {
+ return new QosSession(source);
+ }
+
+ @Override
+ public QosSession[] newArray(int size) {
+ return new QosSession[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 05971c4..4af63b4 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -28,6 +28,7 @@
import android.os.RemoteException;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.Annotation.ApnType;
+import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -65,6 +66,7 @@
private static final int QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER = 2;
private static final int QNS_REMOVE_ALL_NETWORK_AVAILABILITY_PROVIDERS = 3;
private static final int QNS_UPDATE_QUALIFIED_NETWORKS = 4;
+ private static final int QNS_APN_THROTTLE_STATUS_CHANGED = 5;
private final HandlerThread mHandlerThread;
@@ -160,6 +162,17 @@
}
/**
+ * The framework calls this method when the throttle status of an APN changes.
+ *
+ * This method is meant to be overridden.
+ *
+ * @param statuses the statuses that have changed
+ */
+ public void reportApnThrottleStatusChanged(@NonNull List<ApnThrottleStatus> statuses) {
+ Log.d(TAG, "reportApnThrottleStatusChanged: statuses size=" + statuses.size());
+ }
+
+ /**
* Called when the qualified networks provider is removed. The extended class should
* implement this method to perform cleanup works.
*/
@@ -197,6 +210,12 @@
+ slotIndex);
}
break;
+ case QNS_APN_THROTTLE_STATUS_CHANGED:
+ if (provider != null) {
+ List<ApnThrottleStatus> statuses = (List<ApnThrottleStatus>) message.obj;
+ provider.reportApnThrottleStatusChanged(statuses);
+ }
+ break;
case QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER:
if (provider != null) {
@@ -286,6 +305,13 @@
mHandler.obtainMessage(QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER, slotIndex, 0)
.sendToTarget();
}
+
+ @Override
+ public void reportApnThrottleStatusChanged(int slotIndex,
+ List<ApnThrottleStatus> statuses) {
+ mHandler.obtainMessage(QNS_APN_THROTTLE_STATUS_CHANGED, slotIndex, 0, statuses)
+ .sendToTarget();
+ }
}
private void log(String s) {
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 76fc4f7..6fbde50 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -113,6 +113,7 @@
public static final int EVENT_NR_TIMER_WATCHDOG = BASE + 53;
public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 54;
public static final int EVENT_SIM_STATE_UPDATED = BASE + 55;
+ public static final int EVENT_APN_UNTHROTTLED = BASE + 56;
/***** Constants *****/
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0bd4851..3e7defb 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2154,6 +2154,8 @@
oneway void setSystemSelectionChannels(in List<RadioAccessSpecifier> specifiers,
int subId, IBooleanConsumer resultCallback);
+ List<RadioAccessSpecifier> getSystemSelectionChannels(int subId);
+
boolean isMvnoMatched(int subId, int mvnoType, String mvnoMatchData);
/**
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 9d4072f..a2361a7 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -519,6 +519,7 @@
int RIL_REQUEST_RELEASE_PDU_SESSION_ID = 216;
int RIL_REQUEST_START_HANDOVER = 217;
int RIL_REQUEST_CANCEL_HANDOVER = 218;
+ int RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS = 219;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/BatteryStatsPerfTest/Android.bp b/tests/BatteryStatsPerfTest/Android.bp
new file mode 100644
index 0000000..58ccec7
--- /dev/null
+++ b/tests/BatteryStatsPerfTest/Android.bp
@@ -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.
+
+android_test {
+ name: "BatteryStatsPerfTests",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.test.rules",
+ "apct-perftests-utils",
+ "truth-prebuilt",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/tests/BatteryStatsPerfTest/AndroidManifest.xml b/tests/BatteryStatsPerfTest/AndroidManifest.xml
new file mode 100644
index 0000000..7633d52
--- /dev/null
+++ b/tests/BatteryStatsPerfTest/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.perftests.batterystats">
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.BATTERY_STATS"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.perftests.batterystats"/>
+</manifest>
diff --git a/tests/BatteryStatsPerfTest/AndroidTest.xml b/tests/BatteryStatsPerfTest/AndroidTest.xml
new file mode 100644
index 0000000..2f9e114
--- /dev/null
+++ b/tests/BatteryStatsPerfTest/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<configuration description="Runs BatteryStats service Performance Tests">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="BatteryStatsPerfTests.apk"/>
+ <option name="cleanup-apks" value="true"/>
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="BatteryStatsPerfTests"/>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.frameworks.perftests.batterystats"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration>
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java
new file mode 100644
index 0000000..6266cda
--- /dev/null
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class BatteryStatsHelperPerfTest {
+
+ @Rule
+ public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ /**
+ * Measures the performance of {@link BatteryStatsHelper#getStats()}, which triggers
+ * a battery stats sync on every iteration.
+ */
+ @Test
+ public void testGetStats_forceUpdate() {
+ final Context context = InstrumentationRegistry.getContext();
+ final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
+ true /* collectBatteryBroadcast */);
+ statsHelper.create((Bundle) null);
+ statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ statsHelper.clearStats();
+ state.resumeTiming();
+
+ statsHelper.getStats();
+
+ assertThat(statsHelper.getUsageList()).isNotEmpty();
+ }
+ }
+
+ /**
+ * Measures performance of the {@link BatteryStatsHelper#getStats(boolean)}, which does
+ * not trigger a sync and just returns current values.
+ */
+ @Test
+ public void testGetStats_cached() {
+ final Context context = InstrumentationRegistry.getContext();
+ final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
+ true /* collectBatteryBroadcast */);
+ statsHelper.create((Bundle) null);
+ statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ statsHelper.clearStats();
+ state.resumeTiming();
+
+ statsHelper.getStats(false /* forceUpdate */);
+
+ assertThat(statsHelper.getUsageList()).isNotEmpty();
+ }
+ }
+
+ @Test
+ public void testPowerCalculation() {
+ final Context context = InstrumentationRegistry.getContext();
+ final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
+ true /* collectBatteryBroadcast */);
+ statsHelper.create((Bundle) null);
+ statsHelper.getStats();
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ // This will use the cached BatteryStatsObject
+ statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
+
+ assertThat(statsHelper.getUsageList()).isNotEmpty();
+ }
+ }
+
+ @Test
+ public void testEndToEnd() {
+ final Context context = InstrumentationRegistry.getContext();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
+ true /* collectBatteryBroadcast */);
+ statsHelper.create((Bundle) null);
+ statsHelper.clearStats();
+ statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
+
+ state.pauseTiming();
+
+ List<BatterySipper> usageList = statsHelper.getUsageList();
+ double power = 0;
+ for (int i = 0; i < usageList.size(); i++) {
+ BatterySipper sipper = usageList.get(i);
+ power += sipper.sumPower();
+ }
+
+ assertThat(power).isGreaterThan(0.0);
+
+ state.resumeTiming();
+ }
+ }
+}
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml
index 98e02ce..58f56f2 100644
--- a/tests/FlickerTests/AndroidManifest.xml
+++ b/tests/FlickerTests/AndroidManifest.xml
@@ -27,6 +27,8 @@
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
<!-- Enable / Disable tracing !-->
<uses-permission android:name="android.permission.DUMP" />
+ <!-- Force-stop test apps -->
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
<!-- Run layers trace -->
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
<!-- Workaround grant runtime permission exception from b/152733071 -->
diff --git a/tests/SurfaceViewBufferTests/Android.bp b/tests/SurfaceViewBufferTests/Android.bp
index 647da2a..48031de 100644
--- a/tests/SurfaceViewBufferTests/Android.bp
+++ b/tests/SurfaceViewBufferTests/Android.bp
@@ -33,6 +33,8 @@
"kotlinx-coroutines-android",
"flickerlib",
"truth-prebuilt",
+ "cts-wm-util",
+ "CtsSurfaceValidatorLib",
],
}
@@ -43,6 +45,7 @@
],
shared_libs: [
"libutils",
+ "libui",
"libgui",
"liblog",
"libandroid",
diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml
index 95885c1..c910ecd 100644
--- a/tests/SurfaceViewBufferTests/AndroidManifest.xml
+++ b/tests/SurfaceViewBufferTests/AndroidManifest.xml
@@ -23,12 +23,19 @@
<uses-permission android:name="android.permission.DUMP" />
<!-- Enable / Disable sv blast adapter !-->
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Readback virtual display output !-->
+ <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+ <!-- Save failed test bitmap images !-->
+ <uses-permission android:name="android.Manifest.permission.WRITE_EXTERNAL_STORAGE"/>
<application android:allowBackup="false"
android:supportsRtl="true">
<activity android:name=".MainActivity"
android:taskAffinity="com.android.test.MainActivity"
android:theme="@style/AppTheme"
+ android:configChanges="orientation|screenSize"
android:label="SurfaceViewBufferTestApp"
android:exported="true">
<intent-filter>
@@ -36,6 +43,10 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
+ android:foregroundServiceType="mediaProjection"
+ android:enabled="true">
+ </service>
<uses-library android:name="android.test.runner"/>
</application>
diff --git a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
index 0c86524..ce226fd 100644
--- a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
+++ b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
@@ -32,6 +32,7 @@
extern "C" {
int i = 0;
static ANativeWindow* sAnw;
+static std::map<uint32_t /* slot */, ANativeWindowBuffer*> sBuffers;
JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_setSurface(JNIEnv* env, jclass,
jobject surfaceObject) {
@@ -39,11 +40,14 @@
assert(sAnw);
android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
surface->enableFrameTimestamps(true);
+ surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false);
+ native_window_set_usage(sAnw, GRALLOC_USAGE_SW_WRITE_OFTEN);
+ native_window_set_buffers_format(sAnw, HAL_PIXEL_FORMAT_RGBA_8888);
return 0;
}
JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_waitUntilBufferDisplayed(
- JNIEnv*, jclass, jint jFrameNumber, jint timeoutSec) {
+ JNIEnv*, jclass, jlong jFrameNumber, jint timeoutMs) {
using namespace std::chrono_literals;
assert(sAnw);
android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
@@ -63,8 +67,8 @@
&outDisplayPresentTime, &outDequeueReadyTime, &outReleaseTime);
if (outDisplayPresentTime < 0) {
auto end = std::chrono::steady_clock::now();
- if (std::chrono::duration_cast<std::chrono::seconds>(end - start).count() >
- timeoutSec) {
+ if (std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() >
+ timeoutMs) {
return -1;
}
}
@@ -99,4 +103,121 @@
assert(sAnw);
return ANativeWindow_setBuffersGeometry(sAnw, w, h, format);
}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowSetBuffersTransform(
+ JNIEnv* /* env */, jclass /* clazz */, jint transform) {
+ assert(sAnw);
+ return native_window_set_buffers_transform(sAnw, transform);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetScalingMode(JNIEnv* /* env */,
+ jclass /* clazz */,
+ jint scalingMode) {
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+ return surface->setScalingMode(scalingMode);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceDequeueBuffer(JNIEnv* /* env */,
+ jclass /* clazz */,
+ jint slot,
+ jint timeoutMs) {
+ assert(sAnw);
+ ANativeWindowBuffer* anb;
+ int fenceFd;
+ int result = sAnw->dequeueBuffer(sAnw, &anb, &fenceFd);
+ if (result != android::OK) {
+ return result;
+ }
+ sBuffers[slot] = anb;
+ android::sp<android::Fence> fence(new android::Fence(fenceFd));
+ int waitResult = fence->wait(timeoutMs);
+ if (waitResult != android::OK) {
+ sAnw->cancelBuffer(sAnw, sBuffers[slot], -1);
+ sBuffers[slot] = nullptr;
+ return waitResult;
+ }
+ return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceCancelBuffer(JNIEnv* /* env */,
+ jclass /* clazz */,
+ jint slot) {
+ assert(sAnw);
+ assert(sBuffers[slot]);
+ int result = sAnw->cancelBuffer(sAnw, sBuffers[slot], -1);
+ sBuffers[slot] = nullptr;
+ return result;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_drawBuffer(JNIEnv* env,
+ jclass /* clazz */, jint slot,
+ jintArray jintArrayColor) {
+ assert(sAnw);
+ assert(sBuffers[slot]);
+
+ int* color = env->GetIntArrayElements(jintArrayColor, nullptr);
+
+ ANativeWindowBuffer* buffer = sBuffers[slot];
+ android::sp<android::GraphicBuffer> graphicBuffer(static_cast<android::GraphicBuffer*>(buffer));
+ const android::Rect bounds(buffer->width, buffer->height);
+ android::Region newDirtyRegion;
+ newDirtyRegion.set(bounds);
+
+ void* vaddr;
+ int fenceFd = -1;
+ graphicBuffer->lockAsync(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+ newDirtyRegion.bounds(), &vaddr, fenceFd);
+
+ for (int32_t row = 0; row < buffer->height; row++) {
+ uint8_t* dst = static_cast<uint8_t*>(vaddr) + (buffer->stride * row) * 4;
+ for (int32_t column = 0; column < buffer->width; column++) {
+ dst[0] = color[0];
+ dst[1] = color[1];
+ dst[2] = color[2];
+ dst[3] = color[3];
+ dst += 4;
+ }
+ }
+ graphicBuffer->unlockAsync(&fenceFd);
+ env->ReleaseIntArrayElements(jintArrayColor, color, JNI_ABORT);
+ return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceQueueBuffer(JNIEnv* /* env */,
+ jclass /* clazz */,
+ jint slot,
+ jboolean freeSlot) {
+ assert(sAnw);
+ assert(sBuffers[slot]);
+ int result = sAnw->queueBuffer(sAnw, sBuffers[slot], -1);
+ if (freeSlot) {
+ sBuffers[slot] = nullptr;
+ }
+ return result;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetBufferCount(
+ JNIEnv* /* env */, jclass /* clazz */, jint count) {
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+ int result = native_window_set_buffer_count(sAnw, count);
+ return result;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetSharedBufferMode(
+ JNIEnv* /* env */, jclass /* clazz */, jboolean shared) {
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+ int result = native_window_set_shared_buffer_mode(sAnw, shared);
+ return result;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetAutoRefresh(
+ JNIEnv* /* env */, jclass /* clazz */, jboolean autoRefresh) {
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+ int result = native_window_set_auto_refresh(sAnw, autoRefresh);
+ return result;
+}
}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
new file mode 100644
index 0000000..eb16bad
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class BufferPresentationTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) {
+ /** Submit buffers as fast as possible and make sure they are presented on display */
+ @Test
+ fun testQueueBuffers() {
+ val numFrames = 100L
+ val trace = withTrace {
+ for (i in 1..numFrames) {
+ it.mSurfaceProxy.ANativeWindowLock()
+ it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+ }
+ assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 1000 /* ms */))
+ }
+
+ assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+ }
+
+ @Test
+ fun testSetBufferScalingMode_outOfOrderQueueBuffer() {
+ val trace = withTrace {
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+
+ it.mSurfaceProxy.SurfaceQueueBuffer(1)
+ it.mSurfaceProxy.SurfaceQueueBuffer(0)
+ assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(2, 5000 /* ms */))
+ }
+
+ assertThat(trace).hasFrameSequence("SurfaceView", 1..2L)
+ }
+
+ @Test
+ fun testSetBufferScalingMode_multipleDequeueBuffer() {
+ val numFrames = 20L
+ val trace = withTrace {
+ for (count in 1..(numFrames / 2)) {
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+
+ it.mSurfaceProxy.SurfaceQueueBuffer(0)
+ it.mSurfaceProxy.SurfaceQueueBuffer(1)
+ }
+ assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 5000 /* ms */))
+ }
+
+ assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+ }
+
+ @Test
+ fun testSetBufferCount_queueMaxBufferCountMinusOne() {
+ val numBufferCount = 8
+ val numFrames = numBufferCount * 5L
+ val trace = withTrace {
+ assertEquals(0, it.mSurfaceProxy.NativeWindowSetBufferCount(numBufferCount + 1))
+ for (i in 1..numFrames / numBufferCount) {
+ for (bufferSlot in 0..numBufferCount - 1) {
+ assertEquals(0,
+ it.mSurfaceProxy.SurfaceDequeueBuffer(bufferSlot, 1000 /* ms */))
+ }
+
+ for (bufferSlot in 0..numBufferCount - 1) {
+ it.mSurfaceProxy.SurfaceQueueBuffer(bufferSlot)
+ }
+ }
+ assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 5000 /* ms */))
+ }
+
+ assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+ }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt
new file mode 100644
index 0000000..95a7fd5
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.graphics.Point
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode
+import com.android.test.SurfaceViewBufferTestBase.Companion.Transform
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class BufferRejectionTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) {
+ @Test
+ fun testSetBuffersGeometry_0x0_rejectsBuffer() {
+ val trace = withTrace {
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
+ R8G8B8A8_UNORM)
+ it.mSurfaceProxy.ANativeWindowLock()
+ it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+ it.mSurfaceProxy.ANativeWindowLock()
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, R8G8B8A8_UNORM)
+ // Submit buffer one with a different size which should be rejected
+ it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+
+ // submit a buffer with the default buffer size
+ it.mSurfaceProxy.ANativeWindowLock()
+ it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+ it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */)
+ }
+ // Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE
+ assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+
+ // Verify the next buffer is submitted with the correct size
+ assertThat(trace).layer("SurfaceView", 3).also {
+ it.hasBufferSize(defaultBufferSize)
+ // scaling mode is not passed down to the layer for blast
+ if (useBlastAdapter) {
+ it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal)
+ } else {
+ it.hasScalingMode(ScalingMode.FREEZE.ordinal)
+ }
+ }
+ }
+
+ @Test
+ fun testSetBufferScalingMode_freeze() {
+ val bufferSize = Point(300, 200)
+ val trace = withTrace {
+ it.drawFrame()
+ assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0)
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize,
+ R8G8B8A8_UNORM)
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+ // Change buffer size and set scaling mode to freeze
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0),
+ R8G8B8A8_UNORM)
+
+ // first dequeued buffer does not have the new size so it should be rejected.
+ it.mSurfaceProxy.SurfaceQueueBuffer(0)
+ it.mSurfaceProxy.SurfaceSetScalingMode(ScalingMode.SCALE_TO_WINDOW)
+ it.mSurfaceProxy.SurfaceQueueBuffer(1)
+ assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0)
+ }
+
+ // verify buffer size is reset to default buffer size
+ assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+ assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+ assertThat(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize)
+ }
+
+ @Test
+ fun testSetBufferScalingMode_freeze_withBufferRotation() {
+ val rotatedBufferSize = Point(defaultBufferSize.y, defaultBufferSize.x)
+ val trace = withTrace {
+ it.drawFrame()
+ assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0)
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, rotatedBufferSize,
+ R8G8B8A8_UNORM)
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+ // Change buffer size and set scaling mode to freeze
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0),
+ R8G8B8A8_UNORM)
+
+ // first dequeued buffer does not have the new size so it should be rejected.
+ it.mSurfaceProxy.SurfaceQueueBuffer(0)
+ // add a buffer transform so the buffer size is correct.
+ it.mSurfaceProxy.ANativeWindowSetBuffersTransform(Transform.ROT_90)
+ it.mSurfaceProxy.SurfaceQueueBuffer(1)
+ assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0)
+ }
+
+ // verify buffer size is reset to default buffer size
+ assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+ assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+ assertThat(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize)
+ assertThat(trace).layer("SurfaceView", 3).hasBufferOrientation(Transform.ROT_90.value)
+ }
+
+ @Test
+ fun testRejectedBuffersAreReleased() {
+ val bufferSize = Point(300, 200)
+ val trace = withTrace {
+ for (count in 0 until 5) {
+ it.drawFrame()
+ assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed((count * 3) + 1L,
+ 500 /* ms */), 0)
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize,
+ R8G8B8A8_UNORM)
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+ // Change buffer size and set scaling mode to freeze
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0),
+ R8G8B8A8_UNORM)
+
+ // first dequeued buffer does not have the new size so it should be rejected.
+ it.mSurfaceProxy.SurfaceQueueBuffer(0)
+ it.mSurfaceProxy.SurfaceSetScalingMode(ScalingMode.SCALE_TO_WINDOW)
+ it.mSurfaceProxy.SurfaceQueueBuffer(1)
+ assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed((count * 3) + 3L,
+ 500 /* ms */), 0)
+ }
+ }
+
+ for (count in 0 until 5) {
+ assertThat(trace).layer("SurfaceView", (count * 3) + 1L)
+ .hasBufferSize(defaultBufferSize)
+ assertThat(trace).layer("SurfaceView", (count * 3) + 2L)
+ .doesNotExist()
+ assertThat(trace).layer("SurfaceView", (count * 3) + 3L)
+ .hasBufferSize(bufferSize)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt
new file mode 100644
index 0000000..03f8c05
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.graphics.Point
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode
+import com.android.test.SurfaceViewBufferTestBase.Companion.Transform
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) {
+ @Test
+ fun testSetBuffersGeometry_0x0_resetsBufferSize() {
+ val trace = withTrace {
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0,
+ R8G8B8A8_UNORM)
+ it.mSurfaceProxy.ANativeWindowLock()
+ it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+ it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */)
+ }
+
+ // verify buffer size is reset to default buffer size
+ assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+ }
+
+ @Test
+ fun testSetBuffersGeometry_smallerThanBuffer() {
+ val bufferSize = Point(300, 200)
+ val trace = withTrace {
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize,
+ R8G8B8A8_UNORM)
+ it.drawFrame()
+ it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */)
+ }
+
+ assertThat(trace).layer("SurfaceView", 1).also {
+ it.hasBufferSize(bufferSize)
+ it.hasLayerSize(defaultBufferSize)
+ it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal)
+ }
+ }
+
+ @Test
+ fun testSetBuffersGeometry_largerThanBuffer() {
+ val bufferSize = Point(3000, 2000)
+ val trace = withTrace {
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize,
+ R8G8B8A8_UNORM)
+ it.drawFrame()
+ it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */)
+ }
+
+ assertThat(trace).layer("SurfaceView", 1).also {
+ it.hasBufferSize(bufferSize)
+ it.hasLayerSize(defaultBufferSize)
+ it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal)
+ }
+ }
+
+ @Test
+ fun testSetBufferScalingMode_freeze() {
+ val bufferSize = Point(300, 200)
+ val trace = withTrace {
+ it.drawFrame()
+ assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0)
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize,
+ R8G8B8A8_UNORM)
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+ // Change buffer size and set scaling mode to freeze
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0),
+ R8G8B8A8_UNORM)
+
+ // first dequeued buffer does not have the new size so it should be rejected.
+ it.mSurfaceProxy.SurfaceQueueBuffer(0)
+ it.mSurfaceProxy.SurfaceSetScalingMode(ScalingMode.SCALE_TO_WINDOW)
+ it.mSurfaceProxy.SurfaceQueueBuffer(1)
+ assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0)
+ }
+
+ // verify buffer size is reset to default buffer size
+ assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+ assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+ assertThat(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize)
+ }
+
+ @Test
+ fun testSetBuffersTransform_FLIP() {
+ val transforms = arrayOf(Transform.FLIP_H, Transform.FLIP_V, Transform.ROT_180).withIndex()
+ for ((index, transform) in transforms) {
+ val trace = withTrace {
+ it.mSurfaceProxy.ANativeWindowSetBuffersTransform(transform)
+ it.mSurfaceProxy.ANativeWindowLock()
+ it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+ it.mSurfaceProxy.waitUntilBufferDisplayed(index + 1L, 500 /* ms */)
+ }
+
+ assertThat(trace).layer("SurfaceView", index + 1L).also {
+ it.hasBufferSize(defaultBufferSize)
+ it.hasLayerSize(defaultBufferSize)
+ it.hasBufferOrientation(transform.value)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt
new file mode 100644
index 0000000..eac3041
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.graphics.Point
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.test.SurfaceViewBufferTestBase.Companion.Transform
+import junit.framework.Assert.assertEquals
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class InverseDisplayTransformTests(useBlastAdapter: Boolean) :
+ SurfaceTracingTestBase(useBlastAdapter) {
+ @Before
+ override fun setup() {
+ scenarioRule.getScenario().onActivity {
+ it.rotate90()
+ }
+ instrumentation.waitForIdleSync()
+ super.setup()
+ }
+
+ @Test
+ fun testSetBufferScalingMode_freeze_withInvDisplayTransform() {
+ assumeFalse("Blast does not support buffer rejection with Inv display " +
+ "transform since the only user for this hidden api is camera which does not use" +
+ "fixed scaling mode.", useBlastAdapter)
+
+ val rotatedBufferSize = Point(defaultBufferSize.y, defaultBufferSize.x)
+ val trace = withTrace {
+ // Inverse display transforms are sticky AND they are only consumed by the sf after
+ // a valid buffer has been acquired.
+ it.mSurfaceProxy.ANativeWindowSetBuffersTransform(Transform.INVERSE_DISPLAY.value)
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+ it.mSurfaceProxy.SurfaceQueueBuffer(0)
+
+ assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0)
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, rotatedBufferSize,
+ R8G8B8A8_UNORM)
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+ // Change buffer size and set scaling mode to freeze
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0),
+ R8G8B8A8_UNORM)
+
+ // first dequeued buffer does not have the new size so it should be rejected.
+ it.mSurfaceProxy.ANativeWindowSetBuffersTransform(Transform.ROT_90.value)
+ it.mSurfaceProxy.SurfaceQueueBuffer(0)
+ it.mSurfaceProxy.ANativeWindowSetBuffersTransform(0)
+ it.mSurfaceProxy.SurfaceQueueBuffer(1)
+ assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0)
+ }
+
+ // verify buffer size is reset to default buffer size
+ assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+ assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+ assertThat(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize)
+ }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt
index b1e1336..ed79054 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt
@@ -15,51 +15,80 @@
*/
package com.android.test
-import android.app.Activity
import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.graphics.Color
import android.graphics.Paint
+import android.graphics.Point
import android.graphics.Rect
import android.os.Bundle
import android.view.Gravity
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
+import android.view.View
+import android.view.WindowManager
+import android.view.cts.surfacevalidator.CapturedActivity
import android.widget.FrameLayout
import java.util.concurrent.CountDownLatch
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
-class MainActivity : Activity() {
+class MainActivity : CapturedActivity() {
val mSurfaceProxy = SurfaceProxy()
private var mSurfaceHolder: SurfaceHolder? = null
private val mDrawLock = ReentrantLock()
+ var mSurfaceView: SurfaceView? = null
val surface: Surface? get() = mSurfaceHolder?.surface
- public override fun onCreate(savedInstanceState: Bundle?) {
+ override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- addSurfaceView(Rect(0, 0, 500, 200))
+ addSurfaceView(Point(500, 200))
+ window.decorView.apply {
+ systemUiVisibility =
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN
+ }
}
- fun addSurfaceView(size: Rect): CountDownLatch {
+ override fun getCaptureDurationMs(): Long {
+ return 30000
+ }
+
+ fun addSurfaceView(size: Point): CountDownLatch {
val layout = findViewById<FrameLayout>(android.R.id.content)
val surfaceReadyLatch = CountDownLatch(1)
- val surfaceView = createSurfaceView(applicationContext, size, surfaceReadyLatch)
- layout.addView(surfaceView,
- FrameLayout.LayoutParams(size.width(), size.height(), Gravity.TOP or Gravity.LEFT)
+ mSurfaceView = createSurfaceView(applicationContext, size, surfaceReadyLatch)
+ layout.addView(mSurfaceView!!,
+ FrameLayout.LayoutParams(size.x, size.y, Gravity.TOP or Gravity.LEFT)
.also { it.setMargins(100, 100, 0, 0) })
+
return surfaceReadyLatch
}
+ fun enableSeamlessRotation() {
+ val p: WindowManager.LayoutParams = window.attributes
+ p.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+ window.attributes = p
+ }
+
+ fun rotate90() {
+ if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ } else {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
+ }
+ }
+
private fun createSurfaceView(
context: Context,
- size: Rect,
+ size: Point,
surfaceReadyLatch: CountDownLatch
): SurfaceView {
val surfaceView = SurfaceView(context)
surfaceView.setWillNotDraw(false)
- surfaceView.holder.setFixedSize(size.width(), size.height())
+ surfaceView.holder.setFixedSize(size.x, size.y)
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
mDrawLock.withLock {
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt
new file mode 100644
index 0000000..df3d30e
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.annotation.ColorInt
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import android.server.wm.WindowManagerState.getLogicalDisplaySize
+import android.view.cts.surfacevalidator.CapturedActivity
+import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase
+import android.view.cts.surfacevalidator.PixelChecker
+import android.view.cts.surfacevalidator.RectChecker
+import android.widget.FrameLayout
+import androidx.test.rule.ActivityTestRule
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import java.util.concurrent.CountDownLatch
+
+open class ScreenRecordTestBase(useBlastAdapter: Boolean) :
+ SurfaceViewBufferTestBase(useBlastAdapter) {
+ @get:Rule
+ var mActivityRule = ActivityTestRule(MainActivity::class.java)
+
+ private lateinit var mActivity: MainActivity
+
+ @Before
+ override fun setup() {
+ super.setup()
+ mActivity = mActivityRule.launchActivity(Intent())
+ lateinit var surfaceReadyLatch: CountDownLatch
+ runOnUiThread {
+ it.dismissPermissionDialog()
+ it.setLogicalDisplaySize(getLogicalDisplaySize())
+ surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
+ }
+ surfaceReadyLatch.await()
+ // sleep to finish animations
+ instrumentation.waitForIdleSync()
+ }
+
+ @After
+ override fun teardown() {
+ super.teardown()
+ mActivityRule.finishActivity()
+ }
+
+ fun runOnUiThread(predicate: (it: MainActivity) -> Unit) {
+ mActivityRule.runOnUiThread {
+ predicate(mActivity)
+ }
+ }
+
+ fun withScreenRecording(
+ boundsToCheck: Rect,
+ @ColorInt color: Int,
+ predicate: (it: MainActivity) -> Unit
+ ): CapturedActivity.TestResult {
+ val testCase = object : ISurfaceValidatorTestCase {
+ override fun getChecker(): PixelChecker = RectChecker(boundsToCheck, color)
+ override fun start(context: Context, parent: FrameLayout) {
+ predicate(mActivity)
+ }
+ override fun end() { /* do nothing */ }
+ }
+
+ return mActivity.runTest(testCase)
+ }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt
new file mode 100644
index 0000000..996a1d3
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.graphics.Color
+import android.graphics.Rect
+import android.os.SystemClock
+import android.view.cts.surfacevalidator.PixelColor
+import junit.framework.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class SharedBufferModeScreenRecordTests(useBlastAdapter: Boolean) :
+ ScreenRecordTestBase(useBlastAdapter) {
+
+ /** When auto refresh is set, surface flinger will wake up and refresh the display presenting
+ * the latest content in the buffer.
+ */
+ @Test
+ fun testAutoRefresh() {
+ var svBounds = Rect()
+ runOnUiThread {
+ assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true))
+ assertEquals(0, it.mSurfaceProxy.NativeWindowSetAutoRefresh(true))
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */))
+ it.mSurfaceProxy.SurfaceQueueBuffer(0, false /* freeSlot */)
+ assertEquals(0,
+ it.mSurfaceProxy.waitUntilBufferDisplayed(1, 5000 /* ms */))
+
+ svBounds = Rect(0, 0, it.mSurfaceView!!.width, it.mSurfaceView!!.height)
+ val position = Rect()
+ it.mSurfaceView!!.getBoundsOnScreen(position)
+ svBounds.offsetTo(position.left, position.top)
+
+ // wait for buffers from other layers to be latched and transactions to be processed before
+ // updating the buffer
+ SystemClock.sleep(4000)
+ }
+
+ val result = withScreenRecording(svBounds, PixelColor.RED) {
+ it.mSurfaceProxy.drawBuffer(0, Color.RED)
+ }
+ val failRatio = 1.0f * result.failFrames / (result.failFrames + result.passFrames)
+
+ assertTrue("Error: " + result.failFrames +
+ " incorrect frames observed (out of " + (result.failFrames + result.passFrames) +
+ " frames)", failRatio < 0.05)
+ assertTrue("Error: Did not receive sufficient frame updates expected: >1000 actual:" +
+ result.passFrames, result.passFrames > 1000)
+ }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt
new file mode 100644
index 0000000..ae662506
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.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.test
+
+import android.graphics.Color
+import android.graphics.Rect
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class SharedBufferModeTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) {
+ /** Sanity test to check each buffer is presented if its submitted with enough delay
+ * for SF to present the buffers. */
+ @Test
+ fun testCanPresentBuffers() {
+ val numFrames = 15L
+ val trace = withTrace {
+ assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true))
+ for (i in 1..numFrames) {
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */))
+ it.mSurfaceProxy.SurfaceQueueBuffer(0)
+ assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(i, 5000 /* ms */))
+ }
+ }
+
+ assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+ }
+
+ /** Submit buffers as fast as possible testing that we are not blocked when dequeuing the buffer
+ * by setting the dequeue timeout to 1ms and checking that we present the newest buffer. */
+ @Test
+ fun testFastQueueBuffers() {
+ val numFrames = 15L
+ val trace = withTrace {
+ assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true))
+ for (i in 1..numFrames) {
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */))
+ it.mSurfaceProxy.SurfaceQueueBuffer(0)
+ }
+ assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 5000 /* ms */))
+ }
+
+ assertThat(trace).hasFrameSequence("SurfaceView", numFrames..numFrames)
+ }
+
+ /** Keep overwriting the buffer without queuing buffers and check that we present the latest
+ * buffer content. */
+ @Test
+ fun testAutoRefresh() {
+ var svBounds = Rect()
+ runOnUiThread {
+ assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true))
+ assertEquals(0, it.mSurfaceProxy.NativeWindowSetAutoRefresh(true))
+ assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */))
+ it.mSurfaceProxy.SurfaceQueueBuffer(0, false /* freeSlot */)
+ assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(1, 5000 /* ms */))
+
+ svBounds = Rect(0, 0, it.mSurfaceView!!.width, it.mSurfaceView!!.height)
+ val position = Rect()
+ it.mSurfaceView!!.getBoundsOnScreen(position)
+ svBounds.offsetTo(position.left, position.top)
+ }
+
+ runOnUiThread {
+ it.mSurfaceProxy.drawBuffer(0, Color.RED)
+ checkPixels(svBounds, Color.RED)
+ it.mSurfaceProxy.drawBuffer(0, Color.GREEN)
+ checkPixels(svBounds, Color.GREEN)
+ it.mSurfaceProxy.drawBuffer(0, Color.BLUE)
+ checkPixels(svBounds, Color.BLUE)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
index 884aae4..cfbd3ac 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
@@ -16,17 +16,46 @@
package com.android.test
+import android.annotation.ColorInt
+import android.graphics.Color
+import android.graphics.Point
+import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode
+import com.android.test.SurfaceViewBufferTestBase.Companion.Transform
+
class SurfaceProxy {
init {
System.loadLibrary("surface_jni")
}
external fun setSurface(surface: Any)
- external fun waitUntilBufferDisplayed(frameNumber: Int, timeoutSec: Int)
+ external fun waitUntilBufferDisplayed(frameNumber: Long, timeoutMs: Int): Int
external fun draw()
+ fun drawBuffer(slot: Int, @ColorInt c: Int) {
+ drawBuffer(slot, intArrayOf(Color.red(c), Color.green(c), Color.blue(c), Color.alpha(c)))
+ }
+ external fun drawBuffer(slot: Int, color: IntArray)
// android/native_window.h functions
external fun ANativeWindowLock()
external fun ANativeWindowUnlockAndPost()
+ fun ANativeWindowSetBuffersGeometry(surface: Any, size: Point, format: Int) {
+ ANativeWindowSetBuffersGeometry(surface, size.x, size.y, format)
+ }
external fun ANativeWindowSetBuffersGeometry(surface: Any, width: Int, height: Int, format: Int)
+ fun ANativeWindowSetBuffersTransform(transform: Transform) {
+ ANativeWindowSetBuffersTransform(transform.value)
+ }
+ external fun ANativeWindowSetBuffersTransform(transform: Int)
+
+ // gui/Surface.h functions
+ fun SurfaceSetScalingMode(scalingMode: ScalingMode) {
+ SurfaceSetScalingMode(scalingMode.ordinal)
+ }
+ external fun SurfaceSetScalingMode(scalingMode: Int)
+ external fun SurfaceDequeueBuffer(slot: Int, timeoutMs: Int): Int
+ external fun SurfaceCancelBuffer(slot: Int)
+ external fun SurfaceQueueBuffer(slot: Int, freeSlot: Boolean = true)
+ external fun NativeWindowSetBufferCount(count: Int): Int
+ external fun NativeWindowSetSharedBufferMode(shared: Boolean): Int
+ external fun NativeWindowSetAutoRefresh(autoRefresh: Boolean): Int
}
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt
new file mode 100644
index 0000000..cd4b385
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.annotation.ColorInt
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Rect
+import android.util.Log
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import com.android.server.wm.flicker.monitor.LayersTraceMonitor
+import com.android.server.wm.flicker.monitor.withSFTracing
+import com.android.server.wm.flicker.traces.layers.LayersTrace
+import junit.framework.Assert
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import java.io.FileOutputStream
+import java.io.IOException
+import java.util.concurrent.CountDownLatch
+
+open class SurfaceTracingTestBase(useBlastAdapter: Boolean) :
+ SurfaceViewBufferTestBase(useBlastAdapter) {
+ @get:Rule
+ var scenarioRule: ActivityScenarioRule<MainActivity> =
+ ActivityScenarioRule<MainActivity>(MainActivity::class.java)
+
+ @Before
+ override fun setup() {
+ super.setup()
+ stopLayerTrace()
+ addSurfaceView()
+ }
+
+ @After
+ override fun teardown() {
+ super.teardown()
+ scenarioRule.getScenario().close()
+ }
+
+ fun withTrace(predicate: (it: MainActivity) -> Unit): LayersTrace {
+ return withSFTracing(instrumentation, TRACE_FLAGS) {
+ scenarioRule.getScenario().onActivity {
+ predicate(it)
+ }
+ }
+ }
+
+ fun runOnUiThread(predicate: (it: MainActivity) -> Unit) {
+ scenarioRule.getScenario().onActivity {
+ predicate(it)
+ }
+ }
+
+ private fun addSurfaceView() {
+ lateinit var surfaceReadyLatch: CountDownLatch
+ scenarioRule.getScenario().onActivity {
+ surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
+ }
+ surfaceReadyLatch.await()
+ // sleep to finish animations
+ instrumentation.waitForIdleSync()
+ }
+
+ private fun stopLayerTrace() {
+ val tmpDir = instrumentation.targetContext.dataDir.toPath()
+ LayersTraceMonitor(tmpDir).stop()
+ }
+
+ fun checkPixels(bounds: Rect, @ColorInt color: Int) {
+ val screenshot = instrumentation.getUiAutomation().takeScreenshot()
+ val pixels = IntArray(screenshot.width * screenshot.height)
+ screenshot.getPixels(pixels, 0, screenshot.width, 0, 0, screenshot.width, screenshot.height)
+ for (i in bounds.left + 10..bounds.right - 10) {
+ for (j in bounds.top + 10..bounds.bottom - 10) {
+ val actualColor = pixels[j * screenshot.width + i]
+ if (actualColor != color) {
+ val screenshotPath = instrumentation.targetContext
+ .getExternalFilesDir(null)?.resolve("screenshot.png")
+ try {
+ FileOutputStream(screenshotPath).use { out ->
+ screenshot.compress(Bitmap.CompressFormat.PNG, 100, out)
+ }
+ Log.e("SurfaceViewBufferTests", "Bitmap written to $screenshotPath")
+ } catch (e: IOException) {
+ Log.e("SurfaceViewBufferTests", "Error writing bitmap to file", e)
+ }
+ }
+ Assert.assertEquals("Checking $bounds found mismatch $i,$j",
+ Color.valueOf(color), Color.valueOf(actualColor))
+ }
+ }
+ }
+
+ private companion object {
+ private const val TRACE_FLAGS =
+ (1 shl 0) or (1 shl 5) or (1 shl 6) // TRACE_CRITICAL | TRACE_BUFFERS | TRACE_SYNC
+ }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt
deleted file mode 100644
index b48a91d..0000000
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt
+++ /dev/null
@@ -1,185 +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.test
-
-import android.app.Instrumentation
-import android.graphics.Rect
-import android.provider.Settings
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.monitor.LayersTraceMonitor
-import com.android.server.wm.flicker.monitor.withSFTracing
-import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import java.util.concurrent.CountDownLatch
-import kotlin.properties.Delegates
-
-@RunWith(Parameterized::class)
-class SurfaceViewBufferTest(val useBlastAdapter: Boolean) {
- private var mInitialUseBlastConfig by Delegates.notNull<Int>()
-
- @get:Rule
- var scenarioRule: ActivityScenarioRule<MainActivity> =
- ActivityScenarioRule<MainActivity>(MainActivity::class.java)
-
- protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- val defaultBufferSize = Rect(0, 0, 640, 480)
-
- @Before
- fun setup() {
- mInitialUseBlastConfig = Settings.Global.getInt(instrumentation.context.contentResolver,
- "use_blast_adapter_sv", 0)
- val enable = if (useBlastAdapter) 1 else 0
- Settings.Global.putInt(instrumentation.context.contentResolver, "use_blast_adapter_sv",
- enable)
- val tmpDir = instrumentation.targetContext.dataDir.toPath()
- LayersTraceMonitor(tmpDir).stop()
-
- lateinit var surfaceReadyLatch: CountDownLatch
- scenarioRule.getScenario().onActivity {
- surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
- }
- surfaceReadyLatch.await()
- }
-
- @After
- fun teardown() {
- scenarioRule.getScenario().close()
- Settings.Global.putInt(instrumentation.context.contentResolver,
- "use_blast_adapter_sv", mInitialUseBlastConfig)
- }
-
- @Test
- fun testSetBuffersGeometry_0x0_resetsBufferSize() {
- val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
- scenarioRule.getScenario().onActivity {
- it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0,
- R8G8B8A8_UNORM)
- it.mSurfaceProxy.ANativeWindowLock()
- it.mSurfaceProxy.ANativeWindowUnlockAndPost()
- it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
- }
- }
-
- // verify buffer size is reset to default buffer size
- assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
- }
-
- @Test
- fun testSetBuffersGeometry_0x0_rejectsBuffer() {
- val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
- scenarioRule.getScenario().onActivity {
- it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
- R8G8B8A8_UNORM)
- it.mSurfaceProxy.ANativeWindowLock()
- it.mSurfaceProxy.ANativeWindowUnlockAndPost()
- it.mSurfaceProxy.ANativeWindowLock()
- it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, R8G8B8A8_UNORM)
- // Submit buffer one with a different size which should be rejected
- it.mSurfaceProxy.ANativeWindowUnlockAndPost()
-
- // submit a buffer with the default buffer size
- it.mSurfaceProxy.ANativeWindowLock()
- it.mSurfaceProxy.ANativeWindowUnlockAndPost()
- it.mSurfaceProxy.waitUntilBufferDisplayed(3, 1 /* sec */)
- }
- }
- // Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE
- assertThat(trace).layer("SurfaceView", 2).doesNotExist()
-
- // Verify the next buffer is submitted with the correct size
- assertThat(trace).layer("SurfaceView", 3).also {
- it.hasBufferSize(defaultBufferSize)
- it.hasScalingMode(0 /* NATIVE_WINDOW_SCALING_MODE_FREEZE */)
- }
- }
-
- @Test
- fun testSetBuffersGeometry_smallerThanBuffer() {
- val bufferSize = Rect(0, 0, 300, 200)
- val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
- scenarioRule.getScenario().onActivity {
- it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(),
- bufferSize.height(), R8G8B8A8_UNORM)
- it.drawFrame()
- it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
- }
- }
-
- assertThat(trace).layer("SurfaceView", 1).also {
- it.hasBufferSize(bufferSize)
- it.hasLayerSize(defaultBufferSize)
- it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */)
- }
- }
-
- @Test
- fun testSetBuffersGeometry_largerThanBuffer() {
- val bufferSize = Rect(0, 0, 3000, 2000)
- val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
- scenarioRule.getScenario().onActivity {
- it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(),
- bufferSize.height(), R8G8B8A8_UNORM)
- it.drawFrame()
- it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
- }
- }
-
- assertThat(trace).layer("SurfaceView", 1).also {
- it.hasBufferSize(bufferSize)
- it.hasLayerSize(defaultBufferSize)
- it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */)
- }
- }
-
- /** Submit buffers as fast as possible and make sure they are queued */
- @Test
- fun testQueueBuffers() {
- val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
- scenarioRule.getScenario().onActivity {
- it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
- R8G8B8A8_UNORM)
- for (i in 0..100) {
- it.mSurfaceProxy.ANativeWindowLock()
- it.mSurfaceProxy.ANativeWindowUnlockAndPost()
- }
- it.mSurfaceProxy.waitUntilBufferDisplayed(100, 1 /* sec */)
- }
- }
- for (frameNumber in 1..100) {
- assertThat(trace).layer("SurfaceView", frameNumber.toLong())
- }
- }
-
- companion object {
- private const val TRACE_FLAGS = 0x1 // TRACE_CRITICAL
- private const val R8G8B8A8_UNORM = 1
-
- @JvmStatic
- @Parameterized.Parameters(name = "blast={0}")
- fun data(): Collection<Array<Any>> {
- return listOf(
- arrayOf(false), // First test: submit buffers via bufferqueue
- arrayOf(true) // Second test: submit buffers via blast adapter
- )
- }
- }
-}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt
new file mode 100644
index 0000000..093c312
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt
@@ -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.test
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.provider.Settings
+import androidx.test.InstrumentationRegistry
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.rules.TestName
+import org.junit.runners.Parameterized
+import kotlin.properties.Delegates
+
+open class SurfaceViewBufferTestBase(val useBlastAdapter: Boolean) {
+ private var mInitialBlastConfig by Delegates.notNull<Boolean>()
+
+ val instrumentation: Instrumentation
+ get() = InstrumentationRegistry.getInstrumentation()
+
+ @get:Rule
+ var mName = TestName()
+
+ @Before
+ open fun setup() {
+ mInitialBlastConfig = getBlastAdapterSvEnabled()
+ setBlastAdapterSvEnabled(useBlastAdapter)
+ }
+
+ @After
+ open fun teardown() {
+ setBlastAdapterSvEnabled(mInitialBlastConfig)
+ }
+
+ private fun getBlastAdapterSvEnabled(): Boolean {
+ return Settings.Global.getInt(instrumentation.context.contentResolver,
+ "use_blast_adapter_sv", 0) != 0
+ }
+
+ private fun setBlastAdapterSvEnabled(enable: Boolean) {
+ Settings.Global.putInt(instrumentation.context.contentResolver, "use_blast_adapter_sv",
+ if (enable) 1 else 0)
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "blast={0}")
+ fun data(): Collection<Array<Any>> {
+ return listOf(
+ arrayOf(false), // First test: submit buffers via bufferqueue
+ arrayOf(true) // Second test: submit buffers via blast adapter
+ )
+ }
+
+ const val R8G8B8A8_UNORM = 1
+ val defaultBufferSize = Point(640, 480)
+
+ // system/window.h definitions
+ enum class ScalingMode() {
+ FREEZE, // = 0
+ SCALE_TO_WINDOW, // =1
+ SCALE_CROP, // = 2
+ NO_SCALE_CROP // = 3
+ }
+
+ // system/window.h definitions
+ enum class Transform(val value: Int) {
+ /* flip source image horizontally */
+ FLIP_H(1),
+ /* flip source image vertically */
+ FLIP_V(2),
+ /* rotate source image 90 degrees clock-wise, and is applied after TRANSFORM_FLIP_{H|V} */
+ ROT_90(4),
+ /* rotate source image 180 degrees */
+ ROT_180(3),
+ /* rotate source image 270 degrees clock-wise */
+ ROT_270(7),
+ /* transforms source by the inverse transform of the screen it is displayed onto. This
+ * transform is applied last */
+ INVERSE_DISPLAY(0x08)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
index abccd6c..fe9deae 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
@@ -16,6 +16,7 @@
package com.android.test.taskembed
import android.app.Instrumentation
+import android.graphics.Point
import android.graphics.Rect
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.platform.app.InstrumentationRegistry
@@ -93,13 +94,15 @@
// verify buffer size should be changed to expected values.
assertThat(trace).layer(FIRST_ACTIVITY, frame).also {
- it.hasLayerSize(firstBounds)
- it.hasBufferSize(firstBounds)
+ val firstTaskSize = Point(firstBounds.width(), firstBounds.height())
+ it.hasLayerSize(firstTaskSize)
+ it.hasBufferSize(firstTaskSize)
}
assertThat(trace).layer(SECOND_ACTIVITY, frame).also {
- it.hasLayerSize(secondBounds)
- it.hasBufferSize(secondBounds)
+ val secondTaskSize = Point(secondBounds.width(), secondBounds.height())
+ it.hasLayerSize(secondTaskSize)
+ it.hasBufferSize(secondTaskSize)
}
}
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index dba1856e..70f6386 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -200,7 +200,8 @@
nsInstrumentation.addHttpResponse(HttpResponse(httpProbeUrl, responseCode = 204))
nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204))
- val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), context)
+ val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), null /* ncTemplate */,
+ context)
networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
na.addCapability(NET_CAPABILITY_INTERNET)
@@ -238,7 +239,7 @@
val lp = LinkProperties()
lp.captivePortalApiUrl = Uri.parse(apiUrl)
- val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, lp, context)
+ val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, lp, null /* ncTemplate */, context)
networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
na.addCapability(NET_CAPABILITY_INTERNET)
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 85704d0..2a24d1a 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -72,12 +72,12 @@
private long mKeepaliveResponseDelay = 0L;
private Integer mExpectedKeepaliveSlot = null;
- public NetworkAgentWrapper(int transport, LinkProperties linkProperties, Context context)
- throws Exception {
+ public NetworkAgentWrapper(int transport, LinkProperties linkProperties,
+ NetworkCapabilities ncTemplate, Context context) throws Exception {
final int type = transportToLegacyType(transport);
final String typeName = ConnectivityManager.getNetworkTypeName(type);
mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
- mNetworkCapabilities = new NetworkCapabilities();
+ mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities();
mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
mNetworkCapabilities.addTransportType(transport);
switch (transport) {
diff --git a/tests/net/integration/util/com/android/server/TestNetIdManager.kt b/tests/net/integration/util/com/android/server/TestNetIdManager.kt
index eb290dc..938a694 100644
--- a/tests/net/integration/util/com/android/server/TestNetIdManager.kt
+++ b/tests/net/integration/util/com/android/server/TestNetIdManager.kt
@@ -35,4 +35,5 @@
private val nextId = AtomicInteger(MAX_NET_ID)
override fun reserveNetId() = nextId.decrementAndGet()
override fun releaseNetId(id: Int) = Unit
+ fun peekNextNetId() = nextId.get() - 1
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index b78f0e2..4bb1317 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -322,6 +322,7 @@
private static final String MOBILE_IFNAME = "test_rmnet_data0";
private static final String WIFI_IFNAME = "test_wlan0";
private static final String WIFI_WOL_IFNAME = "test_wlan_wol";
+ private static final String VPN_IFNAME = "tun10042";
private static final String TEST_PACKAGE_NAME = "com.android.test.package";
private static final String[] EMPTY_STRING_ARRAY = new String[0];
@@ -339,6 +340,7 @@
private INetworkPolicyListener mPolicyListener;
private WrappedMultinetworkPolicyTracker mPolicyTracker;
private HandlerThread mAlarmManagerThread;
+ private TestNetIdManager mNetIdManager;
@Mock IIpConnectivityMetrics mIpConnectivityMetrics;
@Mock IpConnectivityMetrics.Logger mMetricsService;
@@ -617,12 +619,17 @@
private String mRedirectUrl;
TestNetworkAgentWrapper(int transport) throws Exception {
- this(transport, new LinkProperties());
+ this(transport, new LinkProperties(), null);
}
TestNetworkAgentWrapper(int transport, LinkProperties linkProperties)
throws Exception {
- super(transport, linkProperties, mServiceContext);
+ this(transport, linkProperties, null);
+ }
+
+ private TestNetworkAgentWrapper(int transport, LinkProperties linkProperties,
+ NetworkCapabilities ncTemplate) throws Exception {
+ super(transport, linkProperties, ncTemplate, mServiceContext);
// Waits for the NetworkAgent to be registered, which includes the creation of the
// NetworkMonitor.
@@ -1017,46 +1024,36 @@
}
}
+ private Set<UidRange> uidRangesForUid(int uid) {
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ ranges.add(new UidRange(uid, uid));
+ return ranges;
+ }
+
private static Looper startHandlerThreadAndReturnLooper() {
final HandlerThread handlerThread = new HandlerThread("MockVpnThread");
handlerThread.start();
return handlerThread.getLooper();
}
- private class MockVpn extends Vpn {
- // TODO : the interactions between this mock and the mock network agent are too
- // hard to get right at this moment, because it's unclear in which case which
- // target needs to get a method call or both, and in what order. It's because
- // MockNetworkAgent wants to manage its own NetworkCapabilities, but the Vpn
- // parent class of MockVpn agent wants that responsibility.
- // That being said inside the test it should be possible to make the interactions
- // harder to get wrong with precise speccing, judicious comments, helper methods
- // and a few sprinkled assertions.
-
- private boolean mConnected = false;
+ private class MockVpn extends Vpn implements TestableNetworkCallback.HasNetwork {
// Careful ! This is different from mNetworkAgent, because MockNetworkAgent does
// not inherit from NetworkAgent.
private TestNetworkAgentWrapper mMockNetworkAgent;
- private int mVpnType = VpnManager.TYPE_VPN_SERVICE;
+ private boolean mAgentRegistered = false;
+ private int mVpnType = VpnManager.TYPE_VPN_SERVICE;
private VpnInfo mVpnInfo;
- private Network[] mUnderlyingNetworks;
public MockVpn(int userId) {
super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
userId, mock(KeyStore.class));
- }
-
- public void setNetworkAgent(TestNetworkAgentWrapper agent) {
- agent.waitForIdle(TIMEOUT_MS);
- mMockNetworkAgent = agent;
- mNetworkAgent = agent.getNetworkAgent();
- mNetworkCapabilities.set(agent.getNetworkCapabilities());
+ mConfig = new VpnConfig();
}
public void setUids(Set<UidRange> uids) {
mNetworkCapabilities.setUids(uids);
- updateCapabilities(null /* defaultNetwork */);
+ updateCapabilitiesInternal(null /* defaultNetwork */, true);
}
public void setVpnType(int vpnType) {
@@ -1064,21 +1061,13 @@
}
@Override
+ public Network getNetwork() {
+ return (mMockNetworkAgent == null) ? null : mMockNetworkAgent.getNetwork();
+ }
+
+ @Override
public int getNetId() {
- if (mMockNetworkAgent == null) {
- return NETID_UNSET;
- }
- return mMockNetworkAgent.getNetwork().netId;
- }
-
- @Override
- public boolean appliesToUid(int uid) {
- return mConnected; // Trickery to simplify testing.
- }
-
- @Override
- protected boolean isCallerEstablishedOwnerLocked() {
- return mConnected; // Similar trickery
+ return (mMockNetworkAgent == null) ? NETID_UNSET : mMockNetworkAgent.getNetwork().netId;
}
@Override
@@ -1086,41 +1075,94 @@
return mVpnType;
}
- private void connect(boolean isAlwaysMetered) {
- mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
- mConnected = true;
- mConfig = new VpnConfig();
+ private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp)
+ throws Exception {
+ if (mAgentRegistered) throw new IllegalStateException("already registered");
+ setUids(uids);
mConfig.isMetered = isAlwaysMetered;
+ mInterface = VPN_IFNAME;
+ mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp,
+ mNetworkCapabilities);
+ mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
+ mAgentRegistered = true;
+ mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
+ mNetworkAgent = mMockNetworkAgent.getNetworkAgent();
}
- public void connectAsAlwaysMetered() {
- connect(true /* isAlwaysMetered */);
+ private void registerAgent(Set<UidRange> uids) throws Exception {
+ registerAgent(false /* isAlwaysMetered */, uids, new LinkProperties());
}
- public void connect() {
- connect(false /* isAlwaysMetered */);
+ private void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
+ mMockNetworkAgent.connect(validated, hasInternet, isStrictMode);
+ }
+
+ private void connect(boolean validated) {
+ mMockNetworkAgent.connect(validated);
+ }
+
+ private TestNetworkAgentWrapper getAgent() {
+ return mMockNetworkAgent;
+ }
+
+ public void establish(LinkProperties lp, int uid, Set<UidRange> ranges, boolean validated,
+ boolean hasInternet, boolean isStrictMode) throws Exception {
+ mNetworkCapabilities.setOwnerUid(uid);
+ mNetworkCapabilities.setAdministratorUids(new int[]{uid});
+ registerAgent(false, ranges, lp);
+ connect(validated, hasInternet, isStrictMode);
+ waitForIdle();
+ }
+
+ public void establish(LinkProperties lp, int uid, Set<UidRange> ranges) throws Exception {
+ establish(lp, uid, ranges, true, true, false);
+ }
+
+ public void establishForMyUid(LinkProperties lp) throws Exception {
+ final int uid = Process.myUid();
+ establish(lp, uid, uidRangesForUid(uid), true, true, false);
+ }
+
+ public void establishForMyUid(boolean validated, boolean hasInternet, boolean isStrictMode)
+ throws Exception {
+ final int uid = Process.myUid();
+ establish(new LinkProperties(), uid, uidRangesForUid(uid), validated, hasInternet,
+ isStrictMode);
+ }
+
+ public void establishForMyUid() throws Exception {
+ establishForMyUid(new LinkProperties());
+ }
+
+ public void sendLinkProperties(LinkProperties lp) {
+ mMockNetworkAgent.sendLinkProperties(lp);
+ }
+
+ private NetworkCapabilities updateCapabilitiesInternal(Network defaultNetwork,
+ boolean sendToConnectivityService) {
+ if (!mAgentRegistered) return null;
+ super.updateCapabilities(defaultNetwork);
+ // Because super.updateCapabilities will update the capabilities of the agent but
+ // not the mock agent, the mock agent needs to know about them.
+ copyCapabilitiesToNetworkAgent(sendToConnectivityService);
+ return new NetworkCapabilities(mNetworkCapabilities);
+ }
+
+ private void copyCapabilitiesToNetworkAgent(boolean sendToConnectivityService) {
+ if (null != mMockNetworkAgent) {
+ mMockNetworkAgent.setNetworkCapabilities(mNetworkCapabilities,
+ sendToConnectivityService);
+ }
}
@Override
public NetworkCapabilities updateCapabilities(Network defaultNetwork) {
- if (!mConnected) return null;
- super.updateCapabilities(defaultNetwork);
- // Because super.updateCapabilities will update the capabilities of the agent but
- // not the mock agent, the mock agent needs to know about them.
- copyCapabilitiesToNetworkAgent();
- return new NetworkCapabilities(mNetworkCapabilities);
- }
-
- private void copyCapabilitiesToNetworkAgent() {
- if (null != mMockNetworkAgent) {
- mMockNetworkAgent.setNetworkCapabilities(mNetworkCapabilities,
- false /* sendToConnectivityService */);
- }
+ return updateCapabilitiesInternal(defaultNetwork, false);
}
public void disconnect() {
- mConnected = false;
- mConfig = null;
+ if (mMockNetworkAgent != null) mMockNetworkAgent.disconnect();
+ mAgentRegistered = false;
}
@Override
@@ -1133,18 +1175,6 @@
private synchronized void setVpnInfo(VpnInfo vpnInfo) {
mVpnInfo = vpnInfo;
}
-
- @Override
- public synchronized Network[] getUnderlyingNetworks() {
- if (mUnderlyingNetworks != null) return mUnderlyingNetworks;
-
- return super.getUnderlyingNetworks();
- }
-
- /** Don't override behavior for {@link Vpn#setUnderlyingNetworks}. */
- private synchronized void overrideUnderlyingNetworks(Network[] underlyingNetworks) {
- mUnderlyingNetworks = underlyingNetworks;
- }
}
private void mockVpn(int uid) {
@@ -1207,6 +1237,8 @@
@Before
public void setUp() throws Exception {
+ mNetIdManager = new TestNetIdManager();
+
mContext = InstrumentationRegistry.getContext();
MockitoAnnotations.initMocks(this);
@@ -1277,7 +1309,7 @@
doNothing().when(mSystemProperties).setTcpInitRwnd(anyInt());
final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class);
doReturn(mCsHandlerThread).when(deps).makeHandlerThread();
- doReturn(new TestNetIdManager()).when(deps).makeNetIdManager();
+ doReturn(mNetIdManager).when(deps).makeNetIdManager();
doReturn(mNetworkStack).when(deps).getNetworkStack();
doReturn(mSystemProperties).when(deps).getSystemProperties();
doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
@@ -1335,6 +1367,9 @@
mEthernetNetworkAgent.disconnect();
mEthernetNetworkAgent = null;
}
+ mMockVpn.disconnect();
+ waitForIdle();
+
FakeSettingsProvider.clearSettingsProvider();
mCsHandlerThread.quitSafely();
@@ -3218,20 +3253,12 @@
waitForIdle();
assertEquals(null, mCm.getActiveNetwork());
- final int uid = Process.myUid();
- final TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true);
- mMockVpn.connect();
- defaultNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
+ mMockVpn.establishForMyUid();
+ defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
- vpnNetworkAgent.disconnect();
- defaultNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+ mMockVpn.disconnect();
+ defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
waitForIdle();
assertEquals(null, mCm.getActiveNetwork());
}
@@ -4808,13 +4835,52 @@
mCm.unregisterNetworkCallback(networkCallback);
}
+ private <T> void assertSameElementsNoDuplicates(T[] expected, T[] actual) {
+ // Easier to implement than a proper "assertSameElements" method that also correctly deals
+ // with duplicates.
+ final String msg = Arrays.toString(expected) + " != " + Arrays.toString(actual);
+ assertEquals(msg, expected.length, actual.length);
+ Set expectedSet = new ArraySet<>(Arrays.asList(expected));
+ assertEquals("expected contains duplicates", expectedSet.size(), expected.length);
+ // actual cannot have duplicates because it's the same length and has the same elements.
+ Set actualSet = new ArraySet<>(Arrays.asList(actual));
+ assertEquals(expectedSet, actualSet);
+ }
+
+ private void expectForceUpdateIfaces(Network[] networks, String defaultIface,
+ Integer vpnUid, String vpnIfname, String[] underlyingIfaces) throws Exception {
+ ArgumentCaptor<Network[]> networksCaptor = ArgumentCaptor.forClass(Network[].class);
+ ArgumentCaptor<VpnInfo[]> vpnInfosCaptor = ArgumentCaptor.forClass(VpnInfo[].class);
+
+ verify(mStatsService, atLeastOnce()).forceUpdateIfaces(networksCaptor.capture(),
+ any(NetworkState[].class), eq(defaultIface), vpnInfosCaptor.capture());
+
+ assertSameElementsNoDuplicates(networksCaptor.getValue(), networks);
+
+ VpnInfo[] infos = vpnInfosCaptor.getValue();
+ if (vpnUid != null) {
+ assertEquals("Should have exactly one VPN:", 1, infos.length);
+ VpnInfo info = infos[0];
+ assertEquals("Unexpected VPN owner:", (int) vpnUid, info.ownerUid);
+ assertEquals("Unexpected VPN interface:", vpnIfname, info.vpnIface);
+ assertSameElementsNoDuplicates(underlyingIfaces, info.underlyingIfaces);
+ } else {
+ assertEquals(0, infos.length);
+ return;
+ }
+ }
+
+ private void expectForceUpdateIfaces(Network[] networks, String defaultIface) throws Exception {
+ expectForceUpdateIfaces(networks, defaultIface, null, null, new String[0]);
+ }
+
@Test
public void testStatsIfacesChanged() throws Exception {
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
- Network[] onlyCell = new Network[] {mCellNetworkAgent.getNetwork()};
- Network[] onlyWifi = new Network[] {mWiFiNetworkAgent.getNetwork()};
+ final Network[] onlyCell = new Network[] {mCellNetworkAgent.getNetwork()};
+ final Network[] onlyWifi = new Network[] {mWiFiNetworkAgent.getNetwork()};
LinkProperties cellLp = new LinkProperties();
cellLp.setInterfaceName(MOBILE_IFNAME);
@@ -4825,9 +4891,7 @@
mCellNetworkAgent.connect(false);
mCellNetworkAgent.sendLinkProperties(cellLp);
waitForIdle();
- verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
- eq(new VpnInfo[0]));
+ expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME);
reset(mStatsService);
// Default network switch should update ifaces.
@@ -4835,32 +4899,24 @@
mWiFiNetworkAgent.sendLinkProperties(wifiLp);
waitForIdle();
assertEquals(wifiLp, mService.getActiveLinkProperties());
- verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(eq(onlyWifi), any(NetworkState[].class), eq(WIFI_IFNAME),
- eq(new VpnInfo[0]));
+ expectForceUpdateIfaces(onlyWifi, WIFI_IFNAME);
reset(mStatsService);
// Disconnect should update ifaces.
mWiFiNetworkAgent.disconnect();
waitForIdle();
- verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class),
- eq(MOBILE_IFNAME), eq(new VpnInfo[0]));
+ expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME);
reset(mStatsService);
// Metered change should update ifaces
mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
waitForIdle();
- verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
- eq(new VpnInfo[0]));
+ expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME);
reset(mStatsService);
mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
waitForIdle();
- verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
- eq(new VpnInfo[0]));
+ expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME);
reset(mStatsService);
// Captive portal change shouldn't update ifaces
@@ -4874,9 +4930,102 @@
// Roaming change should update ifaces
mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
waitForIdle();
- verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
- eq(new VpnInfo[0]));
+ expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME);
+ reset(mStatsService);
+
+ // Test VPNs.
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(VPN_IFNAME);
+
+ mMockVpn.establishForMyUid(lp);
+
+ final Network[] cellAndVpn = new Network[] {
+ mCellNetworkAgent.getNetwork(), mMockVpn.getNetwork()};
+ Network[] cellAndWifi = new Network[] {
+ mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork()};
+
+ // A VPN with default (null) underlying networks sets the underlying network's interfaces...
+ expectForceUpdateIfaces(cellAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
+ new String[]{MOBILE_IFNAME});
+
+ // ...and updates them as the default network switches.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false);
+ mWiFiNetworkAgent.sendLinkProperties(wifiLp);
+ final Network[] wifiAndVpn = new Network[] {
+ mWiFiNetworkAgent.getNetwork(), mMockVpn.getNetwork()};
+ cellAndWifi = new Network[] {
+ mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork()};
+
+ waitForIdle();
+ assertEquals(wifiLp, mService.getActiveLinkProperties());
+ expectForceUpdateIfaces(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME,
+ new String[]{WIFI_IFNAME});
+ reset(mStatsService);
+
+ // A VPN that sets its underlying networks passes the underlying interfaces, and influences
+ // the default interface sent to NetworkStatsService by virtue of applying to the system
+ // server UID (or, in this test, to the test's UID). This is the reason for sending
+ // MOBILE_IFNAME even though the default network is wifi.
+ // TODO: fix this to pass in the actual default network interface. Whether or not the VPN
+ // applies to the system server UID should not have any bearing on network stats.
+ mService.setUnderlyingNetworksForVpn(onlyCell);
+ waitForIdle();
+ expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
+ new String[]{MOBILE_IFNAME});
+ reset(mStatsService);
+
+ mService.setUnderlyingNetworksForVpn(cellAndWifi);
+ waitForIdle();
+ expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
+ new String[]{MOBILE_IFNAME, WIFI_IFNAME});
+ reset(mStatsService);
+
+ // If an underlying network disconnects, that interface should no longer be underlying.
+ // This doesn't actually work because disconnectAndDestroyNetwork only notifies
+ // NetworkStatsService before the underlying network is actually removed. So the underlying
+ // network will only be removed if notifyIfacesChangedForNetworkStats is called again. This
+ // could result in incorrect data usage measurements if the interface used by the
+ // disconnected network is reused by a system component that does not register an agent for
+ // it (e.g., tethering).
+ mCellNetworkAgent.disconnect();
+ waitForIdle();
+ assertNull(mService.getLinkProperties(mCellNetworkAgent.getNetwork()));
+ expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
+ new String[]{MOBILE_IFNAME, WIFI_IFNAME});
+
+ // Confirm that we never tell NetworkStatsService that cell is no longer the underlying
+ // network for the VPN...
+ verify(mStatsService, never()).forceUpdateIfaces(any(Network[].class),
+ any(NetworkState[].class), any() /* anyString() doesn't match null */,
+ argThat(infos -> infos[0].underlyingIfaces.length == 1
+ && WIFI_IFNAME.equals(infos[0].underlyingIfaces[0])));
+ verifyNoMoreInteractions(mStatsService);
+ reset(mStatsService);
+
+ // ... but if something else happens that causes notifyIfacesChangedForNetworkStats to be
+ // called again, it does. For example, connect Ethernet, but with a low score, such that it
+ // does not become the default network.
+ mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+ mEthernetNetworkAgent.adjustScore(-40);
+ mEthernetNetworkAgent.connect(false);
+ waitForIdle();
+ verify(mStatsService).forceUpdateIfaces(any(Network[].class),
+ any(NetworkState[].class), any() /* anyString() doesn't match null */,
+ argThat(vpnInfos -> vpnInfos[0].underlyingIfaces.length == 1
+ && WIFI_IFNAME.equals(vpnInfos[0].underlyingIfaces[0])));
+ mEthernetNetworkAgent.disconnect();
+ reset(mStatsService);
+
+ // When a VPN declares no underlying networks (i.e., no connectivity), getAllVpnInfo
+ // does not return the VPN, so CS does not pass it to NetworkStatsService. This causes
+ // NetworkStatsFactory#adjustForTunAnd464Xlat not to attempt any VPN data migration, which
+ // is probably a performance improvement (though it's very unlikely that a VPN would declare
+ // no underlying networks).
+ // Also, for the same reason as above, the active interface passed in is null.
+ mService.setUnderlyingNetworksForVpn(new Network[0]);
+ waitForIdle();
+ expectForceUpdateIfaces(wifiAndVpn, null);
reset(mStatsService);
}
@@ -5232,6 +5381,58 @@
}
@Test
+ public void testVpnConnectDisconnectUnderlyingNetwork() throws Exception {
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .removeCapability(NET_CAPABILITY_NOT_VPN).build();
+
+ mCm.registerNetworkCallback(request, callback);
+
+ // Bring up a VPN that specifies an underlying network that does not exist yet.
+ // Note: it's sort of meaningless for a VPN app to declare a network that doesn't exist yet,
+ // (and doing so is difficult without using reflection) but it's good to test that the code
+ // behaves approximately correctly.
+ mMockVpn.establishForMyUid(false, true, false);
+ final Network wifiNetwork = new Network(mNetIdManager.peekNextNetId());
+ mService.setUnderlyingNetworksForVpn(new Network[]{wifiNetwork});
+ callback.expectAvailableCallbacksUnvalidated(mMockVpn);
+ assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
+ .hasTransport(TRANSPORT_VPN));
+ assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
+ .hasTransport(TRANSPORT_WIFI));
+
+ // Make that underlying network connect, and expect to see its capabilities immediately
+ // reflected in the VPN's capabilities.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ assertEquals(wifiNetwork, mWiFiNetworkAgent.getNetwork());
+ mWiFiNetworkAgent.connect(false);
+ // TODO: the callback for the VPN happens before any callbacks are called for the wifi
+ // network that has just connected. There appear to be two issues here:
+ // 1. The VPN code will accept an underlying network as soon as getNetworkCapabilities() for
+ // it returns non-null (which happens very early, during handleRegisterNetworkAgent).
+ // This is not correct because that that point the network is not connected and cannot
+ // pass any traffic.
+ // 2. When a network connects, updateNetworkInfo propagates underlying network capabilities
+ // before rematching networks.
+ // Given that this scenario can't really happen, this is probably fine for now.
+ callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
+ .hasTransport(TRANSPORT_VPN));
+ assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
+ .hasTransport(TRANSPORT_WIFI));
+
+ // Disconnect the network, and expect to see the VPN capabilities change accordingly.
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ callback.expectCapabilitiesThat(mMockVpn, (nc) ->
+ nc.getTransportTypes().length == 1 && nc.hasTransport(TRANSPORT_VPN));
+
+ mMockVpn.disconnect();
+ mCm.unregisterNetworkCallback(callback);
+ }
+
+ @Test
public void testVpnNetworkActive() throws Exception {
final int uid = Process.myUid();
@@ -5265,42 +5466,38 @@
vpnNetworkCallback.assertNoCallback();
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
- final TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.setUids(ranges);
+ final Set<UidRange> ranges = uidRangesForUid(uid);
+ mMockVpn.registerAgent(ranges);
+
// VPN networks do not satisfy the default request and are automatically validated
// by NetworkMonitor
assertFalse(NetworkMonitorUtils.isValidationRequired(
- vpnNetworkAgent.getNetworkCapabilities()));
- vpnNetworkAgent.setNetworkValid(false /* isStrictMode */);
+ mMockVpn.getAgent().getNetworkCapabilities()));
+ mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */);
- vpnNetworkAgent.connect(false);
- mMockVpn.connect();
- mMockVpn.setUnderlyingNetworks(new Network[0]);
+ mMockVpn.connect(false);
+ mService.setUnderlyingNetworksForVpn(new Network[0]);
- genericNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+ genericNetworkCallback.expectAvailableCallbacksUnvalidated(mMockVpn);
genericNotVpnNetworkCallback.assertNoCallback();
wifiNetworkCallback.assertNoCallback();
- vpnNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
- defaultCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+ vpnNetworkCallback.expectAvailableCallbacksUnvalidated(mMockVpn);
+ defaultCallback.expectAvailableCallbacksUnvalidated(mMockVpn);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
- genericNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
genericNotVpnNetworkCallback.assertNoCallback();
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, nc -> null == nc.getUids());
- defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, nc -> null == nc.getUids());
+ defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
ranges.clear();
- vpnNetworkAgent.setUids(ranges);
+ mMockVpn.setUids(ranges);
- genericNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
genericNotVpnNetworkCallback.assertNoCallback();
wifiNetworkCallback.assertNoCallback();
- vpnNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+ vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
// TODO : The default network callback should actually get a LOST call here (also see the
// comment below for AVAILABLE). This is because ConnectivityService does not look at UID
@@ -5308,19 +5505,18 @@
// can't currently update their UIDs without disconnecting, so this does not matter too
// much, but that is the reason the test here has to check for an update to the
// capabilities instead of the expected LOST then AVAILABLE.
- defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
+ defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
ranges.add(new UidRange(uid, uid));
mMockVpn.setUids(ranges);
- vpnNetworkAgent.setUids(ranges);
- genericNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
+ genericNetworkCallback.expectAvailableCallbacksValidated(mMockVpn);
genericNotVpnNetworkCallback.assertNoCallback();
wifiNetworkCallback.assertNoCallback();
- vpnNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
+ vpnNetworkCallback.expectAvailableCallbacksValidated(mMockVpn);
// TODO : Here like above, AVAILABLE would be correct, but because this can't actually
// happen outside of the test, ConnectivityService does not rematch callbacks.
- defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
+ defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
mWiFiNetworkAgent.disconnect();
@@ -5330,13 +5526,13 @@
vpnNetworkCallback.assertNoCallback();
defaultCallback.assertNoCallback();
- vpnNetworkAgent.disconnect();
+ mMockVpn.disconnect();
- genericNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
genericNotVpnNetworkCallback.assertNoCallback();
wifiNetworkCallback.assertNoCallback();
- vpnNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
- defaultCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+ vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
+ defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
assertEquals(null, mCm.getActiveNetwork());
mCm.unregisterNetworkCallback(genericNetworkCallback);
@@ -5358,20 +5554,13 @@
defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
- TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+ mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
false /* isStrictMode */);
- mMockVpn.connect();
defaultCallback.assertNoCallback();
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
- vpnNetworkAgent.disconnect();
+ mMockVpn.disconnect();
defaultCallback.assertNoCallback();
mCm.unregisterNetworkCallback(defaultCallback);
@@ -5390,21 +5579,14 @@
defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
- TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */,
+ mMockVpn.establishForMyUid(true /* validated */, true /* hasInternet */,
false /* isStrictMode */);
- mMockVpn.connect();
- defaultCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
+ defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
- vpnNetworkAgent.disconnect();
- defaultCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+ mMockVpn.disconnect();
+ defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
mCm.unregisterNetworkCallback(defaultCallback);
@@ -5422,44 +5604,36 @@
callback.assertNoCallback();
// Bring up a VPN that has the INTERNET capability, initially unvalidated.
- final int uid = Process.myUid();
- final TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */,
+ mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */,
false /* isStrictMode */);
- mMockVpn.connect();
// Even though the VPN is unvalidated, it becomes the default network for our app.
- callback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mMockVpn);
callback.assertNoCallback();
- assertTrue(vpnNetworkAgent.getScore() > mEthernetNetworkAgent.getScore());
- assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, vpnNetworkAgent.getScore());
- assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertTrue(mMockVpn.getAgent().getScore() > mEthernetNetworkAgent.getScore());
+ assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, mMockVpn.getAgent().getScore());
+ assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
- NetworkCapabilities nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+ NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED));
assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET));
assertFalse(NetworkMonitorUtils.isValidationRequired(
- vpnNetworkAgent.getNetworkCapabilities()));
+ mMockVpn.getAgent().getNetworkCapabilities()));
assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired(
- vpnNetworkAgent.getNetworkCapabilities()));
+ mMockVpn.getAgent().getNetworkCapabilities()));
// Pretend that the VPN network validates.
- vpnNetworkAgent.setNetworkValid(false /* isStrictMode */);
- vpnNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */);
+ mMockVpn.getAgent().mNetworkMonitor.forceReevaluation(Process.myUid());
// Expect to see the validated capability, but no other changes, because the VPN is already
// the default network for the app.
- callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, vpnNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mMockVpn);
callback.assertNoCallback();
- vpnNetworkAgent.disconnect();
- callback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+ mMockVpn.disconnect();
+ callback.expectCallback(CallbackEntry.LOST, mMockVpn);
callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent);
}
@@ -5481,21 +5655,15 @@
mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
mCellNetworkAgent.connect(true);
- final TestNetworkAgentWrapper vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.connect();
- mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+ mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
false /* isStrictMode */);
- vpnNetworkCallback.expectAvailableCallbacks(vpnNetworkAgent.getNetwork(),
+ vpnNetworkCallback.expectAvailableCallbacks(mMockVpn.getNetwork(),
false /* suspended */, false /* validated */, false /* blocked */, TIMEOUT_MS);
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent.getNetwork(), TIMEOUT_MS,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn.getNetwork(), TIMEOUT_MS,
nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED));
- final NetworkCapabilities nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+ final NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
assertTrue(nc.hasTransport(TRANSPORT_VPN));
assertTrue(nc.hasTransport(TRANSPORT_CELLULAR));
assertFalse(nc.hasTransport(TRANSPORT_WIFI));
@@ -5517,18 +5685,11 @@
mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
vpnNetworkCallback.assertNoCallback();
- final TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.connect();
- mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+ mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
false /* isStrictMode */);
- vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
- nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+ vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+ nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
assertTrue(nc.hasTransport(TRANSPORT_VPN));
assertFalse(nc.hasTransport(TRANSPORT_CELLULAR));
assertFalse(nc.hasTransport(TRANSPORT_WIFI));
@@ -5545,7 +5706,7 @@
mService.setUnderlyingNetworksForVpn(
new Network[] { mCellNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5559,7 +5720,7 @@
mService.setUnderlyingNetworksForVpn(
new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5569,7 +5730,7 @@
mService.setUnderlyingNetworksForVpn(
new Network[] { mCellNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5577,27 +5738,27 @@
// Remove NOT_SUSPENDED from the only network and observe VPN is now suspended.
mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
&& !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
- vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, vpnNetworkAgent);
+ vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn);
// Add NOT_SUSPENDED again and observe VPN is no longer suspended.
mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
&& caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
- vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, vpnNetworkAgent);
+ vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn);
// Use Wifi but not cell. Note the VPN is now unmetered and not suspended.
mService.setUnderlyingNetworksForVpn(
new Network[] { mWiFiNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
&& caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5607,7 +5768,7 @@
mService.setUnderlyingNetworksForVpn(
new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5620,7 +5781,7 @@
// Stop using WiFi. The VPN is suspended again.
mService.setUnderlyingNetworksForVpn(
new Network[] { mCellNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5634,7 +5795,7 @@
mService.setUnderlyingNetworksForVpn(
new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5645,14 +5806,14 @@
// Disconnect cell. Receive update without even removing the dead network from the
// underlying networks – it's dead anyway. Not metered any more.
mCellNetworkAgent.disconnect();
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
&& caps.hasCapability(NET_CAPABILITY_NOT_METERED));
// Disconnect wifi too. No underlying networks means this is now metered.
mWiFiNetworkAgent.disconnect();
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
@@ -5673,18 +5834,11 @@
mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
vpnNetworkCallback.assertNoCallback();
- final TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.connect();
- mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+ mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
false /* isStrictMode */);
- vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
- nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+ vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+ nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
assertTrue(nc.hasTransport(TRANSPORT_VPN));
assertFalse(nc.hasTransport(TRANSPORT_CELLULAR));
assertFalse(nc.hasTransport(TRANSPORT_WIFI));
@@ -5696,7 +5850,7 @@
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
@@ -5706,7 +5860,7 @@
mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
mWiFiNetworkAgent.connect(true);
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
&& caps.hasCapability(NET_CAPABILITY_NOT_METERED));
@@ -5718,7 +5872,7 @@
// Disconnect wifi too. Now we have no default network.
mWiFiNetworkAgent.disconnect();
- vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
@@ -5761,18 +5915,10 @@
assertTrue(mCm.isActiveNetworkMetered());
// Connect VPN network. By default it is using current default network (Cell).
- TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- final int uid = Process.myUid();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true);
- mMockVpn.connect();
- waitForIdle();
+ mMockVpn.establishForMyUid();
+
// Ensure VPN is now the active network.
- assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
// Expect VPN to be metered.
assertTrue(mCm.isActiveNetworkMetered());
@@ -5783,7 +5929,7 @@
mWiFiNetworkAgent.connect(true);
waitForIdle();
// VPN should still be the active network.
- assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
// Expect VPN to be unmetered as it should now be using WiFi (new default).
assertFalse(mCm.isActiveNetworkMetered());
@@ -5801,7 +5947,6 @@
// VPN without any underlying networks is treated as metered.
assertTrue(mCm.isActiveNetworkMetered());
- vpnNetworkAgent.disconnect();
mMockVpn.disconnect();
}
@@ -5822,18 +5967,10 @@
assertFalse(mCm.isActiveNetworkMetered());
// Connect VPN network.
- TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- final int uid = Process.myUid();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true);
- mMockVpn.connect();
- waitForIdle();
+ mMockVpn.establishForMyUid();
+
// Ensure VPN is now the active network.
- assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
// VPN is using Cell
mService.setUnderlyingNetworksForVpn(
new Network[] { mCellNetworkAgent.getNetwork() });
@@ -5873,7 +6010,6 @@
// VPN without underlying networks is treated as metered.
assertTrue(mCm.isActiveNetworkMetered());
- vpnNetworkAgent.disconnect();
mMockVpn.disconnect();
}
@@ -5888,17 +6024,11 @@
assertFalse(mCm.isActiveNetworkMetered());
// Connect VPN network.
- TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- final int uid = Process.myUid();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true);
- mMockVpn.connectAsAlwaysMetered();
+ mMockVpn.registerAgent(true /* isAlwaysMetered */, uidRangesForUid(Process.myUid()),
+ new LinkProperties());
+ mMockVpn.connect(true);
waitForIdle();
- assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
// VPN is tracking current platform default (WiFi).
mService.setUnderlyingNetworksForVpn(null);
@@ -5922,7 +6052,7 @@
assertTrue(mCm.isActiveNetworkMetered());
- vpnNetworkAgent.disconnect();
+ mMockVpn.disconnect();
}
@Test
@@ -6654,34 +6784,21 @@
waitForIdle();
assertNull(mService.getProxyForNetwork(null));
- // Set up a VPN network with a proxy
- final int uid = Process.myUid();
- final TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
- final ArraySet<UidRange> ranges = new ArraySet<>();
- ranges.add(new UidRange(uid, uid));
- mMockVpn.setUids(ranges);
+ // Connect a VPN network with a proxy.
LinkProperties testLinkProperties = new LinkProperties();
testLinkProperties.setHttpProxy(testProxyInfo);
- vpnNetworkAgent.sendLinkProperties(testLinkProperties);
- waitForIdle();
-
- // Connect to VPN with proxy
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- vpnNetworkAgent.connect(true);
- mMockVpn.connect();
- waitForIdle();
+ mMockVpn.establishForMyUid(testLinkProperties);
// Test that the VPN network returns a proxy, and the WiFi does not.
- assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(mMockVpn.getNetwork()));
assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
// Test that the VPN network returns no proxy when it is set to null.
testLinkProperties.setHttpProxy(null);
- vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+ mMockVpn.sendLinkProperties(testLinkProperties);
waitForIdle();
- assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+ assertNull(mService.getProxyForNetwork(mMockVpn.getNetwork()));
assertNull(mService.getProxyForNetwork(null));
// Set WiFi proxy and check that the vpn proxy is still null.
@@ -6692,7 +6809,7 @@
// Disconnect from VPN and check that the active network, which is now the WiFi, has the
// correct proxy setting.
- vpnNetworkAgent.disconnect();
+ mMockVpn.disconnect();
waitForIdle();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
@@ -6707,7 +6824,7 @@
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
// The uid range needs to cover the test app so the network is visible to it.
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
- final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+ mMockVpn.establish(lp, VPN_UID, vpnRange);
// Connected VPN should have interface rules set up. There are two expected invocations,
// one during VPN uid update, one during VPN LinkProperties update
@@ -6717,7 +6834,7 @@
assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange));
- vpnNetworkAgent.disconnect();
+ mMockVpn.disconnect();
waitForIdle();
// Disconnected VPN should have interface rules removed
@@ -6734,8 +6851,7 @@
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
// The uid range needs to cover the test app so the network is visible to it.
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
- final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(
- lp, Process.SYSTEM_UID, vpnRange);
+ mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
// Legacy VPN should not have interface rules set up
verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
@@ -6750,8 +6866,7 @@
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
// The uid range needs to cover the test app so the network is visible to it.
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
- final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(
- lp, Process.SYSTEM_UID, vpnRange);
+ mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
// IPv6 unreachable route should not be misinterpreted as a default route
verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
@@ -6765,7 +6880,7 @@
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
// The uid range needs to cover the test app so the network is visible to it.
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
- final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+ mMockVpn.establish(lp, VPN_UID, vpnRange);
// Connected VPN should have interface rules set up. There are two expected invocations,
// one during VPN uid update, one during VPN LinkProperties update
@@ -6777,7 +6892,7 @@
reset(mMockNetd);
InOrder inOrder = inOrder(mMockNetd);
lp.setInterfaceName("tun1");
- vpnNetworkAgent.sendLinkProperties(lp);
+ mMockVpn.sendLinkProperties(lp);
waitForIdle();
// VPN handover (switch to a new interface) should result in rules being updated (old rules
// removed first, then new rules added)
@@ -6790,7 +6905,7 @@
lp = new LinkProperties();
lp.setInterfaceName("tun1");
lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1"));
- vpnNetworkAgent.sendLinkProperties(lp);
+ mMockVpn.sendLinkProperties(lp);
waitForIdle();
// VPN not routing everything should no longer have interface filtering rules
verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
@@ -6801,7 +6916,7 @@
lp.setInterfaceName("tun1");
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
- vpnNetworkAgent.sendLinkProperties(lp);
+ mMockVpn.sendLinkProperties(lp);
waitForIdle();
// Back to routing all IPv6 traffic should have filtering rules
verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
@@ -6816,8 +6931,7 @@
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
// The uid range needs to cover the test app so the network is visible to it.
final UidRange vpnRange = UidRange.createForUser(VPN_USER);
- final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID,
- Collections.singleton(vpnRange));
+ mMockVpn.establish(lp, VPN_UID, Collections.singleton(vpnRange));
reset(mMockNetd);
InOrder inOrder = inOrder(mMockNetd);
@@ -6826,7 +6940,7 @@
final Set<UidRange> newRanges = new HashSet<>(Arrays.asList(
new UidRange(vpnRange.start, APP1_UID - 1),
new UidRange(APP1_UID + 1, vpnRange.stop)));
- vpnNetworkAgent.setUids(newRanges);
+ mMockVpn.setUids(newRanges);
waitForIdle();
ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
@@ -6967,7 +7081,7 @@
private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
throws Exception {
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
- establishVpn(new LinkProperties(), vpnOwnerUid, vpnRange);
+ mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange);
mMockVpn.setVpnType(vpnType);
final VpnInfo vpnInfo = new VpnInfo();
@@ -7048,19 +7162,6 @@
mService.getConnectionOwnerUid(getTestConnectionInfo());
}
- private TestNetworkAgentWrapper establishVpn(
- LinkProperties lp, int ownerUid, Set<UidRange> vpnRange) throws Exception {
- final TestNetworkAgentWrapper
- vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp);
- vpnNetworkAgent.getNetworkCapabilities().setOwnerUid(ownerUid);
- mMockVpn.setNetworkAgent(vpnNetworkAgent);
- mMockVpn.connect();
- mMockVpn.setUids(vpnRange);
- vpnNetworkAgent.connect(true);
- waitForIdle();
- return vpnNetworkAgent;
- }
-
private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) {
final PackageInfo packageInfo = new PackageInfo();
if (hasSystemPermission) {
@@ -7240,22 +7341,28 @@
setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION);
- mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
// setUp() calls mockVpn() which adds a VPN with the Test Runner's uid. Configure it to be
// active
final VpnInfo info = new VpnInfo();
info.ownerUid = Process.myUid();
- info.vpnIface = "interface";
+ info.vpnIface = VPN_IFNAME;
mMockVpn.setVpnInfo(info);
- mMockVpn.overrideUnderlyingNetworks(new Network[] {network});
+
+ mMockVpn.establishForMyUid();
+ waitForIdle();
+
+ mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+
+
+ assertTrue(mService.setUnderlyingNetworksForVpn(new Network[] {network}));
assertTrue(
"Active VPN permission not applied",
mService.checkConnectivityDiagnosticsPermissions(
Process.myPid(), Process.myUid(), naiWithoutUid,
mContext.getOpPackageName()));
- mMockVpn.overrideUnderlyingNetworks(null);
+ assertTrue(mService.setUnderlyingNetworksForVpn(null));
assertFalse(
"VPN shouldn't receive callback on non-underlying network",
mService.checkConnectivityDiagnosticsPermissions(
@@ -7276,8 +7383,6 @@
Manifest.permission.ACCESS_FINE_LOCATION);
mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
- // Disconnect mock vpn so the uid check on NetworkAgentInfo is tested
- mMockVpn.disconnect();
assertTrue(
"NetworkCapabilities administrator uid permission not applied",
mService.checkConnectivityDiagnosticsPermissions(
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index f92c602..b760958 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -1568,6 +1568,22 @@
return true;
}
+ ResourceEntry* ResolveTableEntry(LinkContext* context, ResourceTable* table,
+ Reference* reference) {
+ if (!reference || !reference->name) {
+ return nullptr;
+ }
+ auto name_ref = ResourceNameRef(reference->name.value());
+ if (name_ref.package.empty()) {
+ name_ref.package = context->GetCompilationPackage();
+ }
+ const auto search_result = table->FindResource(name_ref);
+ if (!search_result) {
+ return nullptr;
+ }
+ return search_result.value().entry;
+ }
+
void AliasAdaptiveIcon(xml::XmlResource* manifest, ResourceTable* table) {
const xml::Element* application = manifest->root->FindChild("", "application");
if (!application) {
@@ -1582,22 +1598,13 @@
// Find the icon resource defined within the application.
const auto icon_reference = ValueCast<Reference>(icon->compiled_value.get());
- if (!icon_reference || !icon_reference->name) {
- return;
- }
-
- auto icon_name = ResourceNameRef(icon_reference->name.value());
- if (icon_name.package.empty()) {
- icon_name.package = context_->GetCompilationPackage();
- }
-
- const auto icon_entry_result = table->FindResource(icon_name);
- if (!icon_entry_result) {
+ const auto icon_entry = ResolveTableEntry(context_, table, icon_reference);
+ if (!icon_entry) {
return;
}
int icon_max_sdk = 0;
- for (auto& config_value : icon_entry_result.value().entry->values) {
+ for (auto& config_value : icon_entry->values) {
icon_max_sdk = (icon_max_sdk < config_value->config.sdkVersion)
? config_value->config.sdkVersion : icon_max_sdk;
}
@@ -1608,22 +1615,13 @@
// Find the roundIcon resource defined within the application.
const auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get());
- if (!round_icon_reference || !round_icon_reference->name) {
- return;
- }
-
- auto round_icon_name = ResourceNameRef(round_icon_reference->name.value());
- if (round_icon_name.package.empty()) {
- round_icon_name.package = context_->GetCompilationPackage();
- }
-
- const auto round_icon_entry_result = table->FindResource(round_icon_name);
- if (!round_icon_entry_result) {
+ const auto round_icon_entry = ResolveTableEntry(context_, table, round_icon_reference);
+ if (!round_icon_entry) {
return;
}
int round_icon_max_sdk = 0;
- for (auto& config_value : round_icon_entry_result.value().entry->values) {
+ for (auto& config_value : round_icon_entry->values) {
round_icon_max_sdk = (round_icon_max_sdk < config_value->config.sdkVersion)
? config_value->config.sdkVersion : round_icon_max_sdk;
}
@@ -1634,7 +1632,7 @@
}
// Add an equivalent v26 entry to the roundIcon for each v26 variant of the regular icon.
- for (auto& config_value : icon_entry_result.value().entry->values) {
+ for (auto& config_value : icon_entry->values) {
if (config_value->config.sdkVersion < SDK_O) {
continue;
}
@@ -1645,12 +1643,62 @@
<< "\" for round icon compatibility");
auto value = icon_reference->Clone(&table->string_pool);
- auto round_config_value = round_icon_entry_result.value().entry->FindOrCreateValue(
- config_value->config, config_value->product);
+ auto round_config_value =
+ round_icon_entry->FindOrCreateValue(config_value->config, config_value->product);
round_config_value->value.reset(value);
}
}
+ bool VerifySharedUserId(xml::XmlResource* manifest, ResourceTable* table) {
+ const xml::Element* manifest_el = xml::FindRootElement(manifest->root.get());
+ if (manifest_el == nullptr) {
+ return true;
+ }
+ if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
+ return true;
+ }
+ const xml::Attribute* attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "sharedUserId");
+ if (!attr) {
+ return true;
+ }
+ const auto validate = [&](const std::string& shared_user_id) -> bool {
+ if (util::IsAndroidSharedUserId(context_->GetCompilationPackage(), shared_user_id)) {
+ return true;
+ }
+ DiagMessage error_msg(manifest_el->line_number);
+ error_msg << "attribute 'sharedUserId' in <manifest> tag is not a valid shared user id: '"
+ << shared_user_id << "'";
+ if (options_.manifest_fixer_options.warn_validation) {
+ // Treat the error only as a warning.
+ context_->GetDiagnostics()->Warn(error_msg);
+ return true;
+ }
+ context_->GetDiagnostics()->Error(error_msg);
+ return false;
+ };
+ // If attr->compiled_value is not null, check if it is a ref
+ if (attr->compiled_value) {
+ const auto ref = ValueCast<Reference>(attr->compiled_value.get());
+ if (ref == nullptr) {
+ return true;
+ }
+ const auto shared_user_id_entry = ResolveTableEntry(context_, table, ref);
+ if (!shared_user_id_entry) {
+ return true;
+ }
+ for (const auto& value : shared_user_id_entry->values) {
+ const auto str_value = ValueCast<String>(value->value.get());
+ if (str_value != nullptr && !validate(*str_value->value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Fallback to checking the raw value
+ return validate(attr->value);
+ }
+
// Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
// to the IArchiveWriter.
bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
@@ -1672,6 +1720,11 @@
// See (b/34829129)
AliasAdaptiveIcon(manifest, table);
+ // Verify the shared user id here to handle the case of reference value.
+ if (!VerifySharedUserId(manifest, table)) {
+ return false;
+ }
+
ResourceFileFlattenerOptions file_flattener_options;
file_flattener_options.keep_raw_values = keep_raw_values;
file_flattener_options.do_not_compress_anything = options_.do_not_compress_anything;
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index fd3a4c0..c03661c 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -163,7 +163,8 @@
return true;
}
-static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
+static bool VerifyManifest(xml::Element* el, xml::XmlActionExecutorPolicy policy,
+ SourcePathDiagnostics* diag) {
xml::Attribute* attr = el->FindAttribute({}, "package");
if (!attr) {
diag->Error(DiagMessage(el->line_number)
@@ -174,10 +175,16 @@
<< "attribute 'package' in <manifest> tag must not be a reference");
return false;
} else if (!util::IsAndroidPackageName(attr->value)) {
- diag->Error(DiagMessage(el->line_number)
- << "attribute 'package' in <manifest> tag is not a valid Android package name: '"
- << attr->value << "'");
- return false;
+ DiagMessage error_msg(el->line_number);
+ error_msg << "attribute 'package' in <manifest> tag is not a valid Android package name: '"
+ << attr->value << "'";
+ if (policy == xml::XmlActionExecutorPolicy::kAllowListWarning) {
+ // Treat the error only as a warning.
+ diag->Warn(error_msg);
+ } else {
+ diag->Error(error_msg);
+ return false;
+ }
}
attr = el->FindAttribute({}, "split");
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index ef33c34..28330db 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -38,6 +38,11 @@
namespace aapt {
namespace util {
+// Package name and shared user id would be used as a part of the file name.
+// Limits size to 223 and reserves 32 for the OS.
+// See frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+constexpr static const size_t kMaxPackageNameSize = 223;
+
static std::vector<std::string> SplitAndTransform(
const StringPiece& str, char sep, const std::function<char(char)>& f) {
std::vector<std::string> parts;
@@ -169,9 +174,21 @@
}
bool IsAndroidPackageName(const StringPiece& str) {
+ if (str.size() > kMaxPackageNameSize) {
+ return false;
+ }
return IsAndroidNameImpl(str) > 1 || str == "android";
}
+bool IsAndroidSharedUserId(const android::StringPiece& package_name,
+ const android::StringPiece& shared_user_id) {
+ if (shared_user_id.size() > kMaxPackageNameSize) {
+ return false;
+ }
+ return shared_user_id.empty() || IsAndroidNameImpl(shared_user_id) > 1 ||
+ package_name == "android";
+}
+
bool IsAndroidSplitName(const StringPiece& str) {
return IsAndroidNameImpl(str) > 0;
}
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index a956957..c77aca3 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -81,6 +81,7 @@
// - First character of each component (separated by '.') must be an ASCII letter.
// - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
// - Package must contain at least two components, unless it is 'android'.
+// - The maximum package name length is 223.
bool IsAndroidPackageName(const android::StringPiece& str);
// Tests that the string is a valid Android split name.
@@ -88,6 +89,15 @@
// - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
bool IsAndroidSplitName(const android::StringPiece& str);
+// Tests that the string is a valid Android shared user id.
+// - First character of each component (separated by '.') must be an ASCII letter.
+// - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
+// - Must contain at least two components, unless package name is 'android'.
+// - The maximum shared user id length is 223.
+// - Treat empty string as valid, it's the case of no shared user id.
+bool IsAndroidSharedUserId(const android::StringPiece& package_name,
+ const android::StringPiece& shared_user_id);
+
// Converts the class name to a fully qualified class name from the given
// `package`. Ex:
//
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index d4e3bec..4ebcb11 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -27,6 +27,17 @@
namespace aapt {
+// Test that a max package name size 223 is valid.
+static const std::string kMaxPackageName =
+ "com.foo.nameRw8ajIGbYmqPuO0K7TYJFsI2pjlDAS0pYOYQlJvtQux"
+ "SoBKV1hMyNh4XfmcMj8OgPHfFaTXeKEHFMdGQHpw9Dz9Uqr8h1krgJLRv2aXyPCsGdVwBJzfZ4COVRiX3sc9O"
+ "CUrTTvZe6wXlgKb5Qz5qdkTBZ5euzGeoyZwestDTBIgT5exAl5efnznwzceS7VsIntgY10UUQvaoTsLBO6l";
+// Test that a long package name size 224 is invalid.
+static const std::string kLongPackageName =
+ "com.foo.nameRw8ajIGbYmqPuO0K7TYJFsI2pjlDAS0pYOYQlJvtQu"
+ "xSoBKV1hMyNh4XfmcMj8OgPHfFaTXeKEHFMdGQHpw9Dz9Uqr8h1krgJLRv2aXyPCsGdVwBJzfZ4COVRiX3sc9O"
+ "CUrTTvZe6wXlgKb5Qz5qdkTBZ5euzGeoyZwestDTBIgT5exAl5efnznwzceS7VsIntgY10UUQvaoTsLBO6le";
+
TEST(UtilTest, TrimOnlyWhitespace) {
const StringPiece trimmed = util::TrimWhitespace("\n ");
EXPECT_TRUE(trimmed.empty());
@@ -108,6 +119,7 @@
EXPECT_TRUE(util::IsAndroidPackageName("com.foo.test_thing"));
EXPECT_TRUE(util::IsAndroidPackageName("com.foo.testing_thing_"));
EXPECT_TRUE(util::IsAndroidPackageName("com.foo.test_99_"));
+ EXPECT_TRUE(util::IsAndroidPackageName(kMaxPackageName));
EXPECT_FALSE(util::IsAndroidPackageName("android._test"));
EXPECT_FALSE(util::IsAndroidPackageName("com"));
@@ -116,6 +128,27 @@
EXPECT_FALSE(util::IsAndroidPackageName(".android"));
EXPECT_FALSE(util::IsAndroidPackageName(".."));
EXPECT_FALSE(util::IsAndroidPackageName("cøm.foo"));
+ EXPECT_FALSE(util::IsAndroidPackageName(kLongPackageName));
+}
+
+TEST(UtilTest, IsAndroidSharedUserId) {
+ EXPECT_TRUE(util::IsAndroidSharedUserId("android", "foo"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "android.test"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.test_thing"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.testing_thing_"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.test_99_"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", ""));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", kMaxPackageName));
+
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "android._test"));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "com"));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "_android"));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "android."));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", ".android"));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", ".."));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "cøm.foo"));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", kLongPackageName));
}
TEST(UtilTest, FullyQualifiedClassName) {
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index fab17c9..ea42d26 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -21,23 +21,34 @@
namespace aapt {
namespace xml {
-static bool wrapper_one(XmlNodeAction::ActionFunc& f, Element* el, SourcePathDiagnostics*) {
+static bool wrapper_one(const XmlNodeAction::ActionFunc& f, Element* el,
+ const XmlActionExecutorPolicy& policy, SourcePathDiagnostics*) {
return f(el);
}
-static bool wrapper_two(XmlNodeAction::ActionFuncWithDiag& f, Element* el,
- SourcePathDiagnostics* diag) {
+static bool wrapper_two(const XmlNodeAction::ActionFuncWithDiag& f, Element* el,
+ const XmlActionExecutorPolicy& policy, SourcePathDiagnostics* diag) {
return f(el, diag);
}
+static bool wrapper_three(const XmlNodeAction::ActionFuncWithPolicyAndDiag& f, Element* el,
+ const XmlActionExecutorPolicy& policy, SourcePathDiagnostics* diag) {
+ return f(el, policy, diag);
+}
+
void XmlNodeAction::Action(XmlNodeAction::ActionFunc f) {
- actions_.emplace_back(std::bind(
- wrapper_one, std::move(f), std::placeholders::_1, std::placeholders::_2));
+ actions_.emplace_back(std::bind(wrapper_one, std::move(f), std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3));
}
void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithDiag f) {
- actions_.emplace_back(std::bind(
- wrapper_two, std::move(f), std::placeholders::_1, std::placeholders::_2));
+ actions_.emplace_back(std::bind(wrapper_two, std::move(f), std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3));
+}
+
+void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithPolicyAndDiag f) {
+ actions_.emplace_back(std::bind(wrapper_three, std::move(f), std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3));
}
static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) {
@@ -51,8 +62,8 @@
bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPiece>* bread_crumb,
SourcePathDiagnostics* diag, Element* el) const {
bool error = false;
- for (const ActionFuncWithDiag& action : actions_) {
- error |= !action(el, diag);
+ for (const ActionFuncWithPolicyAndDiag& action : actions_) {
+ error |= !action(el, policy, diag);
}
for (Element* child_el : el->GetChildElements()) {
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
index a0ad1da..78c4334 100644
--- a/tools/aapt2/xml/XmlActionExecutor.h
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -49,6 +49,8 @@
// holds XmlNodeActions for child XML nodes.
class XmlNodeAction {
public:
+ using ActionFuncWithPolicyAndDiag =
+ std::function<bool(Element*, XmlActionExecutorPolicy, SourcePathDiagnostics*)>;
using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>;
using ActionFunc = std::function<bool(Element*)>;
@@ -61,6 +63,7 @@
// Add an action to be performed at this XmlNodeAction.
void Action(ActionFunc f);
void Action(ActionFuncWithDiag);
+ void Action(ActionFuncWithPolicyAndDiag);
private:
friend class XmlActionExecutor;
@@ -69,7 +72,7 @@
SourcePathDiagnostics* diag, Element* el) const;
std::map<std::string, XmlNodeAction> map_;
- std::vector<ActionFuncWithDiag> actions_;
+ std::vector<ActionFuncWithPolicyAndDiag> actions_;
};
// Allows the definition of actions to execute at specific XML elements defined by their hierarchy.
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
index d235c80..dc96df6 100644
--- a/wifi/jarjar-rules.txt
+++ b/wifi/jarjar-rules.txt
@@ -89,8 +89,6 @@
rule android.util.LocalLog* com.android.wifi.x.@0
rule android.util.Rational* com.android.wifi.x.@0
-rule android.os.BasicShellCommandHandler* com.android.wifi.x.@0
-
# Use our statically linked bouncy castle library
rule org.bouncycastle.** com.android.wifi.x.@0
# Use our statically linked protobuf library
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index e9d1a00..9f9d7f3 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -538,7 +538,7 @@
if (!SdkLevel.isAtLeastS()) {
throw new UnsupportedOperationException();
}
- return mChannels;
+ return mChannels.clone();
}
/**