Merge "Adding a little more data to an exception." into sc-dev
diff --git a/Android.bp b/Android.bp
index 0a3ca3b..240d803 100644
--- a/Android.bp
+++ b/Android.bp
@@ -403,6 +403,7 @@
":framework-mediaprovider-sources",
":framework-permission-sources",
":framework-permission-s-sources",
+ ":framework-scheduling-sources",
":framework-sdkextensions-sources",
":framework-statsd-sources",
":framework-tethering-srcs",
@@ -423,6 +424,7 @@
"framework-mediaprovider.stubs.module_lib",
"framework-permission.stubs.module_lib",
"framework-permission-s.stubs.module_lib",
+ "framework-scheduling.stubs.module_lib",
"framework-sdkextensions.stubs.module_lib",
"framework-statsd.stubs.module_lib",
"framework-tethering.stubs.module_lib",
@@ -443,6 +445,7 @@
"framework-mediaprovider.impl",
"framework-permission.impl",
"framework-permission-s.impl",
+ "framework-scheduling.impl",
"framework-sdkextensions.impl",
"framework-statsd.impl",
"framework-tethering.impl",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 3f2e898..4bd524f 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -315,6 +315,7 @@
"framework-mediaprovider.stubs",
"framework-permission.stubs",
"framework-permission-s.stubs",
+ "framework-scheduling.stubs",
"framework-sdkextensions.stubs",
"framework-statsd.stubs",
"framework-tethering.stubs",
@@ -338,6 +339,7 @@
"framework-mediaprovider.stubs.system",
"framework-permission.stubs.system",
"framework-permission-s.stubs.system",
+ "framework-scheduling.stubs.system",
"framework-sdkextensions.stubs.system",
"framework-statsd.stubs.system",
"framework-tethering.stubs.system",
@@ -377,6 +379,7 @@
"framework-mediaprovider.stubs.system",
"framework-permission.stubs.system",
"framework-permission-s.stubs.system",
+ "framework-scheduling.stubs.system",
"framework-sdkextensions.stubs.system",
"framework-statsd.stubs.system",
"framework-tethering.stubs.system",
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
index 97cfe36..cd75b14 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
@@ -37,24 +37,36 @@
public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
@NonNull private final Map<KeyType, ValueType> mSuccesses;
@NonNull private final Map<KeyType, AppSearchResult<ValueType>> mFailures;
+ @NonNull private final Map<KeyType, AppSearchResult<ValueType>> mAll;
AppSearchBatchResult(
@NonNull Map<KeyType, ValueType> successes,
- @NonNull Map<KeyType, AppSearchResult<ValueType>> failures) {
+ @NonNull Map<KeyType, AppSearchResult<ValueType>> failures,
+ @NonNull Map<KeyType, AppSearchResult<ValueType>> all) {
mSuccesses = successes;
mFailures = failures;
+ mAll = all;
}
private AppSearchBatchResult(@NonNull Parcel in) {
- mSuccesses = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
- mFailures = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
+ mAll = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
+ Map<KeyType, ValueType> successes = new ArrayMap<>();
+ Map<KeyType, AppSearchResult<ValueType>> failures = new ArrayMap<>();
+ for (Map.Entry<KeyType, AppSearchResult<ValueType>> entry : mAll.entrySet()) {
+ if (entry.getValue().isSuccess()) {
+ successes.put(entry.getKey(), entry.getValue().getResultValue());
+ } else {
+ failures.put(entry.getKey(), entry.getValue());
+ }
+ }
+ mSuccesses = Collections.unmodifiableMap(successes);
+ mFailures = Collections.unmodifiableMap(failures);
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeMap(mSuccesses);
- dest.writeMap(mFailures);
+ dest.writeMap(mAll);
}
/** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
@@ -63,8 +75,8 @@
}
/**
- * Returns a {@link Map} of all successful keys mapped to the successful
- * {@link AppSearchResult}s they produced.
+ * Returns a {@link Map} of all successful keys mapped to the successful {@link
+ * AppSearchResult}s they produced.
*
* <p>The values of the {@link Map} will not be {@code null}.
*/
@@ -85,7 +97,19 @@
}
/**
+ * Returns a {@link Map} of all keys mapped to the {@link AppSearchResult}s they produced.
+ *
+ * <p>The values of the {@link Map} will not be {@code null}.
+ * @hide
+ */
+ @NonNull
+ public Map<KeyType, AppSearchResult<ValueType>> getAll() {
+ return mAll;
+ }
+
+ /**
* Asserts that this {@link AppSearchBatchResult} has no failures.
+ *
* @hide
*/
public void checkSuccess() {
@@ -133,6 +157,7 @@
public static final class Builder<KeyType, ValueType> {
private final Map<KeyType, ValueType> mSuccesses = new ArrayMap<>();
private final Map<KeyType, AppSearchResult<ValueType>> mFailures = new ArrayMap<>();
+ private final Map<KeyType, AppSearchResult<ValueType>> mAll = new ArrayMap<>();
private boolean mBuilt = false;
/**
@@ -181,6 +206,7 @@
mFailures.put(key, result);
mSuccesses.remove(key);
}
+ mAll.put(key, result);
return this;
}
@@ -189,7 +215,7 @@
public AppSearchBatchResult<KeyType, ValueType> build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mBuilt = true;
- return new AppSearchBatchResult<>(mSuccesses, mFailures);
+ return new AppSearchBatchResult<>(mSuccesses, mFailures, mAll);
}
}
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
index 76225e4..440f633 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
@@ -22,6 +22,7 @@
import android.app.appsearch.exceptions.AppSearchException;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -35,19 +36,21 @@
*/
public final class AppSearchResult<ValueType> implements Parcelable {
/**
- * Result codes from {@link AppSearchManager} methods.
+ * Result codes from {@link AppSearchSession} methods.
+ *
* @hide
*/
- @IntDef(value = {
- RESULT_OK,
- RESULT_UNKNOWN_ERROR,
- RESULT_INTERNAL_ERROR,
- RESULT_INVALID_ARGUMENT,
- RESULT_IO_ERROR,
- RESULT_OUT_OF_SPACE,
- RESULT_NOT_FOUND,
- RESULT_INVALID_SCHEMA,
- })
+ @IntDef(
+ value = {
+ RESULT_OK,
+ RESULT_UNKNOWN_ERROR,
+ RESULT_INTERNAL_ERROR,
+ RESULT_INVALID_ARGUMENT,
+ RESULT_IO_ERROR,
+ RESULT_OUT_OF_SPACE,
+ RESULT_NOT_FOUND,
+ RESULT_INVALID_SCHEMA,
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface ResultCode {}
@@ -60,21 +63,21 @@
/**
* An internal error occurred within AppSearch, which the caller cannot address.
*
- * This error may be considered similar to {@link IllegalStateException}
+ * <p>This error may be considered similar to {@link IllegalStateException}
*/
public static final int RESULT_INTERNAL_ERROR = 2;
/**
* The caller supplied invalid arguments to the call.
*
- * This error may be considered similar to {@link IllegalArgumentException}.
+ * <p>This error may be considered similar to {@link IllegalArgumentException}.
*/
public static final int RESULT_INVALID_ARGUMENT = 3;
/**
* An issue occurred reading or writing to storage. The call might succeed if repeated.
*
- * This error may be considered similar to {@link java.io.IOException}.
+ * <p>This error may be considered similar to {@link java.io.IOException}.
*/
public static final int RESULT_IO_ERROR = 4;
@@ -127,7 +130,7 @@
/**
* Returns the result value associated with this result, if it was successful.
*
- * <p>See the documentation of the particular {@link AppSearchManager} call producing this
+ * <p>See the documentation of the particular {@link AppSearchSession} call producing this
* {@link AppSearchResult} for what is placed in the result value by that call.
*
* @throws IllegalStateException if this {@link AppSearchResult} is not successful.
@@ -145,8 +148,8 @@
*
* <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
* message may be {@code null} even if {@link #isSuccess} is {@code false}. See the
- * documentation of the particular {@link AppSearchManager} call producing this
- * {@link AppSearchResult} for what is returned by {@link #getErrorMessage}.
+ * documentation of the particular {@link AppSearchSession} call producing this {@link
+ * AppSearchResult} for what is returned by {@link #getErrorMessage}.
*/
@Nullable
public String getErrorMessage() {
@@ -205,6 +208,7 @@
/**
* Creates a new successful {@link AppSearchResult}.
+ *
* @hide
*/
@NonNull
@@ -215,6 +219,7 @@
/**
* Creates a new failed {@link AppSearchResult}.
+ *
* @hide
*/
@NonNull
@@ -227,6 +232,8 @@
@NonNull
public static <ValueType> AppSearchResult<ValueType> throwableToFailedResult(
@NonNull Throwable t) {
+ Log.d("AppSearchResult", "Converting throwable to failed result.", t);
+
if (t instanceof AppSearchException) {
return ((AppSearchException) t).toAppSearchResult();
}
@@ -241,6 +248,6 @@
} else {
resultCode = AppSearchResult.RESULT_UNKNOWN_ERROR;
}
- return AppSearchResult.newFailedResult(resultCode, t.toString());
+ return AppSearchResult.newFailedResult(resultCode, t.getMessage());
}
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index 8fcd2f9..73ca0cc 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -108,54 +108,85 @@
* to {@link #setSchema}, if any, to determine how to treat existing documents. The following
* types of schema modifications are always safe and are made without deleting any existing
* documents:
+ *
* <ul>
- * <li>Addition of new types
- * <li>Addition of new
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} properties to a
- * type
- * <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} property.
+ * <li>Addition of new types
+ * <li>Addition of new {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or
+ * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} properties to a
+ * type
+ * <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an {@link
+ * AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a {@link
+ * AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} property.
* </ul>
*
* <p>The following types of schema changes are not backwards-compatible:
- * <ul>
- * <li>Removal of an existing type
- * <li>Removal of a property from a type
- * <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
- * <li>For properties of {@code Document} type, changing the schema type of
- * {@code Document}s of that property
- * <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property).
- * <li>Adding a
- * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property.
- * </ul>
- * <p>Supplying a schema with such changes will, by default, result in this call returning an
- * {@link AppSearchResult} with a code of {@link AppSearchResult#RESULT_INVALID_SCHEMA} and an
- * error message describing the incompatibility. In this case the previously set schema will
- * remain active.
*
- * <p>If you need to make non-backwards-compatible changes as described above, you can set the
- * {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. In this case,
- * instead of returning an {@link AppSearchResult} with the
- * {@link AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
- * compatible with the new schema will be deleted and the incompatible schema will be applied.
+ * <ul>
+ * <li>Removal of an existing type
+ * <li>Removal of a property from a type
+ * <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
+ * <li>For properties of {@code Document} type, changing the schema type of {@code Document}s
+ * of that property
+ * <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an {@link
+ * AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a {@link
+ * AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property).
+ * <li>Adding a {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property.
+ * </ul>
+ *
+ * <p>Supplying a schema with such changes will, by default, result in this call completing its
+ * future with an {@link android.app.appsearch.exceptions.AppSearchException} with a code of
+ * {@link AppSearchResult#RESULT_INVALID_SCHEMA} and a message describing the incompatibility.
+ * In this case the previously set schema will remain active.
+ *
+ * <p>If you need to make non-backwards-compatible changes as described above, you can either:
+ *
+ * <ul>
+ * <li>Set the {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. In
+ * this case, instead of completing its future with an {@link
+ * android.app.appsearch.exceptions.AppSearchException} with the {@link
+ * AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
+ * compatible with the new schema will be deleted and the incompatible schema will be
+ * applied. Incompatible types and deleted types will be set into {@link
+ * SetSchemaResponse#getIncompatibleTypes()} and {@link
+ * SetSchemaResponse#getDeletedTypes()}, respectively.
+ * <li>Add a {@link android.app.appsearch.AppSearchSchema.Migrator} for each incompatible type
+ * and make no deletion. The migrator will migrate documents from it's old schema version
+ * to the new version. Migrated types will be set into both {@link
+ * SetSchemaResponse#getIncompatibleTypes()} and {@link
+ * SetSchemaResponse#getMigratedTypes()}. See the migration section below.
+ * </ul>
*
* <p>It is a no-op to set the same schema as has been previously set; this is handled
* efficiently.
*
- * <p>By default, documents are visible on platform surfaces. To opt out, call
- * {@link SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} with {@code visible} as
- * false. Any visibility settings apply only to the schemas that are included in the
- * {@code request}. Visibility settings for a schema type do not persist across
- * {@link #setSchema} calls.
+ * <p>By default, documents are visible on platform surfaces. To opt out, call {@code
+ * SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any
+ * visibility settings apply only to the schemas that are included in the {@code request}.
+ * Visibility settings for a schema type do not apply or persist across {@link
+ * SetSchemaRequest}s.
*
- * @param request The schema update request.
+ * <p>Migration: make non-backwards-compatible changes will delete all stored documents in old
+ * schema. You can save your documents by setting {@link
+ * android.app.appsearch.AppSearchSchema.Migrator} via the {@link
+ * SetSchemaRequest.Builder#setMigrator} for each type you want to save.
+ *
+ * <p>{@link android.app.appsearch.AppSearchSchema.Migrator#onDowngrade} or {@link
+ * android.app.appsearch.AppSearchSchema.Migrator#onUpgrade} will be triggered if the version
+ * number of the schema stored in AppSearch is different with the version in the request.
+ *
+ * <p>If any error or Exception occurred in the {@link
+ * android.app.appsearch.AppSearchSchema.Migrator#onDowngrade}, {@link
+ * android.app.appsearch.AppSearchSchema.Migrator#onUpgrade} or {@link
+ * android.app.appsearch.AppSearchMigrationHelper.Transformer#transform}, the migration will be
+ * terminated, the setSchema request will be rejected unless the schema changes are
+ * backwards-compatible, and stored documents won't have any observable changes.
+ *
+ * @param request The schema update request.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive errors resulting from setting the schema. If the
* operation succeeds, the callback will be invoked with {@code null}.
+ * @see android.app.appsearch.AppSearchSchema.Migrator
+ * @see android.app.appsearch.AppSearchMigrationHelper.Transformer
*/
public void setSchema(
@NonNull SetSchemaRequest request,
@@ -193,11 +224,9 @@
executor.execute(() -> {
if (result.isSuccess()) {
callback.accept(
- // TODO(b/151178558) implement Migration in platform.
+ // TODO(b/177266929) implement Migration in platform.
AppSearchResult.newSuccessfulResult(
- new SetSchemaResponse.Builder().setResultCode(
- result.getResultCode())
- .build()));
+ new SetSchemaResponse.Builder().build()));
} else {
callback.accept(result);
}
@@ -256,7 +285,7 @@
* <p>Each {@link GenericDocument}'s {@code schemaType} field must be set to the name of a
* schema type previously registered via the {@link #setSchema} method.
*
- * @param request {@link PutDocumentsRequest} containing documents to be indexed
+ * @param request {@link PutDocumentsRequest} containing documents to be indexed
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive pending result of performing this operation. The keys
* of the returned {@link AppSearchBatchResult} are the URIs of the input
@@ -297,9 +326,10 @@
}
/**
- * Retrieves {@link GenericDocument}s by URI.
+ * Gets {@link GenericDocument} objects by URIs and namespace from the {@link AppSearchSession}
+ * database.
*
- * @param request {@link GetByUriRequest} containing URIs to be retrieved.
+ * @param request a request containing URIs and namespace to get documents for.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive the pending result of performing this operation. The keys
* of the returned {@link AppSearchBatchResult} are the input URIs. The values
@@ -377,48 +407,65 @@
}
/**
- * Searches a document based on a given query string.
+ * Retrieves documents from the open {@link AppSearchSession} that match a given query string
+ * and type of search provided.
*
- * <p>Currently we support following features in the raw query format:
+ * <p>Query strings can be empty, contain one term with no operators, or contain multiple terms
+ * and operators.
+ *
+ * <p>For query strings that are empty, all documents that match the {@link SearchSpec} will be
+ * returned.
+ *
+ * <p>For query strings with a single term and no operators, documents that match the provided
+ * query string and {@link SearchSpec} will be returned.
+ *
+ * <p>The following operators are supported:
+ *
* <ul>
- * <li>AND
- * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and
- * ‘cat’”).
- * Example: hello world matches documents that have both ‘hello’ and ‘world’
- * <li>OR
- * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or
- * ‘cat’”).
- * Example: dog OR puppy
- * <li>Exclusion
- * <p>Exclude a term (e.g. “match documents that do
- * not have the term ‘dog’”).
- * Example: -dog excludes the term ‘dog’
- * <li>Grouping terms
- * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
- * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
- * Example: (dog puppy) (cat kitten) two one group containing two terms.
- * <li>Property restricts
- * <p> Specifies which properties of a document to specifically match terms in (e.g.
- * “match documents where the ‘subject’ property contains ‘important’”).
- * Example: subject:important matches documents with the term ‘important’ in the
- * ‘subject’ property
- * <li>Schema type restricts
- * <p>This is similar to property restricts, but allows for restricts on top-level document
- * fields, such as schema_type. Clients should be able to limit their query to documents of
- * a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”).
- * Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents
- * that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the
- * ‘Video’ schema type.
+ * <li>AND (implicit)
+ * <p>AND is an operator that matches documents that contain <i>all</i> provided terms.
+ * <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly
+ * including "AND" in a query string will treat "AND" as a term, returning documents that
+ * also contain "AND".
+ * <p>Example: "apple AND banana" matches documents that contain the terms "apple", "and",
+ * "banana".
+ * <p>Example: "apple banana" matches documents that contain both "apple" and "banana".
+ * <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and
+ * "cherry".
+ * <li>OR
+ * <p>OR is an operator that matches documents that contain <i>any</i> provided term.
+ * <p>Example: "apple OR banana" matches documents that contain either "apple" or
+ * "banana".
+ * <p>Example: "apple OR banana OR cherry" matches documents that contain any of "apple",
+ * "banana", or "cherry".
+ * <li>Exclusion (-)
+ * <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the
+ * provided term.
+ * <p>Example: "-apple" matches documents that do not contain "apple".
+ * <li>Grouped Terms
+ * <p>For queries that require multiple operators and terms, terms can be grouped into
+ * subqueries. Subqueries are contained within an open "(" and close ")" parenthesis.
+ * <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain either
+ * "donut" or "bagel" and either "coffee" or "tea".
+ * <li>Property Restricts
+ * <p>For queries that require a term to match a specific {@link AppSearchSchema} property
+ * of a document, a ":" must be included between the property name and the term.
+ * <p>Example: "subject:important" matches documents that contain the term "important" in
+ * the "subject" property.
* </ul>
*
- * <p> This method is lightweight. The heavy work will be done in
- * {@link SearchResults#getNextPage}.
+ * <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or
+ * adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter.
*
- * @param queryExpression Query String to search.
- * @param searchSpec Spec for setting filters, raw query etc.
+ * <p>This method is lightweight. The heavy work will be done in {@link
+ * SearchResults#getNextPage}.
+ *
+ * @param queryExpression query string to search.
+ * @param searchSpec spec for setting document filters, adding projection, setting term match
+ * type, etc.
* @param executor Executor on which to invoke the callback of the following request
* {@link SearchResults#getNextPage}.
- * @return The search result of performing this operation.
+ * @return a {@link SearchResults} object for retrieved matched documents.
*/
@NonNull
public SearchResults search(
@@ -440,8 +487,8 @@
*
* <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
* metrics for that particular document. These metrics are used for ordering {@link #search}
- * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and
- * {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
+ * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link
+ * SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
*
* <p>Reporting usage of a document is optional.
*
@@ -479,9 +526,17 @@
}
/**
- * Removes {@link GenericDocument}s from the index by URI.
+ * Removes {@link GenericDocument} objects by URIs and namespace from the {@link
+ * AppSearchSession} database.
*
- * @param request Request containing URIs to be removed.
+ * <p>Removed documents will no longer be surfaced by {@link #search} or {@link #getByUri}
+ * calls.
+ *
+ * <p><b>NOTE:</b>By default, documents are removed via a soft delete operation. Once the
+ * document crosses the count threshold or byte usage threshold, the documents will be removed
+ * from disk.
+ *
+ * @param request {@link RemoveByUriRequest} with URIs and namespace to remove from the index.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive the pending result of performing this operation. The keys
* of the returned {@link AppSearchBatchResult} are the input URIs. The values
@@ -520,19 +575,18 @@
/**
* Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
- * match the {@code queryExpression} in given namespaces and schemaTypes which is set via
- * {@link SearchSpec.Builder#addFilterNamespaces} and
- * {@link SearchSpec.Builder#addFilterSchemas}.
+ * match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@link
+ * SearchSpec.Builder#addFilterNamespaces} and {@link SearchSpec.Builder#addFilterSchemas}.
*
- * <p> An empty {@code queryExpression} matches all documents.
+ * <p>An empty {@code queryExpression} matches all documents.
*
- * <p> An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in
- * the current database.
+ * <p>An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in the
+ * current database.
*
* @param queryExpression Query String to search.
- * @param searchSpec Spec containing schemaTypes, namespaces and query expression indicates
- * how document will be removed. All specific about how to scoring,
- * ordering, snippeting and resulting will be ignored.
+ * @param searchSpec Spec containing schemaTypes, namespaces and query expression indicates how
+ * document will be removed. All specific about how to scoring, ordering, snippeting and
+ * resulting will be ignored.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive errors resulting from removing the documents. If
* the operation succeeds, the callback will be invoked with
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
index 8651834..09bca4f 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 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.
@@ -32,7 +32,7 @@
/**
* This class provides global access to the centralized AppSearch index maintained by the system.
*
- * <p>Apps can retrieve indexed documents through the query API.
+ * <p>Apps can retrieve indexed documents through the {@link #search} API.
*/
public class GlobalSearchSession implements Closeable {
@@ -90,48 +90,26 @@
}
/**
- * Searches across all documents in the storage based on a given query string.
+ * Retrieves documents from all AppSearch databases that the querying application has access to.
*
- * <p>Currently we support following features in the raw query format:
- * <ul>
- * <li>AND
- * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and
- * ‘cat’”).
- * Example: hello world matches documents that have both ‘hello’ and ‘world’
- * <li>OR
- * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or
- * ‘cat’”).
- * Example: dog OR puppy
- * <li>Exclusion
- * <p>Exclude a term (e.g. “match documents that do
- * not have the term ‘dog’”).
- * Example: -dog excludes the term ‘dog’
- * <li>Grouping terms
- * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
- * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
- * Example: (dog puppy) (cat kitten) two one group containing two terms.
- * <li>Property restricts
- * <p> Specifies which properties of a document to specifically match terms in (e.g.
- * “match documents where the ‘subject’ property contains ‘important’”).
- * Example: subject:important matches documents with the term ‘important’ in the
- * ‘subject’ property
- * <li>Schema type restricts
- * <p>This is similar to property restricts, but allows for restricts on top-level document
- * fields, such as schema_type. Clients should be able to limit their query to documents of
- * a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”).
- * Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents
- * that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the
- * ‘Video’ schema type.
- * </ul>
+ * <p>Applications can be granted access to documents by specifying {@link
+ * SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage} when building a schema.
*
- * <p> This method is lightweight. The heavy work will be done in
- * {@link SearchResults#getNextPage}.
+ * <p>Document access can also be granted to system UIs by specifying {@link
+ * SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} when building a schema.
*
- * @param queryExpression Query String to search.
- * @param searchSpec Spec for setting filters, raw query etc.
+ * <p>See {@link AppSearchSession#search} for a detailed explanation on
+ * forming a query string.
+ *
+ * <p>This method is lightweight. The heavy work will be done in {@link
+ * SearchResults#getNextPage}.
+ *
+ * @param queryExpression query string to search.
+ * @param searchSpec spec for setting document filters, adding projection, setting term match
+ * type, etc.
* @param executor Executor on which to invoke the callback of the following request
* {@link SearchResults#getNextPage}.
- * @return The search result of performing this operation.
+ * @return a {@link SearchResults} object for retrieved matched documents.
*/
@NonNull
public SearchResults search(
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
index 704509b..a63e015 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
@@ -35,8 +35,8 @@
/**
* SearchResults are a returned object from a query API.
*
- * <p>Each {@link SearchResult} contains a document and may contain other fields like snippets
- * based on request.
+ * <p>Each {@link SearchResult} contains a document and may contain other fields like snippets based
+ * on request.
*
* <p>Should close this object after finish fetching results.
*
@@ -89,8 +89,8 @@
/**
* Gets a whole page of {@link SearchResult}s.
*
- * <p>Re-call this method to get next page of {@link SearchResult}, until it returns an
- * empty list.
+ * <p>Re-call this method to get next page of {@link SearchResult}, until it returns an empty
+ * list.
*
* <p>The page size is set by {@link SearchSpec.Builder#setResultCountPerPage}.
*
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
index e94b3b2..8bf438d 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -90,6 +90,7 @@
* <p>This method creates a new list when called.
*/
@NonNull
+ @SuppressWarnings("MixedMutabilityReturnType")
public List<PropertyConfig> getProperties() {
ArrayList<Bundle> propertyBundles =
mBundle.getParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
index 4c11514..138eb23 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -36,7 +36,8 @@
/**
* Represents a document unit.
*
- * <p>Documents are constructed via {@link GenericDocument.Builder}.
+ * <p>Documents contain structured data conforming to their {@link AppSearchSchema} type. Each
+ * document is uniquely identified by a URI and namespace.
*
* @see AppSearchSession#put
* @see AppSearchSession#getByUri
@@ -48,16 +49,10 @@
/** 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. */
private static final int MAX_REPEATED_PROPERTY_LENGTH = 100;
- /**
- * The maximum {@link String#length} of a {@link String} field. Will reject the request if
- * {@link String}s longer than this.
- */
+ /** The maximum {@link String#length} of a {@link String} field. */
private static final int MAX_STRING_LENGTH = 20_000;
/** The maximum number of indexed properties a document can have. */
@@ -149,7 +144,7 @@
return mBundle.getString(NAMESPACE_FIELD, DEFAULT_NAMESPACE);
}
- /** Returns the schema type of the {@link GenericDocument}. */
+ /** Returns the {@link AppSearchSchema} type of the {@link GenericDocument}. */
@NonNull
public String getSchemaType() {
return mSchemaType;
@@ -165,14 +160,14 @@
}
/**
- * Returns the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
+ * 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 or {@link AppSearchSession#remove} is called.
*/
public long getTtlMillis() {
return mBundle.getLong(TTL_MILLIS_FIELD, DEFAULT_TTL_MILLIS);
@@ -182,12 +177,12 @@
* Returns 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.
+ * {@link GenericDocument} objects of the same {@link AppSearchSchema} 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.
+ * <p>Any non-negative integer can be used a score.
*/
public int getScore() {
return mBundle.getInt(SCORE_FIELD, DEFAULT_SCORE);
@@ -355,7 +350,7 @@
}
/**
- * Retrieves a repeated {@link String} property by key.
+ * Retrieves a repeated {@code long[]} property by key.
*
* @param key The key to look for.
* @return The {@code long[]} associated with the given key, or {@code null} if no value is set
@@ -580,14 +575,17 @@
private boolean mBuilt = false;
/**
- * Create a new {@link GenericDocument.Builder}.
+ * Creates 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#put}. Otherwise, the document will be rejected by {@link
- * AppSearchSession#put}.
+ * <p>Once {@link #build} is called, the instance can no longer be used.
+ *
+ * @param uri the URI to set for the {@link GenericDocument}.
+ * @param schemaType the {@link AppSearchSchema} type of the {@link GenericDocument}. The
+ * provided {@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#put}. Otherwise, the document will
+ * be rejected by {@link AppSearchSession#put} with result code
+ * {@link AppSearchResult#RESULT_NOT_FOUND}.
*/
@SuppressWarnings("unchecked")
public Builder(@NonNull String uri, @NonNull String schemaType) {
@@ -606,15 +604,18 @@
}
/**
- * Sets the app-defined namespace this Document resides in. No special values are reserved
+ * 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.
*
* <p>The number of namespaces per app should be kept small for efficiency reasons.
+ *
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setNamespace(@NonNull String namespace) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
mBundle.putString(GenericDocument.NAMESPACE_FIELD, namespace);
return mBuilderTypeInstance;
}
@@ -623,14 +624,15 @@
* 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.
+ * {@link GenericDocument} objects of the same {@link AppSearchSchema} 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.
+ * <p>Any non-negative integer can be used a score. By default, scores are set to 0.
*
- * @throws IllegalArgumentException If the provided value is negative.
+ * @param score any non-negative {@code int} representing the document's score.
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setScore(@IntRange(from = 0, to = Integer.MAX_VALUE) int score) {
@@ -645,8 +647,11 @@
/**
* 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.
+ * <p>This should be set using a value obtained from the {@link System#currentTimeMillis}
+ * time base.
+ *
+ * @param creationTimestampMillis a creation timestamp in milliseconds.
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setCreationTimestampMillis(long creationTimestampMillis) {
@@ -657,17 +662,17 @@
}
/**
- * Sets the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
+ * Sets 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.
+ * auto-deleted until the app is uninstalled or {@link AppSearchSession#remove} is called.
*
- * @param ttlMillis A non-negative duration in milliseconds.
- * @throws IllegalArgumentException If the provided value is negative.
+ * @param ttlMillis a non-negative duration in milliseconds.
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setTtlMillis(long ttlMillis) {
@@ -682,8 +687,11 @@
/**
* 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.
+ * @param key the key associated with the {@code values}.
+ * @param values the {@code String} values of the property.
+ * @throws IllegalArgumentException if no values are provided, if provided values exceed
+ * maximum repeated property length, or if a passed in {@code String} is {@code null}.
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyString(@NonNull String key, @NonNull String... values) {
@@ -698,8 +706,11 @@
* Sets one or multiple {@code boolean} values for a property, replacing its previous
* values.
*
- * @param key The key associated with the {@code values}.
- * @param values The {@code boolean} values of the property.
+ * @param key the key associated with the {@code values}.
+ * @param values the {@code boolean} values of the property.
+ * @throws IllegalArgumentException if no values are provided or if values exceed maximum
+ * repeated property length.
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyBoolean(@NonNull String key, @NonNull boolean... values) {
@@ -713,8 +724,11 @@
/**
* 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.
+ * @param key the key associated with the {@code values}.
+ * @param values the {@code long} values of the property.
+ * @throws IllegalArgumentException if no values are provided or if values exceed maximum
+ * repeated property length.
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyLong(@NonNull String key, @NonNull long... values) {
@@ -728,8 +742,11 @@
/**
* 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.
+ * @param key the key associated with the {@code values}.
+ * @param values the {@code double} values of the property.
+ * @throws IllegalArgumentException if no values are provided or if values exceed maximum
+ * repeated property length.
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyDouble(@NonNull String key, @NonNull double... values) {
@@ -743,8 +760,11 @@
/**
* Sets one or multiple {@code byte[]} for a property, replacing its previous values.
*
- * @param key The key associated with the {@code values}.
- * @param values The {@code byte[]} of the property.
+ * @param key the key associated with the {@code values}.
+ * @param values the {@code byte[]} of the property.
+ * @throws IllegalArgumentException if no values are provided, if provided values exceed
+ * maximum repeated property length, or if a passed in {@code byte[]} is {@code null}.
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyBytes(@NonNull String key, @NonNull byte[]... values) {
@@ -759,8 +779,12 @@
* Sets one or multiple {@link GenericDocument} values for a property, replacing its
* previous values.
*
- * @param key The key associated with the {@code values}.
- * @param values The {@link GenericDocument} values of the property.
+ * @param key the key associated with the {@code values}.
+ * @param values the {@link GenericDocument} values of the property.
+ * @throws IllegalArgumentException if no values are provided, if provided values exceed if
+ * provided values exceed maximum repeated property length, or if a passed in {@link
+ * GenericDocument} is {@code null}.
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyDocument(
@@ -853,7 +877,11 @@
}
}
- /** Builds the {@link GenericDocument} object. */
+ /**
+ * Builds the {@link GenericDocument} object.
+ *
+ * @throws IllegalStateException if the builder has already been used.
+ */
@NonNull
public GenericDocument build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
index 656608d..c927e34 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -31,7 +31,8 @@
import java.util.Set;
/**
- * Encapsulates a request to retrieve documents by namespace and URI.
+ * Encapsulates a request to retrieve documents by namespace and URIs from the {@link
+ * AppSearchSession} database.
*
* @see AppSearchSession#getByUri
*/
@@ -56,13 +57,13 @@
mTypePropertyPathsMap = Preconditions.checkNotNull(typePropertyPathsMap);
}
- /** Returns the namespace to get documents from. */
+ /** Returns the namespace attached to the request. */
@NonNull
public String getNamespace() {
return mNamespace;
}
- /** Returns the URIs to get from the namespace. */
+ /** Returns the set of URIs attached to the request. */
@NonNull
public Set<String> getUris() {
return Collections.unmodifiableSet(mUris);
@@ -100,7 +101,11 @@
return mTypePropertyPathsMap;
}
- /** Builder for {@link GetByUriRequest} objects. */
+ /**
+ * Builder for {@link GetByUriRequest} objects.
+ *
+ * <p>Once {@link #build} is called, the instance can no longer be used.
+ */
public static final class Builder {
private String mNamespace = GenericDocument.DEFAULT_NAMESPACE;
private final Set<String> mUris = new ArraySet<>();
@@ -108,9 +113,12 @@
private boolean mBuilt = false;
/**
- * Sets which namespace these documents will be retrieved from.
+ * Sets the namespace to retrieve documents for.
*
- * <p>If this is not set, it defaults to {@link GenericDocument#DEFAULT_NAMESPACE}.
+ * <p>If this is not called, the namespace defaults to {@link
+ * GenericDocument#DEFAULT_NAMESPACE}.
+ *
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public Builder setNamespace(@NonNull String namespace) {
@@ -120,14 +128,22 @@
return this;
}
- /** Adds one or more URIs to the request. */
+ /**
+ * Adds one or more URIs to the request.
+ *
+ * @throws IllegalStateException if the builder has already been used.
+ */
@NonNull
public Builder addUris(@NonNull String... uris) {
Preconditions.checkNotNull(uris);
return addUris(Arrays.asList(uris));
}
- /** Adds one or more URIs to the request. */
+ /**
+ * Adds a collection of URIs to the request.
+ *
+ * @throws IllegalStateException if the builder has already been used.
+ */
@NonNull
public Builder addUris(@NonNull Collection<String> uris) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
@@ -149,7 +165,8 @@
* GetByUriRequest#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will apply to
* all results, excepting any types that have their own, specific property paths set.
*
- * <p>{@see SearchSpec.Builder#addProjection(String, String...)}
+ * @throws IllegalStateException if the builder has already been used.
+ * <p>{@see SearchSpec.Builder#addProjection(String, String...)}
*/
@NonNull
public Builder addProjection(@NonNull String schemaType, @NonNull String... propertyPaths) {
@@ -170,7 +187,8 @@
* GetByUriRequest#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will apply to
* all results, excepting any types that have their own, specific property paths set.
*
- * <p>{@see SearchSpec.Builder#addProjection(String, String...)}
+ * @throws IllegalStateException if the builder has already been used.
+ * <p>{@see SearchSpec.Builder#addProjection(String, String...)}
*/
@NonNull
public Builder addProjection(
@@ -187,7 +205,11 @@
return this;
}
- /** Builds a new {@link GetByUriRequest}. */
+ /**
+ * Builds a new {@link GetByUriRequest}.
+ *
+ * @throws IllegalStateException if the builder has already been used.
+ */
@NonNull
public GetByUriRequest build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
index 198eee8..39b53b6 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -27,7 +27,8 @@
import java.util.Set;
/**
- * Encapsulates a request to remove documents by namespace and URI.
+ * Encapsulates a request to remove documents by namespace and URIs from the {@link
+ * AppSearchSession} database.
*
* @see AppSearchSession#remove
*/
@@ -46,22 +47,28 @@
return mNamespace;
}
- /** Returns the URIs of documents to remove from the namespace. */
+ /** Returns the set of URIs attached to the request. */
@NonNull
public Set<String> getUris() {
return Collections.unmodifiableSet(mUris);
}
- /** Builder for {@link RemoveByUriRequest} objects. */
+ /**
+ * Builder for {@link RemoveByUriRequest} objects.
+ *
+ * <p>Once {@link #build} is called, the instance can no longer be used.
+ */
public static final class Builder {
private String mNamespace = GenericDocument.DEFAULT_NAMESPACE;
private final Set<String> mUris = new ArraySet<>();
private boolean mBuilt = false;
/**
- * Sets which namespace these documents will be removed from.
+ * Sets the namespace to remove documents for.
*
* <p>If this is not set, it defaults to {@link GenericDocument#DEFAULT_NAMESPACE}.
+ *
+ * @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public Builder setNamespace(@NonNull String namespace) {
@@ -71,14 +78,22 @@
return this;
}
- /** Adds one or more URIs to the request. */
+ /**
+ * Adds one or more URIs to the request.
+ *
+ * @throws IllegalStateException if the builder has already been used.
+ */
@NonNull
public Builder addUris(@NonNull String... uris) {
Preconditions.checkNotNull(uris);
return addUris(Arrays.asList(uris));
}
- /** Adds one or more URIs to the request. */
+ /**
+ * Adds a collection of URIs to the request.
+ *
+ * @throws IllegalStateException if the builder has already been used.
+ */
@NonNull
public Builder addUris(@NonNull Collection<String> uris) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
@@ -87,7 +102,11 @@
return this;
}
- /** Builds a new {@link RemoveByUriRequest}. */
+ /**
+ * Builds a new {@link RemoveByUriRequest}.
+ *
+ * @throws IllegalStateException if the builder has already been used.
+ */
@NonNull
public RemoveByUriRequest build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
index 4869aa3..bc99d4f 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -16,10 +16,9 @@
package android.app.appsearch;
-import static android.app.appsearch.AppSearchResult.RESULT_OK;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Bundle;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
@@ -32,23 +31,58 @@
/** The response class of {@link AppSearchSession#setSchema} */
public class SetSchemaResponse {
- private final List<MigrationFailure> mMigrationFailures;
- private final Set<String> mDeletedTypes;
- private final Set<String> mMigratedTypes;
- private final Set<String> mIncompatibleTypes;
- private final @AppSearchResult.ResultCode int mResultCode;
- SetSchemaResponse(
- @NonNull List<MigrationFailure> migrationFailures,
- @NonNull Set<String> deletedTypes,
- @NonNull Set<String> migratedTypes,
- @NonNull Set<String> incompatibleTypes,
- @AppSearchResult.ResultCode int resultCode) {
+ private static final String DELETED_TYPES_FIELD = "deletedTypes";
+ private static final String INCOMPATIBLE_TYPES_FIELD = "incompatibleTypes";
+ private static final String MIGRATED_TYPES_FIELD = "migratedTypes";
+
+ private final Bundle mBundle;
+ /**
+ * The migrationFailures won't be saved in the bundle. Since:
+ *
+ * <ul>
+ * <li>{@link MigrationFailure} is generated in {@link AppSearchSession} which will be the SDK
+ * side in platform. We don't need to pass it from service side via binder.
+ * <li>Translate multiple {@link MigrationFailure}s to bundles in {@link Builder} and then
+ * back in constructor will be a huge waste.
+ * </ul>
+ */
+ private final List<MigrationFailure> mMigrationFailures;
+
+ /** Cache of the inflated deleted schema types. Comes from inflating mBundles at first use. */
+ @Nullable private Set<String> mDeletedTypes;
+
+ /** Cache of the inflated migrated schema types. Comes from inflating mBundles at first use. */
+ @Nullable private Set<String> mMigratedTypes;
+
+ /**
+ * Cache of the inflated incompatible schema types. Comes from inflating mBundles at first use.
+ */
+ @Nullable private Set<String> mIncompatibleTypes;
+
+ SetSchemaResponse(@NonNull Bundle bundle, @NonNull List<MigrationFailure> migrationFailures) {
+ mBundle = Preconditions.checkNotNull(bundle);
mMigrationFailures = Preconditions.checkNotNull(migrationFailures);
- mDeletedTypes = Preconditions.checkNotNull(deletedTypes);
- mMigratedTypes = Preconditions.checkNotNull(migratedTypes);
- mIncompatibleTypes = Preconditions.checkNotNull(incompatibleTypes);
- mResultCode = resultCode;
+ }
+
+ SetSchemaResponse(@NonNull Bundle bundle) {
+ this(bundle, /*migrationFailures=*/ Collections.emptyList());
+ }
+
+ /**
+ * Returns the {@link Bundle} populated by this builder.
+ *
+ * @hide
+ */
+ @NonNull
+ public Bundle getBundle() {
+ return mBundle;
+ }
+
+ /** TODO(b/177266929): Remove this deprecated method */
+ //@Deprecated
+ public boolean isSuccess() {
+ return true;
}
/**
@@ -72,6 +106,12 @@
*/
@NonNull
public Set<String> getDeletedTypes() {
+ if (mDeletedTypes == null) {
+ mDeletedTypes =
+ new ArraySet<>(
+ Preconditions.checkNotNull(
+ mBundle.getStringArrayList(DELETED_TYPES_FIELD)));
+ }
return Collections.unmodifiableSet(mDeletedTypes);
}
@@ -81,6 +121,12 @@
*/
@NonNull
public Set<String> getMigratedTypes() {
+ if (mMigratedTypes == null) {
+ mMigratedTypes =
+ new ArraySet<>(
+ Preconditions.checkNotNull(
+ mBundle.getStringArrayList(MIGRATED_TYPES_FIELD)));
+ }
return Collections.unmodifiableSet(mMigratedTypes);
}
@@ -96,22 +142,28 @@
*/
@NonNull
public Set<String> getIncompatibleTypes() {
+ if (mIncompatibleTypes == null) {
+ mIncompatibleTypes =
+ new ArraySet<>(
+ Preconditions.checkNotNull(
+ mBundle.getStringArrayList(INCOMPATIBLE_TYPES_FIELD)));
+ }
return Collections.unmodifiableSet(mIncompatibleTypes);
}
- /** Returns {@code true} if all {@link AppSearchSchema}s are successful set to the system. */
- public boolean isSuccess() {
- return mResultCode == RESULT_OK;
- }
-
- @Override
+ /**
+ * Translates the {@link SetSchemaResponse}'s bundle to {@link Builder}.
+ *
+ * @hide
+ */
@NonNull
- public String toString() {
- return "{\n Does setSchema success? : "
- + isSuccess()
- + "\n failures: "
- + mMigrationFailures
- + "\n}";
+ // TODO(b/179302942) change to Builder(mBundle) powered by mBundle.deepCopy
+ public Builder toBuilder() {
+ return new Builder()
+ .addDeletedTypes(getDeletedTypes())
+ .addIncompatibleTypes(getIncompatibleTypes())
+ .addMigratedTypes(getMigratedTypes())
+ .addMigrationFailures(mMigrationFailures);
}
/**
@@ -120,44 +172,26 @@
* @hide
*/
public static class Builder {
- private final List<MigrationFailure> mMigrationFailures = new ArrayList<>();
- private final Set<String> mDeletedTypes = new ArraySet<>();
- private final Set<String> mMigratedTypes = new ArraySet<>();
- private final Set<String> mIncompatibleTypes = new ArraySet<>();
- private @AppSearchResult.ResultCode int mResultCode = RESULT_OK;
+ private final ArrayList<MigrationFailure> mMigrationFailures = new ArrayList<>();
+ private final ArrayList<String> mDeletedTypes = new ArrayList<>();
+ private final ArrayList<String> mMigratedTypes = new ArrayList<>();
+ private final ArrayList<String> mIncompatibleTypes = new ArrayList<>();
private boolean mBuilt = false;
- /** Adds a {@link MigrationFailure}. */
+ /** Adds {@link MigrationFailure}s to the list of migration failures. */
@NonNull
- public Builder setFailure(
- @NonNull String schemaType,
- @NonNull String namespace,
- @NonNull String uri,
- @NonNull AppSearchResult<Void> failureResult) {
+ public Builder addMigrationFailures(
+ @NonNull Collection<MigrationFailure> migrationFailures) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(schemaType);
- Preconditions.checkNotNull(namespace);
- Preconditions.checkNotNull(uri);
- Preconditions.checkNotNull(failureResult);
- Preconditions.checkState(!failureResult.isSuccess());
- mMigrationFailures.add(new MigrationFailure(schemaType, namespace, uri, failureResult));
+ mMigrationFailures.addAll(Preconditions.checkNotNull(migrationFailures));
return this;
}
- /** Adds a {@link MigrationFailure}. */
+ /** Adds a {@link MigrationFailure} to the list of migration failures. */
@NonNull
- public Builder setFailure(
- @NonNull String schemaType,
- @NonNull String namespace,
- @NonNull String uri,
- @AppSearchResult.ResultCode int resultCode,
- @Nullable String errorMessage) {
- mMigrationFailures.add(
- new MigrationFailure(
- schemaType,
- namespace,
- uri,
- AppSearchResult.newFailedResult(resultCode, errorMessage)));
+ public Builder addMigrationFailure(@NonNull MigrationFailure migrationFailure) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mMigrationFailures.add(Preconditions.checkNotNull(migrationFailure));
return this;
}
@@ -169,6 +203,14 @@
return this;
}
+ /** Adds one deletedType to the list of deleted schema types. */
+ @NonNull
+ public Builder addDeletedType(@NonNull String deletedType) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mDeletedTypes.add(Preconditions.checkNotNull(deletedType));
+ return this;
+ }
+
/** Adds incompatibleTypes to the list of incompatible schema types. */
@NonNull
public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) {
@@ -177,6 +219,14 @@
return this;
}
+ /** Adds one incompatibleType to the list of incompatible schema types. */
+ @NonNull
+ public Builder addIncompatibleType(@NonNull String incompatibleType) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mIncompatibleTypes.add(Preconditions.checkNotNull(incompatibleType));
+ return this;
+ }
+
/** Adds migratedTypes to the list of migrated schema types. */
@NonNull
public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) {
@@ -185,11 +235,11 @@
return this;
}
- /** Sets the {@link AppSearchResult.ResultCode} of the response. */
+ /** Adds one migratedType to the list of migrated schema types. */
@NonNull
- public Builder setResultCode(@AppSearchResult.ResultCode int resultCode) {
+ public Builder addMigratedType(@NonNull String migratedType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mResultCode = resultCode;
+ mMigratedTypes.add(Preconditions.checkNotNull(migratedType));
return this;
}
@@ -197,13 +247,15 @@
@NonNull
public SetSchemaResponse build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Bundle bundle = new Bundle();
+ bundle.putStringArrayList(INCOMPATIBLE_TYPES_FIELD, mIncompatibleTypes);
+ bundle.putStringArrayList(DELETED_TYPES_FIELD, mDeletedTypes);
+ bundle.putStringArrayList(MIGRATED_TYPES_FIELD, mMigratedTypes);
mBuilt = true;
- return new SetSchemaResponse(
- mMigrationFailures,
- mDeletedTypes,
- mMigratedTypes,
- mIncompatibleTypes,
- mResultCode);
+ // Avoid converting the potential thousands of MigrationFailures to Pracelable and
+ // back just for put in bundle. In platform, we should set MigrationFailures in
+ // AppSearchSession after we pass SetSchemaResponse via binder.
+ return new SetSchemaResponse(bundle, mMigrationFailures);
}
}
@@ -212,38 +264,44 @@
* {@link AppSearchSession#setSchema}.
*/
public static class MigrationFailure {
- private final String mSchemaType;
- private final String mNamespace;
- private final String mUri;
- AppSearchResult<Void> mFailureResult;
+ private static final String SCHEMA_TYPE_FIELD = "schemaType";
+ private static final String NAMESPACE_FIELD = "namespace";
+ private static final String URI_FIELD = "uri";
+ private static final String ERROR_MESSAGE_FIELD = "errorMessage";
+ private static final String RESULT_CODE_FIELD = "resultCode";
- MigrationFailure(
- @NonNull String schemaType,
- @NonNull String namespace,
- @NonNull String uri,
- @NonNull AppSearchResult<Void> result) {
- mSchemaType = schemaType;
- mNamespace = namespace;
- mUri = uri;
- mFailureResult = result;
+ private final Bundle mBundle;
+
+ MigrationFailure(@NonNull Bundle bundle) {
+ mBundle = bundle;
+ }
+
+ /**
+ * Returns the Bundle of the {@link MigrationFailure}.
+ *
+ * @hide
+ */
+ @NonNull
+ public Bundle getBundle() {
+ return mBundle;
}
/** Returns the schema type of the {@link GenericDocument} that fails to be migrated. */
@NonNull
public String getSchemaType() {
- return mSchemaType;
+ return mBundle.getString(SCHEMA_TYPE_FIELD, /*defaultValue=*/ "");
}
/** Returns the namespace of the {@link GenericDocument} that fails to be migrated. */
@NonNull
public String getNamespace() {
- return mNamespace;
+ return mBundle.getString(NAMESPACE_FIELD, /*defaultValue=*/ "");
}
/** Returns the uri of the {@link GenericDocument} that fails to be migrated. */
@NonNull
public String getUri() {
- return mUri;
+ return mBundle.getString(URI_FIELD, /*defaultValue=*/ "");
}
/**
@@ -252,7 +310,69 @@
*/
@NonNull
public AppSearchResult<Void> getAppSearchResult() {
- return mFailureResult;
+ return AppSearchResult.newFailedResult(
+ mBundle.getInt(RESULT_CODE_FIELD),
+ mBundle.getString(ERROR_MESSAGE_FIELD, /*defaultValue=*/ ""));
+ }
+
+ /**
+ * Builder for {@link MigrationFailure} objects.
+ *
+ * @hide
+ */
+ public static class Builder {
+ private String mSchemaType;
+ private String mNamespace;
+ private String mUri;
+ private final Bundle mBundle = new Bundle();
+ private AppSearchResult<Void> mFailureResult;
+ private boolean mBuilt = false;
+
+ /** Sets the schema type for the {@link MigrationFailure}. */
+ @NonNull
+ public Builder setSchemaType(@NonNull String schemaType) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mSchemaType = Preconditions.checkNotNull(schemaType);
+ return this;
+ }
+
+ /** Sets the namespace for the {@link MigrationFailure}. */
+ @NonNull
+ public Builder setNamespace(@NonNull String namespace) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mNamespace = Preconditions.checkNotNull(namespace);
+ return this;
+ }
+
+ /** Sets the uri for the {@link MigrationFailure}. */
+ @NonNull
+ public Builder setUri(@NonNull String uri) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mUri = Preconditions.checkNotNull(uri);
+ return this;
+ }
+
+ /** Sets the failure {@link AppSearchResult} for the {@link MigrationFailure}. */
+ @NonNull
+ public Builder setAppSearchResult(@NonNull AppSearchResult<Void> appSearchResult) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkState(!appSearchResult.isSuccess(), "Input a success result");
+ mFailureResult = Preconditions.checkNotNull(appSearchResult);
+ return this;
+ }
+
+ /** Builds a {@link MigrationFailure} object. */
+ @NonNull
+ public MigrationFailure build() {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBundle.putString(SCHEMA_TYPE_FIELD, mSchemaType);
+ mBundle.putString(NAMESPACE_FIELD, mNamespace);
+ mBundle.putString(URI_FIELD, mUri);
+ mBundle.putString(ERROR_MESSAGE_FIELD, mFailureResult.getErrorMessage());
+ mBundle.putInt(RESULT_CODE_FIELD, mFailureResult.getResultCode());
+ mBuilt = true;
+ return new MigrationFailure(mBundle);
+ }
}
}
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java
deleted file mode 100644
index f04ace6..0000000
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.appsearch;
-
-import android.annotation.NonNull;
-import android.os.Bundle;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * This class represents the results of setSchema().
- *
- * @hide
- */
-public class SetSchemaResult {
-
- public static final String DELETED_SCHEMA_TYPES_FIELD = "deletedSchemaTypes";
- public static final String INCOMPATIBLE_SCHEMA_TYPES_FIELD = "incompatibleSchemaTypes";
- public static final String RESULT_CODE_FIELD = "resultCode";
- private final List<String> mDeletedSchemaTypes;
- private final List<String> mIncompatibleSchemaTypes;
- private final Bundle mBundle;
-
- SetSchemaResult(@NonNull Bundle bundle) {
- mBundle = Preconditions.checkNotNull(bundle);
- mDeletedSchemaTypes =
- Preconditions.checkNotNull(mBundle.getStringArrayList(DELETED_SCHEMA_TYPES_FIELD));
- mIncompatibleSchemaTypes =
- Preconditions.checkNotNull(
- mBundle.getStringArrayList(INCOMPATIBLE_SCHEMA_TYPES_FIELD));
- }
-
- /** Returns the {@link Bundle} of this class. */
- @NonNull
- public Bundle getBundle() {
- return mBundle;
- }
-
- /** returns all deleted schema types in this setSchema call. */
- @NonNull
- public List<String> getDeletedSchemaTypes() {
- return Collections.unmodifiableList(mDeletedSchemaTypes);
- }
-
- /** returns all incompatible schema types in this setSchema call. */
- @NonNull
- public List<String> getIncompatibleSchemaTypes() {
- return Collections.unmodifiableList(mIncompatibleSchemaTypes);
- }
-
- /**
- * returns the {@link android.app.appsearch.AppSearchResult.ResultCode} of the {@link
- * AppSearchSession#setSchema} call.
- */
- public int getResultCode() {
- return mBundle.getInt(RESULT_CODE_FIELD);
- }
-
- /** Builder for {@link SetSchemaResult} objects. */
- public static final class Builder {
- private final ArrayList<String> mDeletedSchemaTypes = new ArrayList<>();
- private final ArrayList<String> mIncompatibleSchemaTypes = new ArrayList<>();
- @AppSearchResult.ResultCode private int mResultCode;
- private boolean mBuilt = false;
-
- /** Adds a deletedSchemaTypes to the {@link SetSchemaResult}. */
- @NonNull
- public Builder addDeletedSchemaType(@NonNull String deletedSchemaType) {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- mDeletedSchemaTypes.add(Preconditions.checkNotNull(deletedSchemaType));
- return this;
- }
-
- /** Adds a incompatible SchemaTypes to the {@link SetSchemaResult}. */
- @NonNull
- public Builder addIncompatibleSchemaType(@NonNull String incompatibleSchemaTypes) {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- mIncompatibleSchemaTypes.add(Preconditions.checkNotNull(incompatibleSchemaTypes));
- return this;
- }
-
- /**
- * Sets the {@link android.app.appsearch.AppSearchResult.ResultCode} of the {@link
- * AppSearchSession#setSchema} call to the {@link SetSchemaResult}
- */
- @NonNull
- public Builder setResultCode(@AppSearchResult.ResultCode int resultCode) {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- mResultCode = resultCode;
- return this;
- }
-
- /** Builds a {@link SetSchemaResult}. */
- @NonNull
- public SetSchemaResult build() {
- Preconditions.checkState(!mBuilt, "Builder has already been used");
- Bundle bundle = new Bundle();
- bundle.putStringArrayList(
- SetSchemaResult.DELETED_SCHEMA_TYPES_FIELD, mDeletedSchemaTypes);
- bundle.putStringArrayList(
- SetSchemaResult.INCOMPATIBLE_SCHEMA_TYPES_FIELD, mIncompatibleSchemaTypes);
- bundle.putInt(RESULT_CODE_FIELD, mResultCode);
- mBuilt = true;
- return new SetSchemaResult(bundle);
- }
- }
-}
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 2f1817e..6c2e30e 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
@@ -25,7 +25,7 @@
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
-import android.app.appsearch.SetSchemaResult;
+import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.os.Bundle;
@@ -41,7 +41,7 @@
import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SearchResultToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SearchSpecToProtoConverter;
-import com.android.server.appsearch.external.localstorage.converter.SetSchemaResultToProtoConverter;
+import com.android.server.appsearch.external.localstorage.converter.SetSchemaResponseToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.TypePropertyPathToProtoConverter;
import com.google.android.icing.IcingSearchEngine;
@@ -73,6 +73,7 @@
import com.google.android.icing.proto.TypePropertyMask;
import com.google.android.icing.proto.UsageReport;
+import java.io.Closeable;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
@@ -119,7 +120,7 @@
* @hide
*/
@WorkerThread
-public final class AppSearchImpl {
+public final class AppSearchImpl implements Closeable {
private static final String TAG = "AppSearchImpl";
@VisibleForTesting static final char DATABASE_DELIMITER = '/';
@@ -151,12 +152,16 @@
private final Map<String, Set<String>> mNamespaceMapLocked = new HashMap<>();
/**
- * The counter to check when to call {@link #checkForOptimizeLocked(boolean)}. The interval is
- * {@link #CHECK_OPTIMIZE_INTERVAL}.
+ * The counter to check when to call {@link #checkForOptimize}. The interval is {@link
+ * #CHECK_OPTIMIZE_INTERVAL}.
*/
@GuardedBy("mReadWriteLock")
private int mOptimizeIntervalCountLocked = 0;
+ /** Whether this instance has been closed, and therefore unusable. */
+ @GuardedBy("mReadWriteLock")
+ private boolean mClosedLocked = false;
+
/**
* Creates and initializes an instance of {@link AppSearchImpl} which writes data to the given
* folder.
@@ -208,7 +213,7 @@
} catch (AppSearchException e) {
Log.w(TAG, "Error initializing, resetting IcingSearchEngine.", e);
// Some error. Reset and see if it fixes it.
- reset();
+ resetLocked();
return;
}
@@ -222,11 +227,6 @@
for (String prefixedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
addToMap(mNamespaceMapLocked, getPrefix(prefixedNamespace), prefixedNamespace);
}
-
- // 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.
- checkForOptimizeLocked(/* force= */ true);
-
} finally {
mReadWriteLock.writeLock().unlock();
}
@@ -240,12 +240,45 @@
void initializeVisibilityStore() throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
+ throwIfClosedLocked();
+
mVisibilityStoreLocked.initialize();
} finally {
mReadWriteLock.writeLock().unlock();
}
}
+ @GuardedBy("mReadWriteLock")
+ private void throwIfClosedLocked() {
+ if (mClosedLocked) {
+ throw new IllegalStateException("Trying to use a closed AppSearchImpl instance.");
+ }
+ }
+
+ /**
+ * Persists data to disk and closes the instance.
+ *
+ * <p>This instance is no longer usable after it's been closed. Call {@link #create} to create a
+ * new, usable instance.
+ */
+ @Override
+ public void close() {
+ mReadWriteLock.writeLock().lock();
+ try {
+ if (mClosedLocked) {
+ return;
+ }
+
+ persistToDisk();
+ mIcingSearchEngineLocked.close();
+ mClosedLocked = true;
+ } catch (AppSearchException e) {
+ Log.w(TAG, "Error when closing AppSearchImpl.", e);
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ }
+
/**
* Updates the AppSearch schema for this app.
*
@@ -259,10 +292,14 @@
* @param schemasPackageAccessible Schema types that are visible to the specified packages.
* @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.
- * @throws AppSearchException on IcingSearchEngine error.
+ * @throws AppSearchException On IcingSearchEngine error. If the status code is
+ * FAILED_PRECONDITION for the incompatible change, the exception will be converted to the
+ * SetSchemaResponse.
+ * @return The response contains deleted schema types and incompatible schema types of this
+ * call.
*/
@NonNull
- public SetSchemaResult setSchema(
+ public SetSchemaResponse setSchema(
@NonNull String packageName,
@NonNull String databaseName,
@NonNull List<AppSearchSchema> schemas,
@@ -272,6 +309,8 @@
throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
+ throwIfClosedLocked();
+
SchemaProto.Builder existingSchemaBuilder = getSchemaProtoLocked().toBuilder();
SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
@@ -297,9 +336,16 @@
try {
checkSuccess(setSchemaResultProto.getStatus());
} catch (AppSearchException e) {
- if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
- || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0) {
- return SetSchemaResultToProtoConverter.toSetSchemaResult(
+ // Swallow the exception for the incompatible change case. We will propagate
+ // those deleted schemas and incompatible types to the SetSchemaResponse.
+ boolean isFailedPrecondition =
+ setSchemaResultProto.getStatus().getCode()
+ == StatusProto.Code.FAILED_PRECONDITION;
+ boolean isIncompatible =
+ setSchemaResultProto.getDeletedSchemaTypesCount() > 0
+ || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0;
+ if (isFailedPrecondition && isIncompatible) {
+ return SetSchemaResponseToProtoConverter.toSetSchemaResponse(
setSchemaResultProto, prefix);
} else {
throw e;
@@ -328,16 +374,8 @@
prefixedSchemasNotPlatformSurfaceable,
prefixedSchemasPackageAccessible);
- // Determine whether to schedule an immediate optimize.
- if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
- || (setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0
- && 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.
- checkForOptimizeLocked(/* force= */ true);
- }
- return SetSchemaResultToProtoConverter.toSetSchemaResult(setSchemaResultProto, prefix);
+ return SetSchemaResponseToProtoConverter.toSetSchemaResponse(
+ setSchemaResultProto, prefix);
} finally {
mReadWriteLock.writeLock().unlock();
}
@@ -355,44 +393,47 @@
@NonNull
public List<AppSearchSchema> getSchema(
@NonNull String packageName, @NonNull String databaseName) throws AppSearchException {
- SchemaProto fullSchema;
mReadWriteLock.readLock().lock();
try {
- fullSchema = getSchemaProtoLocked();
+ throwIfClosedLocked();
+
+ SchemaProto fullSchema = getSchemaProtoLocked();
+
+ String prefix = createPrefix(packageName, databaseName);
+ List<AppSearchSchema> result = new ArrayList<>();
+ for (int i = 0; i < fullSchema.getTypesCount(); i++) {
+ String typePrefix = getPrefix(fullSchema.getTypes(i).getSchemaType());
+ if (!prefix.equals(typePrefix)) {
+ continue;
+ }
+ // Rewrite SchemaProto.types.schema_type
+ SchemaTypeConfigProto.Builder typeConfigBuilder =
+ fullSchema.getTypes(i).toBuilder();
+ String newSchemaType = typeConfigBuilder.getSchemaType().substring(prefix.length());
+ typeConfigBuilder.setSchemaType(newSchemaType);
+
+ // Rewrite SchemaProto.types.properties.schema_type
+ for (int propertyIdx = 0;
+ propertyIdx < typeConfigBuilder.getPropertiesCount();
+ propertyIdx++) {
+ PropertyConfigProto.Builder propertyConfigBuilder =
+ typeConfigBuilder.getProperties(propertyIdx).toBuilder();
+ if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
+ String newPropertySchemaType =
+ propertyConfigBuilder.getSchemaType().substring(prefix.length());
+ propertyConfigBuilder.setSchemaType(newPropertySchemaType);
+ typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
+ }
+ }
+
+ AppSearchSchema schema =
+ SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder);
+ result.add(schema);
+ }
+ return result;
} finally {
mReadWriteLock.readLock().unlock();
}
-
- String prefix = createPrefix(packageName, databaseName);
- List<AppSearchSchema> result = new ArrayList<>();
- for (int i = 0; i < fullSchema.getTypesCount(); i++) {
- String typePrefix = getPrefix(fullSchema.getTypes(i).getSchemaType());
- if (!prefix.equals(typePrefix)) {
- continue;
- }
- // Rewrite SchemaProto.types.schema_type
- SchemaTypeConfigProto.Builder typeConfigBuilder = fullSchema.getTypes(i).toBuilder();
- String newSchemaType = typeConfigBuilder.getSchemaType().substring(prefix.length());
- typeConfigBuilder.setSchemaType(newSchemaType);
-
- // Rewrite SchemaProto.types.properties.schema_type
- for (int propertyIdx = 0;
- propertyIdx < typeConfigBuilder.getPropertiesCount();
- propertyIdx++) {
- PropertyConfigProto.Builder propertyConfigBuilder =
- typeConfigBuilder.getProperties(propertyIdx).toBuilder();
- if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
- String newPropertySchemaType =
- propertyConfigBuilder.getSchemaType().substring(prefix.length());
- propertyConfigBuilder.setSchemaType(newPropertySchemaType);
- typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
- }
- }
-
- AppSearchSchema schema = SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder);
- result.add(schema);
- }
- return result;
}
/**
@@ -410,23 +451,22 @@
@NonNull String databaseName,
@NonNull GenericDocument document)
throws AppSearchException {
- DocumentProto.Builder documentBuilder =
- GenericDocumentToProtoConverter.toDocumentProto(document).toBuilder();
- String prefix = createPrefix(packageName, databaseName);
- addPrefixToDocument(documentBuilder, prefix);
-
- PutResultProto putResultProto;
mReadWriteLock.writeLock().lock();
try {
- putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
+ throwIfClosedLocked();
+
+ DocumentProto.Builder documentBuilder =
+ GenericDocumentToProtoConverter.toDocumentProto(document).toBuilder();
+ String prefix = createPrefix(packageName, databaseName);
+ addPrefixToDocument(documentBuilder, prefix);
+
+ PutResultProto putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
addToMap(mNamespaceMapLocked, prefix, documentBuilder.getNamespace());
- // The existing documents with same URI will be deleted, so there maybe some resources
- // could be released after optimize().
- checkForOptimizeLocked(/* force= */ false);
+
+ checkSuccess(putResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
}
- checkSuccess(putResultProto.getStatus());
}
/**
@@ -451,40 +491,42 @@
@NonNull String uri,
@NonNull Map<String, List<String>> typePropertyPaths)
throws AppSearchException {
- GetResultProto getResultProto;
- List<TypePropertyMask> nonPrefixedPropertyMasks =
- TypePropertyPathToProtoConverter.toTypePropertyMaskList(typePropertyPaths);
- List<TypePropertyMask> prefixedPropertyMasks =
- new ArrayList<>(nonPrefixedPropertyMasks.size());
- for (int i = 0; i < nonPrefixedPropertyMasks.size(); ++i) {
- TypePropertyMask typePropertyMask = nonPrefixedPropertyMasks.get(i);
- String nonPrefixedType = typePropertyMask.getSchemaType();
- String prefixedType =
- nonPrefixedType.equals(GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD)
- ? nonPrefixedType
- : createPrefix(packageName, databaseName) + nonPrefixedType;
- prefixedPropertyMasks.add(
- typePropertyMask.toBuilder().setSchemaType(prefixedType).build());
- }
- GetResultSpecProto getResultSpec =
- GetResultSpecProto.newBuilder()
- .addAllTypePropertyMasks(prefixedPropertyMasks)
- .build();
mReadWriteLock.readLock().lock();
try {
- getResultProto =
+ throwIfClosedLocked();
+
+ List<TypePropertyMask> nonPrefixedPropertyMasks =
+ TypePropertyPathToProtoConverter.toTypePropertyMaskList(typePropertyPaths);
+ List<TypePropertyMask> prefixedPropertyMasks =
+ new ArrayList<>(nonPrefixedPropertyMasks.size());
+ for (int i = 0; i < nonPrefixedPropertyMasks.size(); ++i) {
+ TypePropertyMask typePropertyMask = nonPrefixedPropertyMasks.get(i);
+ String nonPrefixedType = typePropertyMask.getSchemaType();
+ String prefixedType =
+ nonPrefixedType.equals(GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD)
+ ? nonPrefixedType
+ : createPrefix(packageName, databaseName) + nonPrefixedType;
+ prefixedPropertyMasks.add(
+ typePropertyMask.toBuilder().setSchemaType(prefixedType).build());
+ }
+ GetResultSpecProto getResultSpec =
+ GetResultSpecProto.newBuilder()
+ .addAllTypePropertyMasks(prefixedPropertyMasks)
+ .build();
+
+ GetResultProto getResultProto =
mIcingSearchEngineLocked.get(
createPrefix(packageName, databaseName) + namespace,
uri,
getResultSpec);
+ checkSuccess(getResultProto.getStatus());
+
+ DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
+ removePrefixesFromDocument(documentBuilder);
+ return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build());
} finally {
mReadWriteLock.readLock().unlock();
}
- checkSuccess(getResultProto.getStatus());
-
- DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
- removePrefixesFromDocument(documentBuilder);
- return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build());
}
/**
@@ -507,17 +549,19 @@
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec)
throws AppSearchException {
- List<String> filterPackageNames = searchSpec.getFilterPackageNames();
- if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
- // Client wanted to query over some packages that weren't its own. This isn't
- // allowed through local query so we can return early with no results.
- return new SearchResultPage(Bundle.EMPTY);
- }
-
mReadWriteLock.readLock().lock();
try {
+ throwIfClosedLocked();
+
+ List<String> filterPackageNames = searchSpec.getFilterPackageNames();
+ if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
+ // Client wanted to query over some packages that weren't its own. This isn't
+ // allowed through local query so we can return early with no results.
+ return new SearchResultPage(Bundle.EMPTY);
+ }
+
String prefix = createPrefix(packageName, databaseName);
- Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemas(prefix, searchSpec);
+ Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemasLocked(prefix, searchSpec);
return doQueryLocked(
Collections.singleton(createPrefix(packageName, databaseName)),
@@ -552,6 +596,8 @@
throws AppSearchException {
mReadWriteLock.readLock().lock();
try {
+ throwIfClosedLocked();
+
Set<String> packageFilters = new ArraySet<>(searchSpec.getFilterPackageNames());
Set<String> prefixFilters = new ArraySet<>();
Set<String> allPrefixes = mNamespaceMapLocked.keySet();
@@ -654,6 +700,8 @@
public SearchResultPage getNextPage(long nextPageToken) throws AppSearchException {
mReadWriteLock.readLock().lock();
try {
+ throwIfClosedLocked();
+
SearchResultProto searchResultProto =
mIcingSearchEngineLocked.getNextPage(nextPageToken);
checkSuccess(searchResultProto.getStatus());
@@ -674,6 +722,8 @@
public void invalidateNextPageToken(long nextPageToken) {
mReadWriteLock.readLock().lock();
try {
+ throwIfClosedLocked();
+
mIcingSearchEngineLocked.invalidateNextPageToken(nextPageToken);
} finally {
mReadWriteLock.readLock().unlock();
@@ -688,16 +738,19 @@
@NonNull String uri,
long usageTimestampMillis)
throws AppSearchException {
- String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
- UsageReport report =
- UsageReport.newBuilder()
- .setDocumentNamespace(prefixedNamespace)
- .setDocumentUri(uri)
- .setUsageTimestampMs(usageTimestampMillis)
- .setUsageType(UsageReport.UsageType.USAGE_TYPE1)
- .build();
mReadWriteLock.writeLock().lock();
try {
+ throwIfClosedLocked();
+
+ String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
+ UsageReport report =
+ UsageReport.newBuilder()
+ .setDocumentNamespace(prefixedNamespace)
+ .setDocumentUri(uri)
+ .setUsageTimestampMs(usageTimestampMillis)
+ .setUsageType(UsageReport.UsageType.USAGE_TYPE1)
+ .build();
+
ReportUsageResultProto result = mIcingSearchEngineLocked.reportUsage(report);
checkSuccess(result.getStatus());
} finally {
@@ -722,16 +775,18 @@
@NonNull String namespace,
@NonNull String uri)
throws AppSearchException {
- String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
- DeleteResultProto deleteResultProto;
mReadWriteLock.writeLock().lock();
try {
- deleteResultProto = mIcingSearchEngineLocked.delete(prefixedNamespace, uri);
- checkForOptimizeLocked(/* force= */ false);
+ throwIfClosedLocked();
+
+ String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
+ DeleteResultProto deleteResultProto =
+ mIcingSearchEngineLocked.delete(prefixedNamespace, uri);
+
+ checkSuccess(deleteResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
}
- checkSuccess(deleteResultProto.getStatus());
}
/**
@@ -751,22 +806,25 @@
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec)
throws AppSearchException {
- List<String> filterPackageNames = searchSpec.getFilterPackageNames();
- if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
- // We're only removing documents within the parameter `packageName`. If we're not
- // restricting our remove-query to this package name, then there's nothing for us to
- // remove.
- return;
- }
-
- SearchSpecProto searchSpecProto = SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
- SearchSpecProto.Builder searchSpecBuilder =
- searchSpecProto.toBuilder().setQuery(queryExpression);
- DeleteByQueryResultProto deleteResultProto;
mReadWriteLock.writeLock().lock();
try {
+ throwIfClosedLocked();
+
+ List<String> filterPackageNames = searchSpec.getFilterPackageNames();
+ if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
+ // We're only removing documents within the parameter `packageName`. If we're not
+ // restricting our remove-query to this package name, then there's nothing for us to
+ // remove.
+ return;
+ }
+
+ SearchSpecProto searchSpecProto =
+ SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
+ SearchSpecProto.Builder searchSpecBuilder =
+ searchSpecProto.toBuilder().setQuery(queryExpression);
+
String prefix = createPrefix(packageName, databaseName);
- Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemas(prefix, searchSpec);
+ Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemasLocked(prefix, searchSpec);
// rewriteSearchSpecForPrefixesLocked will return false if there is nothing to search
// over given their search filters, so we can return early and skip sending request
@@ -775,15 +833,16 @@
searchSpecBuilder, Collections.singleton(prefix), allowedPrefixedSchemas)) {
return;
}
- deleteResultProto = mIcingSearchEngineLocked.deleteByQuery(searchSpecBuilder.build());
- checkForOptimizeLocked(/* force= */ true);
+ DeleteByQueryResultProto deleteResultProto =
+ mIcingSearchEngineLocked.deleteByQuery(searchSpecBuilder.build());
+
+ // 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);
} 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);
}
/**
@@ -799,9 +858,16 @@
* @throws AppSearchException on any error that AppSearch persist data to disk.
*/
public void persistToDisk() throws AppSearchException {
- PersistToDiskResultProto persistToDiskResultProto =
- mIcingSearchEngineLocked.persistToDisk();
- checkSuccess(persistToDiskResultProto.getStatus());
+ mReadWriteLock.writeLock().lock();
+ try {
+ throwIfClosedLocked();
+
+ PersistToDiskResultProto persistToDiskResultProto =
+ mIcingSearchEngineLocked.persistToDisk();
+ checkSuccess(persistToDiskResultProto.getStatus());
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
}
/**
@@ -814,21 +880,16 @@
*
* @throws AppSearchException on IcingSearchEngine error.
*/
- private void reset() throws AppSearchException {
- ResetResultProto resetResultProto;
- mReadWriteLock.writeLock().lock();
- try {
- resetResultProto = mIcingSearchEngineLocked.reset();
- mOptimizeIntervalCountLocked = 0;
- mSchemaMapLocked.clear();
- mNamespaceMapLocked.clear();
+ @GuardedBy("mReadWriteLock")
+ private void resetLocked() throws AppSearchException {
+ ResetResultProto 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();
- }
+ // Must be called after everything else since VisibilityStore may repopulate
+ // IcingSearchEngine with an initial schema.
+ mVisibilityStoreLocked.handleReset();
checkSuccess(resetResultProto.getStatus());
}
@@ -1079,7 +1140,8 @@
* <p>This only checks intersection of schema filters on the search spec with those that the
* prefix owns itself. This does not check global query permissions.
*/
- private Set<String> getAllowedPrefixSchemas(
+ @GuardedBy("mReadWriteLock")
+ private Set<String> getAllowedPrefixSchemasLocked(
@NonNull String prefix, @NonNull SearchSpec searchSpec) {
Set<String> allowedPrefixedSchemas = new ArraySet<>();
@@ -1295,35 +1357,72 @@
/**
* Checks whether {@link IcingSearchEngine#optimize()} should be called to release resources.
*
- * <p>This method should be only called in mutate methods and get the WRITE lock to keep thread
- * safety.
+ * <p>This method should be only called after a mutation to local storage backend which deletes
+ * a mass of data and could release lots resources after {@link IcingSearchEngine#optimize()}.
+ *
+ * <p>This method will trigger {@link IcingSearchEngine#getOptimizeInfo()} to check resources
+ * that could be released for every {@link #CHECK_OPTIMIZE_INTERVAL} mutations.
*
* <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()}.
+ * @param mutationSize The number of how many mutations have been executed for current request.
+ * An inside counter will accumulates it. Once the counter reaches {@link
+ * #CHECK_OPTIMIZE_INTERVAL}, {@link IcingSearchEngine#getOptimizeInfo()} will be triggered
+ * and the counter will be reset.
*/
- @GuardedBy("mReadWriteLock")
- private void checkForOptimizeLocked(boolean force) throws AppSearchException {
- ++mOptimizeIntervalCountLocked;
- if (force || mOptimizeIntervalCountLocked >= CHECK_OPTIMIZE_INTERVAL) {
- mOptimizeIntervalCountLocked = 0;
+ public void checkForOptimize(int mutationSize) throws AppSearchException {
+ mReadWriteLock.writeLock().lock();
+ try {
+ mOptimizeIntervalCountLocked += mutationSize;
+ if (mOptimizeIntervalCountLocked >= CHECK_OPTIMIZE_INTERVAL) {
+ checkForOptimize();
+ }
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Checks whether {@link IcingSearchEngine#optimize()} should be called to release resources.
+ *
+ * <p>This method will directly trigger {@link IcingSearchEngine#getOptimizeInfo()} to check
+ * resources that could be released.
+ *
+ * <p>{@link IcingSearchEngine#optimize()} should be called only if {@link
+ * GetOptimizeInfoResultProto} shows there is enough resources could be released.
+ */
+ public void checkForOptimize() throws AppSearchException {
+ mReadWriteLock.writeLock().lock();
+ try {
GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResultLocked();
checkSuccess(optimizeInfo.getStatus());
+ mOptimizeIntervalCountLocked = 0;
// Second threshold, decide when to call optimize().
if (optimizeInfo.getOptimizableDocs() >= OPTIMIZE_THRESHOLD_DOC_COUNT
|| 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 = mIcingSearchEngineLocked.optimize();
- checkSuccess(optimizeResultProto.getStatus());
+ optimize();
}
- // TODO(b/147699081): Return OptimizeResultProto & log lost data detail once we add
- // a field to indicate lost_schema and lost_documents in OptimizeResultProto.
- // go/icing-library-apis.
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ // TODO(b/147699081): Return OptimizeResultProto & log lost data detail once we add
+ // a field to indicate lost_schema and lost_documents in OptimizeResultProto.
+ // go/icing-library-apis.
+ }
+
+ /**
+ * Triggers {@link IcingSearchEngine#optimize()} directly.
+ *
+ * <p>This method should be only called as a scheduled task in AppSearch Platform backend.
+ */
+ public void optimize() throws AppSearchException {
+ mReadWriteLock.writeLock().lock();
+ try {
+ OptimizeResultProto optimizeResultProto = mIcingSearchEngineLocked.optimize();
+ checkSuccess(optimizeResultProto.getStatus());
+ } finally {
+ mReadWriteLock.writeLock().unlock();
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
index b3f8264..a501e99 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
@@ -76,7 +76,7 @@
int currentVersion = mCurrentVersionMap.get(schemaType);
int finalVersion = mFinalVersionMap.get(schemaType);
try (FileOutputStream outputStream = new FileOutputStream(mFile)) {
- // TODO(b/151178558) change the output stream so that we can use it in platform
+ // TODO(b/177266929) change the output stream so that we can use it in platform
CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputStream);
SearchResultPage searchResultPage =
mAppSearchImpl.query(
@@ -126,11 +126,13 @@
try {
mAppSearchImpl.putDocument(mPackageName, mDatabaseName, document);
} catch (Throwable t) {
- responseBuilder.setFailure(
- document.getSchemaType(),
- document.getNamespace(),
- document.getUri(),
- throwableToFailedResult(t));
+ responseBuilder.addMigrationFailure(
+ new SetSchemaResponse.MigrationFailure.Builder()
+ .setNamespace(document.getNamespace())
+ .setSchemaType(document.getSchemaType())
+ .setUri(document.getUri())
+ .setAppSearchResult(throwableToFailedResult(t))
+ .build());
}
}
mAppSearchImpl.persistToDisk();
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
similarity index 70%
rename from apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java
rename to apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
index e1e7d46..a0f39ec 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
@@ -17,45 +17,41 @@
package com.android.server.appsearch.external.localstorage.converter;
import android.annotation.NonNull;
-import android.app.appsearch.SetSchemaResult;
+import android.app.appsearch.SetSchemaResponse;
import com.android.internal.util.Preconditions;
import com.google.android.icing.proto.SetSchemaResultProto;
/**
- * Translates a {@link SetSchemaResultProto} into {@link SetSchemaResult}.
+ * Translates a {@link SetSchemaResultProto} into {@link SetSchemaResponse}.
*
* @hide
*/
-public class SetSchemaResultToProtoConverter {
+public class SetSchemaResponseToProtoConverter {
- private SetSchemaResultToProtoConverter() {}
+ private SetSchemaResponseToProtoConverter() {}
/**
- * Translate a {@link SetSchemaResultProto} into {@link SetSchemaResult}.
+ * Translate a {@link SetSchemaResultProto} into {@link SetSchemaResponse}.
*
* @param proto The {@link SetSchemaResultProto} containing results.
* @param prefix The prefix need to removed from schemaTypes
- * @return {@link SetSchemaResult} of results.
+ * @return The {@link SetSchemaResponse} object.
*/
@NonNull
- public static SetSchemaResult toSetSchemaResult(
+ public static SetSchemaResponse toSetSchemaResponse(
@NonNull SetSchemaResultProto proto, @NonNull String prefix) {
Preconditions.checkNotNull(proto);
Preconditions.checkNotNull(prefix);
- SetSchemaResult.Builder builder =
- new SetSchemaResult.Builder()
- .setResultCode(
- ResultCodeToProtoConverter.toResultCode(
- proto.getStatus().getCode()));
+ SetSchemaResponse.Builder builder = new SetSchemaResponse.Builder();
for (int i = 0; i < proto.getDeletedSchemaTypesCount(); i++) {
- builder.addDeletedSchemaType(proto.getDeletedSchemaTypes(i).substring(prefix.length()));
+ builder.addDeletedType(proto.getDeletedSchemaTypes(i).substring(prefix.length()));
}
for (int i = 0; i < proto.getIncompatibleSchemaTypesCount(); i++) {
- builder.addIncompatibleSchemaType(
+ builder.addIncompatibleType(
proto.getIncompatibleSchemaTypes(i).substring(prefix.length()));
}
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 6ba5572..d076db3 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I2bf8bd9db1b71b7da4ab50dd7480e4529678413a
+Ia9a8daef1a6d7d9432f7808d440abd64f4797701
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
index b2ffd5b..ea21e19 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
@@ -77,10 +77,14 @@
* android.app.appsearch.exceptions.AppSearchException} with the {@link
* AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
* compatible with the new schema will be deleted and the incompatible schema will be
- * applied.
+ * applied. Incompatible types and deleted types will be set into {@link
+ * SetSchemaResponse#getIncompatibleTypes()} and {@link
+ * SetSchemaResponse#getDeletedTypes()}, respectively.
* <li>Add a {@link android.app.appsearch.AppSearchSchema.Migrator} for each incompatible type
* and make no deletion. The migrator will migrate documents from it's old schema version
- * to the new version. See the migration section below.
+ * to the new version. Migrated types will be set into both {@link
+ * SetSchemaResponse#getIncompatibleTypes()} and {@link
+ * SetSchemaResponse#getMigratedTypes()}. See the migration section below.
* </ul>
*
* <p>It is a no-op to set the same schema as has been previously set; this is handled
@@ -109,10 +113,8 @@
* backwards-compatible, and stored documents won't have any observable changes.
*
* @param request The schema update request.
- * @return The pending {@link SetSchemaResponse} of performing this operation. Success if the
- * the schema has been set and any migrations has been done. Otherwise, the failure {@link
- * android.app.appsearch.SetSchemaResponse.MigrationFailure} indicates which document is
- * fail to be migrated.
+ * @return A {@link ListenableFuture} with exception if we hit any error. Or the pending {@link
+ * SetSchemaResponse} of performing this operation, if the schema has been successfully set.
* @see android.app.appsearch.AppSearchSchema.Migrator
* @see android.app.appsearch.AppSearchMigrationHelper.Transformer
*/
@@ -144,57 +146,79 @@
ListenableFuture<AppSearchBatchResult<String, Void>> put(@NonNull PutDocumentsRequest request);
/**
- * Retrieves {@link GenericDocument}s by URI.
+ * Gets {@link GenericDocument} objects by URIs and namespace from the {@link
+ * AppSearchSessionShim} database.
*
- * @param request {@link GetByUriRequest} containing URIs to be retrieved.
- * @return The pending result of performing this operation. The keys of the returned {@link
- * AppSearchBatchResult} are the input URIs. The values are the returned {@link
- * GenericDocument}s on success, or a failed {@link AppSearchResult} otherwise. URIs that
- * are not found will return a failed {@link AppSearchResult} with a result code of {@link
- * AppSearchResult#RESULT_NOT_FOUND}.
+ * @param request a request containing URIs and namespace to get documents for.
+ * @return A {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}. The
+ * keys of the {@link AppSearchBatchResult} represent the input URIs from the {@link
+ * GetByUriRequest} object. The values are either the corresponding {@link GenericDocument}
+ * object for the URI on success, or an {@link AppSearchResult} object on failure. For
+ * example, if a URI is not found, the value for that URI will be set to an {@link
+ * AppSearchResult} object with result code: {@link AppSearchResult#RESULT_NOT_FOUND}.
*/
@NonNull
ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByUri(
@NonNull GetByUriRequest request);
/**
- * Searches for documents based on a given query string.
+ * Retrieves documents from the open {@link AppSearchSessionShim} that match a given query
+ * string and type of search provided.
*
- * <p>Currently we support following features in the raw query format:
+ * <p>Query strings can be empty, contain one term with no operators, or contain multiple terms
+ * and operators.
+ *
+ * <p>For query strings that are empty, all documents that match the {@link SearchSpec} will be
+ * returned.
+ *
+ * <p>For query strings with a single term and no operators, documents that match the provided
+ * query string and {@link SearchSpec} will be returned.
+ *
+ * <p>The following operators are supported:
*
* <ul>
- * <li>AND
- * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and ‘cat’”).
- * Example: hello world matches documents that have both ‘hello’ and ‘world’
+ * <li>AND (implicit)
+ * <p>AND is an operator that matches documents that contain <i>all</i> provided terms.
+ * <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly
+ * including "AND" in a query string will treat "AND" as a term, returning documents that
+ * also contain "AND".
+ * <p>Example: "apple AND banana" matches documents that contain the terms "apple", "and",
+ * "banana".
+ * <p>Example: "apple banana" matches documents that contain both "apple" and "banana".
+ * <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and
+ * "cherry".
* <li>OR
- * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example:
- * dog OR puppy
- * <li>Exclusion
- * <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example:
- * -dog excludes the term ‘dog’
- * <li>Grouping terms
- * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
- * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
- * Example: (dog puppy) (cat kitten) two one group containing two terms.
- * <li>Property restricts
- * <p>Specifies which properties of a document to specifically match terms in (e.g. “match
- * documents where the ‘subject’ property contains ‘important’”). Example:
- * subject:important matches documents with the term ‘important’ in the ‘subject’ property
- * <li>Schema type restricts
- * <p>This is similar to property restricts, but allows for restricts on top-level
- * document fields, such as schema_type. Clients should be able to limit their query to
- * documents of a certain schema_type (e.g. “match documents that are of the ‘Email’
- * schema_type”). Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will
- * match documents that contain the query term ‘dog’ and are of either the ‘Email’ schema
- * type or the ‘Video’ schema type.
+ * <p>OR is an operator that matches documents that contain <i>any</i> provided term.
+ * <p>Example: "apple OR banana" matches documents that contain either "apple" or
+ * "banana".
+ * <p>Example: "apple OR banana OR cherry" matches documents that contain any of "apple",
+ * "banana", or "cherry".
+ * <li>Exclusion (-)
+ * <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the
+ * provided term.
+ * <p>Example: "-apple" matches documents that do not contain "apple".
+ * <li>Grouped Terms
+ * <p>For queries that require multiple operators and terms, terms can be grouped into
+ * subqueries. Subqueries are contained within an open "(" and close ")" parenthesis.
+ * <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain either
+ * "donut" or "bagel" and either "coffee" or "tea".
+ * <li>Property Restricts
+ * <p>For queries that require a term to match a specific {@link AppSearchSchema} property
+ * of a document, a ":" must be included between the property name and the term.
+ * <p>Example: "subject:important" matches documents that contain the term "important" in
+ * the "subject" property.
* </ul>
*
+ * <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or
+ * adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter.
+ *
* <p>This method is lightweight. The heavy work will be done in {@link
* SearchResultsShim#getNextPage()}.
*
- * @param queryExpression Query String to search.
- * @param searchSpec Spec for setting filters, raw query etc.
- * @return The search result of performing this operation.
+ * @param queryExpression query string to search.
+ * @param searchSpec spec for setting document filters, adding projection, setting term match
+ * type, etc.
+ * @return a {@link SearchResultsShim} object for retrieved matched documents.
*/
@NonNull
SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
@@ -219,13 +243,22 @@
ListenableFuture<Void> reportUsage(@NonNull ReportUsageRequest request);
/**
- * Removes {@link GenericDocument}s from the index by URI.
+ * Removes {@link GenericDocument} objects by URIs and namespace from the {@link
+ * AppSearchSessionShim} database.
*
- * @param request Request containing URIs to be removed.
- * @return The pending result of performing this operation. The keys of the returned {@link
- * AppSearchBatchResult} are the input URIs. The values are {@code null} on success, or a
- * failed {@link AppSearchResult} otherwise. URIs that are not found will return a failed
- * {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
+ * <p>Removed documents will no longer be surfaced by {@link #search} or {@link #getByUri}
+ * calls.
+ *
+ * <p><b>NOTE:</b>By default, documents are removed via a soft delete operation. Once the
+ * document crosses the count threshold or byte usage threshold, the documents will be removed
+ * from disk.
+ *
+ * @param request {@link RemoveByUriRequest} with URIs and namespace to remove from the index.
+ * @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}. The
+ * keys of the {@link AppSearchBatchResult} represent the input URIs from the {@link
+ * RemoveByUriRequest} object. The values are either {@code null} on success, or a failed
+ * {@link AppSearchResult} otherwise. URIs that are not found will return a failed {@link
+ * AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
*/
@NonNull
ListenableFuture<AppSearchBatchResult<String, Void>> remove(
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
index 44d5180..37717d6 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
@@ -25,7 +25,6 @@
import android.app.appsearch.GetByUriRequest;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultsShim;
-import android.app.appsearch.SetSchemaResponse;
import java.util.ArrayList;
import java.util.List;
@@ -43,15 +42,6 @@
return result;
}
- // TODO(b/151178558) check setSchemaResponse.xxxtypes for the test need to verify.
- public static void checkIsSetSchemaResponseSuccess(Future<SetSchemaResponse> future)
- throws Exception {
- SetSchemaResponse setSchemaResponse = future.get();
- assertWithMessage("SetSchemaResponse not successful.")
- .that(setSchemaResponse.isSuccess())
- .isTrue();
- }
-
public static List<GenericDocument> doGet(
AppSearchSessionShim session, String namespace, String... uris) throws Exception {
AppSearchBatchResult<String, GenericDocument> result =
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
index b96f99e..31c934f 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
@@ -27,43 +27,26 @@
*/
public interface GlobalSearchSessionShim extends Closeable {
/**
- * Searches across all documents in the storage based on a given query string.
+ * Retrieves documents from all AppSearch databases that the querying application has access to.
*
- * <p>Currently we support following features in the raw query format:
+ * <p>Applications can be granted access to documents by specifying {@link
+ * SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage}, or {@link
+ * SetSchemaRequest.Builder#setDocumentClassVisibilityForPackage} when building a schema.
*
- * <ul>
- * <li>AND
- * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and ‘cat’”).
- * Example: hello world matches documents that have both ‘hello’ and ‘world’
- * <li>OR
- * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example:
- * dog OR puppy
- * <li>Exclusion
- * <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example:
- * -dog excludes the term ‘dog’
- * <li>Grouping terms
- * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
- * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
- * Example: (dog puppy) (cat kitten) two one group containing two terms.
- * <li>Property restricts
- * <p>Specifies which properties of a document to specifically match terms in (e.g. “match
- * documents where the ‘subject’ property contains ‘important’”). Example:
- * subject:important matches documents with the term ‘important’ in the ‘subject’ property
- * <li>Schema type restricts
- * <p>This is similar to property restricts, but allows for restricts on top-level
- * document fields, such as schema_type. Clients should be able to limit their query to
- * documents of a certain schema_type (e.g. “match documents that are of the ‘Email’
- * schema_type”). Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will
- * match documents that contain the query term ‘dog’ and are of either the ‘Email’ schema
- * type or the ‘Video’ schema type.
- * </ul>
+ * <p>Document access can also be granted to system UIs by specifying {@link
+ * SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi}, or {@link
+ * SetSchemaRequest.Builder#setDocumentClassVisibilityForSystemUi} when building a schema.
+ *
+ * <p>See {@link AppSearchSessionShim#search(String, SearchSpec)} for a detailed explanation on
+ * forming a query string.
*
* <p>This method is lightweight. The heavy work will be done in {@link
* SearchResultsShim#getNextPage()}.
*
- * @param queryExpression Query String to search.
- * @param searchSpec Spec for setting filters, raw query etc.
- * @return The search result of performing this operation.
+ * @param queryExpression query string to search.
+ * @param searchSpec spec for setting document filters, adding projection, setting term match
+ * type, etc.
+ * @return a {@link SearchResultsShim} object for retrieved matched documents.
*/
@NonNull
SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
diff --git a/apex/media/Android.bp b/apex/media/Android.bp
index 5f1bd37..d6666f5 100644
--- a/apex/media/Android.bp
+++ b/apex/media/Android.bp
@@ -18,3 +18,10 @@
"//frameworks/av/apex/testing",
],
}
+
+sdk {
+ name: "media-module-sdk",
+ java_sdk_libs: [
+ "framework-media",
+ ],
+}
diff --git a/api/Android.bp b/api/Android.bp
index d5c6bf6..5466bd2 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -37,6 +37,7 @@
":framework-mediaprovider{.public.api.txt}",
":framework-permission{.public.api.txt}",
":framework-permission-s{.public.api.txt}",
+ ":framework-scheduling{.public.api.txt}",
":framework-sdkextensions{.public.api.txt}",
":framework-statsd{.public.api.txt}",
":framework-tethering{.public.api.txt}",
@@ -63,6 +64,22 @@
}
genrule {
+ name: "frameworks-base-api-current-compat",
+ srcs: [
+ ":android.api.public.latest",
+ ":android-incompatibilities.api.public.latest",
+ ":frameworks-base-api-current.txt",
+ ],
+ out: ["stdout.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 " +
+ "--check-compatibility:api:released $(location :android.api.public.latest) " +
+ "--baseline:compatibility:released $(location :android-incompatibilities.api.public.latest) " +
+ "$(location :frameworks-base-api-current.txt) " +
+ "> $(genDir)/stdout.txt",
+}
+
+genrule {
name: "frameworks-base-api-current.srcjar",
srcs: [
":android.net.ipsec.ike{.public.stubs.source}",
@@ -75,6 +92,7 @@
":framework-mediaprovider{.public.stubs.source}",
":framework-permission{.public.stubs.source}",
":framework-permission-s{.public.stubs.source}",
+ ":framework-scheduling{.public.stubs.source}",
":framework-sdkextensions{.public.stubs.source}",
":framework-statsd{.public.stubs.source}",
":framework-tethering{.public.stubs.source}",
@@ -99,6 +117,7 @@
":framework-mediaprovider{.public.removed-api.txt}",
":framework-permission{.public.removed-api.txt}",
":framework-permission-s{.public.removed-api.txt}",
+ ":framework-scheduling{.public.removed-api.txt}",
":framework-sdkextensions{.public.removed-api.txt}",
":framework-statsd{.public.removed-api.txt}",
":framework-tethering{.public.removed-api.txt}",
@@ -133,6 +152,7 @@
":framework-mediaprovider{.system.api.txt}",
":framework-permission{.system.api.txt}",
":framework-permission-s{.system.api.txt}",
+ ":framework-scheduling{.system.api.txt}",
":framework-sdkextensions{.system.api.txt}",
":framework-statsd{.system.api.txt}",
":framework-tethering{.system.api.txt}",
@@ -158,6 +178,24 @@
}
genrule {
+ name: "frameworks-base-api-system-current-compat",
+ srcs: [
+ ":android.api.system.latest",
+ ":android-incompatibilities.api.system.latest",
+ ":frameworks-base-api-current.txt",
+ ":frameworks-base-api-system-current.txt",
+ ],
+ out: ["stdout.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 " +
+ "--check-compatibility:api:released $(location :android.api.system.latest) " +
+ "--check-compatibility:base $(location :frameworks-base-api-current.txt) " +
+ "--baseline:compatibility:released $(location :android-incompatibilities.api.system.latest) " +
+ "$(location :frameworks-base-api-system-current.txt) " +
+ "> $(genDir)/stdout.txt",
+}
+
+genrule {
name: "frameworks-base-api-system-removed.txt",
srcs: [
":android.net.ipsec.ike{.system.removed-api.txt}",
@@ -167,6 +205,7 @@
":framework-mediaprovider{.system.removed-api.txt}",
":framework-permission{.system.removed-api.txt}",
":framework-permission-s{.system.removed-api.txt}",
+ ":framework-scheduling{.system.removed-api.txt}",
":framework-sdkextensions{.system.removed-api.txt}",
":framework-statsd{.system.removed-api.txt}",
":framework-tethering{.system.removed-api.txt}",
@@ -201,6 +240,7 @@
":framework-mediaprovider{.module-lib.api.txt}",
":framework-permission{.module-lib.api.txt}",
":framework-permission-s{.module-lib.api.txt}",
+ ":framework-scheduling{.module-lib.api.txt}",
":framework-sdkextensions{.module-lib.api.txt}",
":framework-statsd{.module-lib.api.txt}",
":framework-tethering{.module-lib.api.txt}",
@@ -225,6 +265,27 @@
}
genrule {
+ name: "frameworks-base-api-module-lib-current-compat",
+ srcs: [
+ ":android.api.module-lib.latest",
+ ":android-incompatibilities.api.module-lib.latest",
+ ":frameworks-base-api-current.txt",
+ ":frameworks-base-api-module-lib-current.txt",
+ ],
+ out: ["stdout.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 " +
+ "--check-compatibility:api:released $(location :android.api.module-lib.latest) " +
+ // Note: having "public" be the base of module-lib is not perfect -- it should
+ // ideally be a merged public+system), but this will help when migrating from
+ // MODULE_LIBS -> public.
+ "--check-compatibility:base $(location :frameworks-base-api-current.txt) " +
+ "--baseline:compatibility:released $(location :android-incompatibilities.api.module-lib.latest) " +
+ "$(location :frameworks-base-api-module-lib-current.txt) " +
+ "> $(genDir)/stdout.txt",
+}
+
+genrule {
name: "frameworks-base-api-module-lib-removed.txt",
srcs: [
":android.net.ipsec.ike{.module-lib.removed-api.txt}",
@@ -234,6 +295,7 @@
":framework-mediaprovider{.module-lib.removed-api.txt}",
":framework-permission{.module-lib.removed-api.txt}",
":framework-permission-s{.module-lib.removed-api.txt}",
+ ":framework-scheduling{.module-lib.removed-api.txt}",
":framework-sdkextensions{.module-lib.removed-api.txt}",
":framework-statsd{.module-lib.removed-api.txt}",
":framework-tethering{.module-lib.removed-api.txt}",
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 227b9e2..115c1f2 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -55,7 +55,11 @@
+ " svc usb getGadgetHalVersion\n"
+ " Gets current Gadget Hal Version\n"
+ " possible values of Hal version are any of 'unknown', 'V1_0', 'V1_1',\n"
- + " 'V1_2'\n";
+ + " 'V1_2'\n"
+ + " svc usb getUsbHalVersion\n"
+ + " Gets current USB Hal Version\n"
+ + " possible values of Hal version are any of 'unknown', 'V1_0', 'V1_1',\n"
+ + " 'V1_2', 'V1_3'\n";
}
@Override
@@ -111,6 +115,25 @@
System.err.println("Error communicating with UsbManager: " + e);
}
return;
+ } else if ("getUsbHalVersion".equals(args[1])) {
+ try {
+ int version = usbMgr.getUsbHalVersion();
+
+ if (version == 13) {
+ System.err.println("V1_3");
+ } else if (version == 12) {
+ System.err.println("V1_2");
+ } else if (version == 11) {
+ System.err.println("V1_1");
+ } else if (version == 10) {
+ System.err.println("V1_0");
+ } else {
+ System.err.println("unknown");
+ }
+ } catch (RemoteException e) {
+ System.err.println("Error communicating with UsbManager: " + e);
+ }
+ return;
}
}
System.err.println(longHelp());
diff --git a/core/api/current.txt b/core/api/current.txt
index 9ef3eb4..c7710fc 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -88,6 +88,7 @@
field @Deprecated public static final String GET_TASKS = "android.permission.GET_TASKS";
field public static final String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH";
field public static final String HIDE_OVERLAY_WINDOWS = "android.permission.HIDE_OVERLAY_WINDOWS";
+ field public static final String HIGH_SAMPLING_RATE_SENSORS = "android.permission.HIGH_SAMPLING_RATE_SENSORS";
field public static final String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER";
field public static final String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES";
field public static final String INSTALL_SHORTCUT = "com.android.launcher.permission.INSTALL_SHORTCUT";
@@ -959,6 +960,8 @@
field public static final int maxLines = 16843091; // 0x1010153
field public static final int maxLongVersionCode = 16844163; // 0x1010583
field public static final int maxRecents = 16843846; // 0x1010446
+ field public static final int maxResizeHeight = 16844339; // 0x1010633
+ field public static final int maxResizeWidth = 16844338; // 0x1010632
field public static final int maxRows = 16843059; // 0x1010133
field public static final int maxSdkVersion = 16843377; // 0x1010271
field public static final int maxWidth = 16843039; // 0x101011f
@@ -1185,6 +1188,7 @@
field public static final int right = 16843183; // 0x10101af
field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093
field public static final int ringtoneType = 16843257; // 0x10101f9
+ field public static final int rippleStyle = 16844337; // 0x1010631
field public static final int rollbackDataPolicy = 16844311; // 0x1010617
field public static final int rotation = 16843558; // 0x1010326
field public static final int rotationAnimation = 16844090; // 0x101053a
@@ -1392,6 +1396,8 @@
field public static final int tabWidgetStyle = 16842883; // 0x1010083
field public static final int tag = 16842961; // 0x10100d1
field public static final int targetActivity = 16843266; // 0x1010202
+ field public static final int targetCellHeight = 16844341; // 0x1010635
+ field public static final int targetCellWidth = 16844340; // 0x1010634
field public static final int targetClass = 16842799; // 0x101002f
field @Deprecated public static final int targetDescriptions = 16843680; // 0x10103a0
field public static final int targetId = 16843740; // 0x10103dc
@@ -1746,6 +1752,9 @@
field public static final int dialog_min_width_minor = 17104900; // 0x1050004
field public static final int notification_large_icon_height = 17104902; // 0x1050006
field public static final int notification_large_icon_width = 17104901; // 0x1050005
+ field public static final int system_app_widget_background_radius = 17104904; // 0x1050008
+ field public static final int system_app_widget_inner_radius = 17104905; // 0x1050009
+ field public static final int system_app_widget_internal_padding = 17104906; // 0x105000a
field public static final int thumbnail_height = 17104897; // 0x1050001
field public static final int thumbnail_width = 17104898; // 0x1050002
}
@@ -6069,6 +6078,13 @@
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
+ field public static final String EDIT_CONVERSATION = "convo";
+ field public static final String EDIT_IMPORTANCE = "importance";
+ field public static final String EDIT_LAUNCHER = "launcher";
+ field public static final String EDIT_LOCKED_DEVICE = "locked";
+ field public static final String EDIT_SOUND = "sound";
+ field public static final String EDIT_VIBRATION = "vibration";
+ field public static final String EDIT_ZEN = "dnd";
}
public final class NotificationChannelGroup implements android.os.Parcelable {
@@ -6958,6 +6974,7 @@
method public void addUserRestriction(@NonNull android.content.ComponentName, String);
method public boolean bindDeviceAdminServiceAsUser(@NonNull android.content.ComponentName, android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
method public boolean canAdminGrantSensorsPermissions();
+ method public boolean canUsbDataSignalingBeDisabled();
method public void clearApplicationUserData(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener);
method public void clearCrossProfileIntentFilters(@NonNull android.content.ComponentName);
method @Deprecated public void clearDeviceOwnerApp(String);
@@ -7085,6 +7102,7 @@
method public boolean isSecurityLoggingEnabled(@Nullable android.content.ComponentName);
method public boolean isUninstallBlocked(@Nullable android.content.ComponentName, String);
method public boolean isUniqueDeviceAttestationSupported();
+ method public boolean isUsbDataSignalingEnabled();
method public boolean isUsingUnifiedPassword(@NonNull android.content.ComponentName);
method public void lockNow();
method public void lockNow(int);
@@ -7186,6 +7204,7 @@
method public boolean setTimeZone(@NonNull android.content.ComponentName, String);
method public void setTrustAgentConfiguration(@NonNull android.content.ComponentName, @NonNull android.content.ComponentName, android.os.PersistableBundle);
method public void setUninstallBlocked(@Nullable android.content.ComponentName, String, boolean);
+ method public void setUsbDataSignalingEnabled(boolean);
method public void setUserControlDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
method public void setUserIcon(@NonNull android.content.ComponentName, android.graphics.Bitmap);
method public int startUserInBackground(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
@@ -8302,6 +8321,7 @@
public class AppWidgetHostView extends android.widget.FrameLayout {
ctor public AppWidgetHostView(android.content.Context);
ctor public AppWidgetHostView(android.content.Context, int, int);
+ method public void clearCurrentSize();
method public int getAppWidgetId();
method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo();
method public static android.graphics.Rect getDefaultPaddingForWidget(android.content.Context, android.content.ComponentName, android.graphics.Rect);
@@ -8309,11 +8329,13 @@
method protected android.view.View getErrorView();
method protected void prepareView(android.view.View);
method public void setAppWidget(int, android.appwidget.AppWidgetProviderInfo);
+ method public void setCurrentSize(@NonNull android.graphics.PointF);
method public void setExecutor(java.util.concurrent.Executor);
method public void setOnLightBackground(boolean);
method public void updateAppWidget(android.widget.RemoteViews);
method public void updateAppWidgetOptions(android.os.Bundle);
- method public void updateAppWidgetSize(android.os.Bundle, int, int, int, int);
+ method @Deprecated public void updateAppWidgetSize(android.os.Bundle, int, int, int, int);
+ method public void updateAppWidgetSize(@NonNull android.os.Bundle, @NonNull java.util.List<android.graphics.PointF>);
}
public class AppWidgetManager {
@@ -8366,6 +8388,7 @@
field public static final String OPTION_APPWIDGET_MIN_HEIGHT = "appWidgetMinHeight";
field public static final String OPTION_APPWIDGET_MIN_WIDTH = "appWidgetMinWidth";
field public static final String OPTION_APPWIDGET_RESTORE_COMPLETED = "appWidgetRestoreCompleted";
+ field public static final String OPTION_APPWIDGET_SIZES = "appWidgetSizes";
}
public class AppWidgetProvider extends android.content.BroadcastReceiver {
@@ -8409,6 +8432,8 @@
field public int initialKeyguardLayout;
field public int initialLayout;
field @Deprecated public String label;
+ field public int maxResizeHeight;
+ field public int maxResizeWidth;
field public int minHeight;
field public int minResizeHeight;
field public int minResizeWidth;
@@ -8417,6 +8442,8 @@
field @IdRes public int previewLayout;
field public android.content.ComponentName provider;
field public int resizeMode;
+ field public int targetCellHeight;
+ field public int targetCellWidth;
field public int updatePeriodMillis;
field public int widgetCategory;
field public int widgetFeatures;
@@ -10234,12 +10261,15 @@
method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
+ method @NonNull public int[] checkCallingOrSelfUriPermissions(@NonNull java.util.List<android.net.Uri>, int);
method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
method @CheckResult(suggest="#enforceCallingUriPermission(Uri,int,String)") public abstract int checkCallingUriPermission(android.net.Uri, int);
+ method @NonNull public int[] checkCallingUriPermissions(@NonNull java.util.List<android.net.Uri>, int);
method @CheckResult(suggest="#enforcePermission(String,int,int,String)") public abstract int checkPermission(@NonNull String, int, int);
method public abstract int checkSelfPermission(@NonNull String);
method @CheckResult(suggest="#enforceUriPermission(Uri,int,int,String)") public abstract int checkUriPermission(android.net.Uri, int, int, int);
method @CheckResult(suggest="#enforceUriPermission(Uri,String,String,int,int,int,String)") public abstract int checkUriPermission(@Nullable android.net.Uri, @Nullable String, @Nullable String, int, int, int);
+ method @NonNull public int[] checkUriPermissions(@NonNull java.util.List<android.net.Uri>, int, int, int);
method @Deprecated public abstract void clearWallpaper() throws java.io.IOException;
method @NonNull public android.content.Context createAttributionContext(@Nullable String);
method public abstract android.content.Context createConfigurationContext(@NonNull android.content.res.Configuration);
@@ -16524,9 +16554,13 @@
public class RippleDrawable extends android.graphics.drawable.LayerDrawable {
ctor public RippleDrawable(@NonNull android.content.res.ColorStateList, @Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable);
method public int getRadius();
+ method public int getRippleStyle();
method public void setColor(android.content.res.ColorStateList);
method public void setRadius(int);
+ method public void setRippleStyle(int) throws java.lang.IllegalArgumentException;
field public static final int RADIUS_AUTO = -1; // 0xffffffff
+ field public static final int STYLE_PATTERNED = 1; // 0x1
+ field public static final int STYLE_SOLID = 0; // 0x0
}
public class RotateDrawable extends android.graphics.drawable.DrawableWrapper {
@@ -24056,13 +24090,49 @@
package android.media.metrics {
+ public abstract class Event {
+ ctor protected Event(long);
+ method @IntRange(from=0xffffffff) public long getTimeSinceCreatedMillis();
+ }
+
public class MediaMetricsManager {
method @NonNull public android.media.metrics.PlaybackSession createPlaybackSession();
+ field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL
}
public final class PlaybackSession implements java.lang.AutoCloseable {
method public void close();
method @NonNull public String getId();
+ method public void reportPlaybackStateEvent(@NonNull android.media.metrics.PlaybackStateEvent);
+ }
+
+ public final class PlaybackStateEvent extends android.media.metrics.Event implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getState();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.PlaybackStateEvent> CREATOR;
+ field public static final int STATE_ABANDONED = 15; // 0xf
+ field public static final int STATE_BUFFERING = 6; // 0x6
+ field public static final int STATE_ENDED = 11; // 0xb
+ field public static final int STATE_FAILED = 13; // 0xd
+ field public static final int STATE_INTERRUPTED_BY_AD = 14; // 0xe
+ field public static final int STATE_JOINING_BACKGROUND = 1; // 0x1
+ field public static final int STATE_JOINING_FOREGROUND = 2; // 0x2
+ field public static final int STATE_NOT_STARTED = 0; // 0x0
+ field public static final int STATE_PAUSED = 4; // 0x4
+ field public static final int STATE_PAUSED_BUFFERING = 7; // 0x7
+ field public static final int STATE_PLAYING = 3; // 0x3
+ field public static final int STATE_SEEKING = 5; // 0x5
+ field public static final int STATE_STOPPED = 12; // 0xc
+ field public static final int STATE_SUPPRESSED = 9; // 0x9
+ field public static final int STATE_SUPPRESSED_BUFFERING = 10; // 0xa
+ }
+
+ public static final class PlaybackStateEvent.Builder {
+ ctor public PlaybackStateEvent.Builder();
+ method @NonNull public android.media.metrics.PlaybackStateEvent build();
+ method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setState(int);
+ method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
}
}
@@ -34710,6 +34780,7 @@
field public static final String EXTRA_AUTHORITIES = "authorities";
field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
field public static final String EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED = "android.provider.extra.BIOMETRIC_AUTHENTICATORS_ALLOWED";
+ field public static final String EXTRA_CHANNEL_FILTER_LIST = "android.provider.extra.CHANNEL_FILTER_LIST";
field public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID";
field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
@@ -35050,30 +35121,18 @@
public static final class SimPhonebookContract.SimRecords {
method @NonNull public static android.net.Uri getContentUri(int, int);
+ method @WorkerThread public static int getEncodedNameLength(@NonNull android.content.ContentResolver, @NonNull String);
method @NonNull public static android.net.Uri getItemUri(int, int, int);
- method @NonNull @WorkerThread public static android.provider.SimPhonebookContract.SimRecords.NameValidationResult validateName(@NonNull android.content.ContentResolver, int, int, @NonNull String);
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2";
field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
field public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type";
+ field public static final int ERROR_NAME_UNSUPPORTED = -1; // 0xffffffff
field public static final String NAME = "name";
field public static final String PHONE_NUMBER = "phone_number";
field public static final String RECORD_NUMBER = "record_number";
field public static final String SUBSCRIPTION_ID = "subscription_id";
}
- public static final class SimPhonebookContract.SimRecords.NameValidationResult implements android.os.Parcelable {
- ctor public SimPhonebookContract.SimRecords.NameValidationResult(@NonNull String, @NonNull String, int, int);
- method public int describeContents();
- method public int getEncodedLength();
- method public int getMaxEncodedLength();
- method @NonNull public String getName();
- method @NonNull public String getSanitizedName();
- method public boolean isSupportedCharacter(int);
- method public boolean isValid();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.provider.SimPhonebookContract.SimRecords.NameValidationResult> CREATOR;
- }
-
public class SyncStateContract {
ctor public SyncStateContract();
}
@@ -54635,6 +54694,7 @@
public class RemoteViews implements android.view.LayoutInflater.Filter android.os.Parcelable {
ctor public RemoteViews(String, int);
ctor public RemoteViews(android.widget.RemoteViews, android.widget.RemoteViews);
+ ctor public RemoteViews(@NonNull java.util.Map<android.graphics.PointF,android.widget.RemoteViews>);
ctor public RemoteViews(android.widget.RemoteViews);
ctor public RemoteViews(android.os.Parcel);
method public void addView(@IdRes int, android.widget.RemoteViews);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 51d5139..de02d0b 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -73,6 +73,7 @@
public class UsbManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int getGadgetHalVersion();
method public int getUsbBandwidth();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int getUsbHalVersion();
field public static final int GADGET_HAL_NOT_SUPPORTED = -1; // 0xffffffff
field public static final int GADGET_HAL_V1_0 = 10; // 0xa
field public static final int GADGET_HAL_V1_1 = 11; // 0xb
@@ -85,6 +86,11 @@
field public static final int USB_DATA_TRANSFER_RATE_HIGH_SPEED = 480; // 0x1e0
field public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2; // 0x2
field public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1; // 0xffffffff
+ field public static final int USB_HAL_NOT_SUPPORTED = -1; // 0xffffffff
+ field public static final int USB_HAL_V1_0 = 10; // 0xa
+ field public static final int USB_HAL_V1_1 = 11; // 0xb
+ field public static final int USB_HAL_V1_2 = 12; // 0xc
+ field public static final int USB_HAL_V1_3 = 13; // 0xd
}
}
@@ -211,6 +217,7 @@
method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>);
method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder);
method public void teardownTestNetwork(@NonNull android.net.Network);
+ field public static final String TEST_TAP_PREFIX = "testtap";
}
public final class UnderlyingNetworkInfo implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b261d92..cec2f98 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -27,6 +27,7 @@
field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
field public static final String BACKUP = "android.permission.BACKUP";
+ field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION";
field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE";
field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE";
field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
@@ -343,6 +344,7 @@
field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
field public static final int config_systemGallery = 17039399; // 0x1040027
+ field public static final int config_systemShell = 17039402; // 0x104002a
}
public static final class R.style {
@@ -680,6 +682,7 @@
}
public static class Notification.Action implements android.os.Parcelable {
+ field public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; // 0xc
field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb
}
@@ -872,7 +875,6 @@
}
public class DevicePolicyManager {
- method public boolean canAdminGrantSensorsPermissionsForUser(int);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
@@ -1852,6 +1854,7 @@
}
public final class BluetoothDevice implements android.os.Parcelable {
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean canBondWithoutDialog();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean cancelBondProcess();
method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission();
@@ -2120,6 +2123,7 @@
field public static final String OEM_LOCK_SERVICE = "oem_lock";
field public static final String PERMISSION_SERVICE = "permission";
field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
+ field public static final String REBOOT_READINESS_SERVICE = "reboot_readiness";
field public static final String ROLLBACK_SERVICE = "rollback";
field public static final String SEARCH_UI_SERVICE = "search_ui";
field public static final String SECURE_ELEMENT_SERVICE = "secure_element";
@@ -2815,16 +2819,49 @@
package android.graphics.fonts {
+ public final class FontFamilyUpdateRequest {
+ method @NonNull public java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.FontFamily> getFontFamilies();
+ method @NonNull public java.util.List<android.graphics.fonts.FontFileUpdateRequest> getFontFileUpdateRequests();
+ }
+
+ public static final class FontFamilyUpdateRequest.Builder {
+ ctor public FontFamilyUpdateRequest.Builder();
+ method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.Builder addFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest.FontFamily);
+ method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.Builder addFontFileUpdateRequest(@NonNull android.graphics.fonts.FontFileUpdateRequest);
+ method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest build();
+ }
+
+ public static final class FontFamilyUpdateRequest.Font {
+ ctor public FontFamilyUpdateRequest.Font(@NonNull String, @NonNull android.graphics.fonts.FontStyle, @NonNull java.util.List<android.graphics.fonts.FontVariationAxis>);
+ method @NonNull public java.util.List<android.graphics.fonts.FontVariationAxis> getAxes();
+ method @NonNull public String getPostScriptName();
+ method @NonNull public android.graphics.fonts.FontStyle getStyle();
+ }
+
+ public static final class FontFamilyUpdateRequest.FontFamily {
+ ctor public FontFamilyUpdateRequest.FontFamily(@NonNull String, @NonNull java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font>);
+ method @NonNull public java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font> getFonts();
+ method @NonNull public String getName();
+ }
+
+ public final class FontFileUpdateRequest {
+ ctor public FontFileUpdateRequest(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[]);
+ method @NonNull public android.os.ParcelFileDescriptor getParcelFileDescriptor();
+ method @NonNull public byte[] getSignature();
+ }
+
public class FontManager {
method @Nullable public android.text.FontConfig getFontConfig();
- method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
+ method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int);
+ method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.graphics.fonts.FontFileUpdateRequest, @IntRange(from=0) int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb
field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff
field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa
+ field public static final int RESULT_ERROR_FONT_NOT_FOUND = -9; // 0xfffffff7
field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9
field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd
field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc
- field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7
field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe
field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8
field public static final int RESULT_SUCCESS = 0; // 0x0
@@ -3507,6 +3544,7 @@
field public static final int RESULT_FAILED_BAD_PARAMS = 2; // 0x2
field public static final int RESULT_FAILED_BUSY = 4; // 0x4
field public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8; // 0x8
+ field public static final int RESULT_FAILED_PERMISSION_DENIED = 9; // 0x9
field public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7; // 0x7
field public static final int RESULT_FAILED_TIMEOUT = 6; // 0x6
field public static final int RESULT_FAILED_UNINITIALIZED = 3; // 0x3
@@ -7341,6 +7379,7 @@
method @NonNull public int[] getAdministratorUids();
method @Nullable public String getSsid();
method @NonNull public int[] getTransportTypes();
+ method public boolean isPrivateDnsBroken();
method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c
field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
@@ -7355,6 +7394,7 @@
method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
method @NonNull public android.net.NetworkCapabilities build();
+ method @NonNull public android.net.NetworkCapabilities.Builder clearAll();
method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
@@ -8528,7 +8568,7 @@
method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
- method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
+ method @RequiresPermission(anyOf={android.Manifest.permission.BATTERY_PREDICTION, android.Manifest.permission.DEVICE_POWER}) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setFullPowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
@@ -8598,6 +8638,7 @@
public class SystemConfigManager {
method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+ method @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public int[] getSystemPermissionUids(@NonNull String);
}
public class SystemProperties {
@@ -9410,22 +9451,8 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean);
}
- public final class SimPhonebookContract {
- method @NonNull public static String getEfUriPath(int);
- field public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
- }
-
- public static final class SimPhonebookContract.ElementaryFiles {
- field public static final String EF_ADN_PATH_SEGMENT = "adn";
- field public static final String EF_FDN_PATH_SEGMENT = "fdn";
- field public static final String EF_SDN_PATH_SEGMENT = "sdn";
- field public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
- }
-
public static final class SimPhonebookContract.SimRecords {
- field public static final String EXTRA_NAME_VALIDATION_RESULT = "android.provider.extra.NAME_VALIDATION_RESULT";
field public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2";
- field public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
}
public static final class Telephony.Carriers implements android.provider.BaseColumns {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 29be5c6..c03f660 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -837,14 +837,16 @@
public class FontManager {
method @Nullable public android.text.FontConfig getFontConfig();
- method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
+ method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int);
+ method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.graphics.fonts.FontFileUpdateRequest, @IntRange(from=0) int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb
field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff
field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa
+ field public static final int RESULT_ERROR_FONT_NOT_FOUND = -9; // 0xfffffff7
field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9
field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd
field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc
- field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7
field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe
field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8
field public static final int RESULT_SUCCESS = 0; // 0x0
@@ -2568,6 +2570,7 @@
public final class InputMethodManager {
method public int getDisplayId();
+ method public boolean hasActiveInputConnection(@Nullable android.view.View);
method public boolean isInputMethodPickerShown();
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 9a20e0f..85fb543 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -16,6 +16,7 @@
package android.app;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.StrictMode.vmIncorrectContextUseEnabled;
@@ -105,6 +106,7 @@
import java.nio.ByteOrder;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -2181,6 +2183,18 @@
}
}
+ @NonNull
+ @Override
+ public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+ int modeFlags) {
+ try {
+ return ActivityManager.getService().checkUriPermissions(uris, pid, uid, modeFlags,
+ null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @hide */
@Override
public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
@@ -2207,12 +2221,30 @@
return PackageManager.PERMISSION_DENIED;
}
+ @NonNull
+ @Override
+ public int[] checkCallingUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+ int pid = Binder.getCallingPid();
+ if (pid != Process.myPid()) {
+ return checkUriPermissions(uris, pid, Binder.getCallingUid(), modeFlags);
+ }
+ int[] res = new int[uris.size()];
+ Arrays.fill(res, PERMISSION_DENIED);
+ return res;
+ }
+
@Override
public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
return checkUriPermission(uri, Binder.getCallingPid(),
Binder.getCallingUid(), modeFlags);
}
+ @NonNull
+ @Override
+ public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+ return checkUriPermissions(uris, Binder.getCallingPid(), Binder.getCallingUid(), modeFlags);
+ }
+
@Override
public int checkUriPermission(Uri uri, String readPermission,
String writePermission, int pid, int uid, int modeFlags) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e73636f..4ad13e1 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -220,6 +220,8 @@
int getProcessLimit();
int checkUriPermission(in Uri uri, int pid, int uid, int mode, int userId,
in IBinder callerToken);
+ int[] checkUriPermissions(in List<Uri> uris, int pid, int uid, int mode,
+ in IBinder callerToken);
void grantUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri,
int mode, int userId);
void revokeUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri,
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/app/ILocalWallpaperColorConsumer.aidl
new file mode 100644
index 0000000..28b11ec
--- /dev/null
+++ b/core/java/android/app/ILocalWallpaperColorConsumer.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+import android.app.WallpaperColors;
+import android.graphics.RectF;
+
+/**
+ * @hide
+ */
+oneway interface ILocalWallpaperColorConsumer {
+ void onColorsChanged(in RectF area, in WallpaperColors colors);
+}
\ No newline at end of file
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 101917b..5402381 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -17,9 +17,11 @@
package android.app;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.app.IWallpaperManagerCallback;
+import android.app.ILocalWallpaperColorConsumer;
import android.app.WallpaperInfo;
import android.content.ComponentName;
import android.app.WallpaperColors;
@@ -162,6 +164,18 @@
WallpaperColors getWallpaperColors(int which, int userId, int displayId);
/**
+ * @hide
+ */
+ void removeOnLocalColorsChangedListener(
+ in ILocalWallpaperColorConsumer callback, int which, int userId, int displayId);
+
+ /**
+ * @hide
+ */
+ void addOnLocalColorsChangedListener(in ILocalWallpaperColorConsumer callback,
+ in List<RectF> regions, int which, int userId, int displayId);
+
+ /**
* Register a callback to receive color updates from a display
*/
void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId, int displayId);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b6fc47f..77daf8d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1604,6 +1604,14 @@
@SystemApi
public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11;
+ /**
+ * {@code SemanticAction}: Mark content as a potential phishing attempt.
+ * Note that this is only for use by the notification assistant services.
+ * @hide
+ */
+ @SystemApi
+ public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
+
private final Bundle mExtras;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Icon mIcon;
@@ -2315,7 +2323,8 @@
SEMANTIC_ACTION_THUMBS_UP,
SEMANTIC_ACTION_THUMBS_DOWN,
SEMANTIC_ACTION_CALL,
- SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
+ SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
+ SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
})
@Retention(RetentionPolicy.SOURCE)
public @interface SemanticAction {}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 323af821..685c222 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -80,6 +80,48 @@
public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id";
/**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing sound, like a tone picker
+ * ({@link #setSound(Uri, AudioAttributes)}).
+ */
+ public static final String EDIT_SOUND = "sound";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing vibration ({@link #enableVibration(boolean)},
+ * {@link #setVibrationPattern(long[])}).
+ */
+ public static final String EDIT_VIBRATION = "vibration";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing importance ({@link #setImportance(int)}) and/or conversation
+ * priority.
+ */
+ public static final String EDIT_IMPORTANCE = "importance";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing behavior on devices that are locked or have a turned off
+ * display ({@link #setLockscreenVisibility(int)}, {@link #enableLights(boolean)},
+ * {@link #setLightColor(int)}).
+ */
+ public static final String EDIT_LOCKED_DEVICE = "locked";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing do not disturb bypass {(@link #setBypassDnd(boolean)}) .
+ */
+ public static final String EDIT_ZEN = "dnd";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing conversation settings (demoting or restoring a channel to
+ * be a Conversation, changing bubble behavior, or setting the priority of a conversation).
+ */
+ public static final String EDIT_CONVERSATION = "convo";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing launcher behavior (showing badges)}.
+ */
+ public static final String EDIT_LAUNCHER = "launcher";
+
+ /**
* The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
* limit.
*/
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index c047fc2..c47b546 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -185,6 +185,7 @@
import android.permission.PermissionManager;
import android.print.IPrintManager;
import android.print.PrintManager;
+import android.scheduling.SchedulingFrameworkInitializer;
import android.security.FileIntegrityManager;
import android.security.IFileIntegrityService;
import android.service.oemlock.IOemLockService;
@@ -1440,6 +1441,7 @@
MediaFrameworkPlatformInitializer.registerServiceWrappers();
MediaFrameworkInitializer.registerServiceWrappers();
RoleFrameworkInitializer.registerServiceWrappers();
+ SchedulingFrameworkInitializer.registerServiceWrappers();
} finally {
// If any of the above code throws, we're in a pretty bad shape and the process
// will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 938ce0d..9019ddf 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -343,7 +343,9 @@
// TopActivityToken and bounds are important if top activity is in size compat
&& (!topActivityInSizeCompat || topActivityToken.equals(that.topActivityToken))
&& (!topActivityInSizeCompat || configuration.windowConfiguration.getBounds()
- .equals(that.configuration.windowConfiguration.getBounds()));
+ .equals(that.configuration.windowConfiguration.getBounds()))
+ && (!topActivityInSizeCompat || configuration.getLayoutDirection()
+ == that.configuration.getLayoutDirection());
}
/**
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index ac2f223..d7587bd 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -66,6 +66,7 @@
import android.os.StrictMode;
import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
@@ -107,6 +108,8 @@
private static boolean DEBUG = false;
private float mWallpaperXStep = -1;
private float mWallpaperYStep = -1;
+ private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+ new RectF(0, 0, 1, 1);
/** {@hide} */
private static final String PROP_WALLPAPER = "ro.config.wallpaper";
@@ -309,6 +312,8 @@
private int mCachedWallpaperUserId;
private Bitmap mDefaultWallpaper;
private Handler mMainLooperHandler;
+ private ArrayMap<LocalWallpaperColorConsumer, ILocalWallpaperColorConsumer>
+ mLocalColorCallbacks = new ArrayMap<>();
Globals(IWallpaperManager service, Looper looper) {
mService = service;
@@ -350,6 +355,40 @@
}
}
+ private ILocalWallpaperColorConsumer wrap(LocalWallpaperColorConsumer callback) {
+ ILocalWallpaperColorConsumer callback2 = new ILocalWallpaperColorConsumer.Stub() {
+ @Override
+ public void onColorsChanged(RectF area, WallpaperColors colors) {
+ callback.onColorsChanged(area, colors);
+ }
+ };
+ mLocalColorCallbacks.put(callback, callback2);
+ return callback2;
+ }
+
+ public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
+ @NonNull List<RectF> regions, int which, int userId, int displayId) {
+ try {
+ mService.addOnLocalColorsChangedListener(wrap(callback) , regions, which,
+ userId, displayId);
+ } catch (RemoteException e) {
+ // Can't get colors, connection lost.
+ }
+ }
+
+ public void removeOnColorsChangedListener(
+ @NonNull LocalWallpaperColorConsumer callback, int which, int userId,
+ int displayId) {
+ ILocalWallpaperColorConsumer callback2 = mLocalColorCallbacks.remove(callback);
+ if (callback2 == null) return;
+ try {
+ mService.removeOnLocalColorsChangedListener(
+ callback2, which, userId, displayId);
+ } catch (RemoteException e) {
+ // Can't get colors, connection lost.
+ }
+ }
+
/**
* Stop listening to wallpaper color events.
*
@@ -1057,6 +1096,29 @@
}
/**
+ * @hide
+ */
+ public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
+ List<RectF> regions) throws IllegalArgumentException {
+ for (RectF region : regions) {
+ if (!LOCAL_COLOR_BOUNDS.contains(region)) {
+ throw new IllegalArgumentException("Regions must be within bounds "
+ + LOCAL_COLOR_BOUNDS);
+ }
+ }
+ sGlobals.addOnColorsChangedListener(callback, regions, FLAG_SYSTEM,
+ mContext.getUserId(), mContext.getDisplayId());
+ }
+
+ /**
+ * @hide
+ */
+ public void removeOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback) {
+ sGlobals.removeOnColorsChangedListener(callback, FLAG_SYSTEM, mContext.getUserId(),
+ mContext.getDisplayId());
+ }
+
+ /**
* Version of {@link #getWallpaperFile(int)} that can access the wallpaper data
* for a given user. The caller must hold the INTERACT_ACROSS_USERS_FULL
* permission to access another user's wallpaper data.
@@ -2202,4 +2264,18 @@
onColorsChanged(colors, which);
}
}
+
+ /**
+ * Callback to update a consumer with a local color change
+ * @hide
+ */
+ public interface LocalWallpaperColorConsumer {
+
+ /**
+ * Gets called when a color of an area gets updated
+ * @param area
+ * @param colors
+ */
+ void onColorsChanged(RectF area, WallpaperColors colors);
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 907abb2..28242b0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1869,13 +1869,13 @@
/**
* Grants access to {@link #setNetworkLoggingEnabled}, {@link #isNetworkLoggingEnabled} and
* {@link #retrieveNetworkLogs}. Once granted the delegated app will start receiving
- * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner will no longer
- * receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback.
+ * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner or Profile Owner
+ * will no longer receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback.
* There can be at most one app that has this delegation.
* If another app already had delegated network logging access,
* it will lose the delegation when a new app is delegated.
*
- * <p> Can only be granted by Device Owner.
+ * <p> Can only be granted by Device Owner or Profile Owner of a managed profile.
*/
public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
@@ -11718,8 +11718,11 @@
}
/**
- * Called by a device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to
- * control the network logging feature.
+ * Called by a device owner, profile owner of a managed profile or delegated app with
+ * {@link #DELEGATION_NETWORK_LOGGING} to control the network logging feature.
+ *
+ * <p> When network logging is enabled by a profile owner, the network logs will only include
+ * work profile network activity, not activity on the personal profile.
*
* <p> Network logs contain DNS lookup and connect() library call events. The following library
* functions are recorded while network logging is active:
@@ -11759,7 +11762,7 @@
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if called by a delegated app.
* @param enabled whether network logging should be enabled or not.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not a device owner or profile owner.
* @see #setAffiliationIds
* @see #retrieveNetworkLogs
*/
@@ -11773,14 +11776,16 @@
}
/**
- * Return whether network logging is enabled by a device owner.
+ * Return whether network logging is enabled by a device owner or profile owner of
+ * a managed profile.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only
* be {@code null} if the caller is a delegated app with {@link #DELEGATION_NETWORK_LOGGING}
* or has MANAGE_USERS permission.
- * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise.
- * @throws SecurityException if {@code admin} is not a device owner and caller has
- * no MANAGE_USERS permission
+ * @return {@code true} if network logging is enabled by device owner or profile owner,
+ * {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not a device owner or profile owner and
+ * caller has no MANAGE_USERS permission
*/
public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) {
throwIfParentInstance("isNetworkLoggingEnabled");
@@ -11792,9 +11797,14 @@
}
/**
- * Called by device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to retrieve
- * the most recent batch of network logging events.
- * A device owner has to provide a batchToken provided as part of
+ * Called by device owner, profile owner of a managed profile or delegated app with
+ * {@link #DELEGATION_NETWORK_LOGGING} to retrieve the most recent batch of
+ * network logging events.
+ *
+ * <p> When network logging is enabled by a profile owner, the network logs will only include
+ * work profile network activity, not activity on the personal profile.
+ *
+ * A device owner or profile owner has to provide a batchToken provided as part of
* {@link DeviceAdminReceiver#onNetworkLogsAvailable} callback. If the token doesn't match the
* token of the most recent available batch of logs, {@code null} will be returned.
*
@@ -11806,11 +11816,11 @@
* after the device device owner has been notified via
* {@link DeviceAdminReceiver#onNetworkLogsAvailable}.
*
- * <p>If a secondary user or profile is created, calling this method will throw a
- * {@link SecurityException} until all users become affiliated again. It will also no longer be
- * possible to retrieve the network logs batch with the most recent batchToken provided
- * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See
- * {@link DevicePolicyManager#setAffiliationIds}.
+ * <p>If the caller is not a profile owner and a secondary user or profile is created, calling
+ * this method will throw a {@link SecurityException} until all users become affiliated again.
+ * It will also no longer be possible to retrieve the network logs batch with the most recent
+ * batchToken provided by {@link DeviceAdminReceiver#onNetworkLogsAvailable}.
+ * See {@link DevicePolicyManager#setAffiliationIds}.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if called by a delegated app.
@@ -11818,8 +11828,9 @@
* @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns
* {@code null} if the batch represented by batchToken is no longer available or if
* logging is disabled.
- * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
- * profile or secondary user that is not affiliated with the device.
+ * @throws SecurityException if {@code admin} is not a device owner, profile owner or if the
+ * {@code admin} is not a profile owner and there is at least one profile or secondary user
+ * that is not affiliated with the device.
* @see #setAffiliationIds
* @see DeviceAdminReceiver#onNetworkLogsAvailable
*/
@@ -11938,11 +11949,12 @@
}
/**
- * Called by the system to get the time at which the device owner last retrieved network logging
- * events.
+ * Called by the system to get the time at which the device owner or profile owner of a
+ * managed profile last retrieved network logging events.
*
- * @return the time at which the device owner most recently retrieved network logging events, in
- * milliseconds since epoch; -1 if network logging events were never retrieved.
+ * @return the time at which the device owner or profile owner most recently retrieved network
+ * logging events, in milliseconds since epoch; -1 if network logging events were
+ * never retrieved.
* @throws SecurityException if the caller is not the device owner, does not hold the
* MANAGE_USERS permission and is not the system.
*
@@ -13358,26 +13370,93 @@
*/
public boolean canAdminGrantSensorsPermissions() {
throwIfParentInstance("canAdminGrantSensorsPermissions");
- return canAdminGrantSensorsPermissionsForUser(myUserId());
- }
-
- /**
- * Returns true if the admin can control grants of sensors-related permissions, for
- * a given user.
- *
- * @hide
- * @param userId The ID of the user to check.
- * @return if the admin may grant these permissions, false otherwise.
- */
- @SystemApi
- public boolean canAdminGrantSensorsPermissionsForUser(int userId) {
if (mService == null) {
return false;
}
try {
- return mService.canAdminGrantSensorsPermissionsForUser(userId);
+ return mService.canAdminGrantSensorsPermissionsForUser(myUserId());
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
+
+ /**
+ * Called by device owner or profile owner of an organization-owned managed profile to
+ * enable or disable USB data signaling for the device. When disabled, USB data connections
+ * (except from charging functions) are prohibited.
+ *
+ * <p> This API is not supported on all devices, the caller should call
+ * {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data
+ * signaling is supported on the device.
+ *
+ * @param enabled whether USB data signaling should be enabled or not.
+ * @throws SecurityException if the caller is not a device owner or a profile owner on
+ * an organization-owned managed profile.
+ * @throws IllegalStateException if disabling USB data signaling is not supported or
+ * if USB data signaling fails to be enabled/disabled.
+ */
+ public void setUsbDataSignalingEnabled(boolean enabled) {
+ throwIfParentInstance("setUsbDataSignalingEnabled");
+ if (mService != null) {
+ try {
+ mService.setUsbDataSignalingEnabled(mContext.getPackageName(), enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Called by device owner or profile owner of an organization-owned managed profile to return
+ * whether USB data signaling is currently enabled by the admin.
+ *
+ * @return {@code true} if USB data signaling is enabled, {@code false} otherwise.
+ */
+ public boolean isUsbDataSignalingEnabled() {
+ throwIfParentInstance("isUsbDataSignalingEnabled");
+ if (mService != null) {
+ try {
+ return mService.isUsbDataSignalingEnabled(mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Called by the system to check whether USB data signaling is currently enabled for this user.
+ *
+ * @param userId which user to check for.
+ * @return {@code true} if USB data signaling is enabled, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isUsbDataSignalingEnabledForUser(@UserIdInt int userId) {
+ throwIfParentInstance("isUsbDataSignalingEnabledForUser");
+ if (mService != null) {
+ try {
+ return mService.isUsbDataSignalingEnabledForUser(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether enabling or disabling USB data signaling is supported on the device.
+ *
+ * @return {@code true} if the device supports enabling and disabling USB data signaling.
+ */
+ public boolean canUsbDataSignalingBeDisabled() {
+ throwIfParentInstance("canUsbDataSignalingBeDisabled");
+ if (mService != null) {
+ try {
+ return mService.canUsbDataSignalingBeDisabled();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 032cf24..94388cf 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -502,4 +502,9 @@
void resetDefaultCrossProfileIntentFilters(int userId);
boolean canAdminGrantSensorsPermissionsForUser(int userId);
+
+ void setUsbDataSignalingEnabled(String callerPackage, boolean enabled);
+ boolean isUsbDataSignalingEnabled(String callerPackage);
+ boolean isUsbDataSignalingEnabledForUser(int userId);
+ boolean canUsbDataSignalingBeDisabled();
}
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index 28b7340..ab38832 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -20,8 +20,16 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.compat.Compatibility;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
+import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.IPlatformCompat;
+
+import java.util.Map;
+
/**
* CompatChanges APIs - to be used by platform code only (including mainline
* modules).
@@ -89,4 +97,25 @@
return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid));
}
+ /**
+ * Set an app compat override for a given package. This will check whether the caller is allowed
+ * to perform this operation on the given apk and build. Only the installer package is allowed
+ * to set overrides on a non-debuggable final build and a non-test apk.
+ *
+ * @param packageName The package name of the app in question.
+ * @param overrides A map from changeId to the override applied for this change id.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG)
+ public static void setPackageOverride(String packageName,
+ Map<Long, PackageOverride> overrides) {
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides);
+ try {
+ platformCompat.setOverridesFromInstaller(config, packageName);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java
new file mode 100644
index 0000000..9f97cd4
--- /dev/null
+++ b/core/java/android/app/compat/PackageOverride.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.compat;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An app compat override applied to a given package and change id pairing.
+ *
+ * A package override contains a list of version ranges with the desired boolean value of
+ * the override for the app in this version range. Ranges can be open ended in either direction.
+ * An instance of PackageOverride gets created via {@link Builder} and is immutable once created.
+ *
+ * @hide
+ */
+public class PackageOverride implements Parcelable {
+
+ @IntDef({
+ VALUE_UNDEFINED,
+ VALUE_ENABLED,
+ VALUE_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
+ public @interface EvaluatedOverride {
+ }
+
+ /**
+ * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that
+ * this PackageOverride does not define the value of the override for the given version.
+ * @hide
+ */
+ public static final int VALUE_UNDEFINED = 0;
+ /**
+ * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that
+ * the override evaluates to {@code true} for the given version.
+ * @hide
+ */
+ public static final int VALUE_ENABLED = 1;
+ /**
+ * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that
+ * the override evaluates to {@code fakse} for the given version.
+ * @hide
+ */
+ public static final int VALUE_DISABLED = 2;
+
+ private final long mMinVersionCode;
+ private final long mMaxVersionCode;
+ private final boolean mEnabled;
+
+ private PackageOverride(long minVersionCode,
+ long maxVersionCode,
+ boolean enabled) {
+ this.mMinVersionCode = minVersionCode;
+ this.mMaxVersionCode = maxVersionCode;
+ this.mEnabled = enabled;
+ }
+
+ private PackageOverride(Parcel in) {
+ this(in.readLong(), in.readLong(), in.readBoolean());
+ }
+
+ /**
+ * Evaluate the override for the given {@code versionCode}. If no override is defined for
+ * the specified version code, {@link #VALUE_UNDEFINED} is returned.
+ * @hide
+ */
+ public @EvaluatedOverride int evaluate(long versionCode) {
+ if (versionCode >= mMinVersionCode && versionCode <= mMaxVersionCode) {
+ return mEnabled ? VALUE_ENABLED : VALUE_DISABLED;
+ }
+ return VALUE_UNDEFINED;
+ }
+
+ /**
+ * Evaluate the override independent of version code, i.e. only return an evaluated value if
+ * this range covers all versions, otherwise {@link #VALUE_UNDEFINED} is returned.
+ * @hide
+ */
+ public int evaluateForAllVersions() {
+ if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) {
+ return mEnabled ? VALUE_ENABLED : VALUE_DISABLED;
+ }
+ return VALUE_UNDEFINED;
+ }
+
+ /** Returns the minimum version code the override applies to. */
+ public long getMinVersionCode() {
+ return mMinVersionCode;
+ }
+
+ /** Returns the minimum version code the override applies from. */
+ public long getMaxVersionCode() {
+ return mMaxVersionCode;
+ }
+
+ /** Returns the enabled value for the override. */
+ public boolean getEnabled() {
+ return mEnabled;
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mMinVersionCode);
+ dest.writeLong(mMaxVersionCode);
+ dest.writeBoolean(mEnabled);
+ }
+
+ /** @hide */
+ @Override
+ public String toString() {
+ if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) {
+ return Boolean.toString(mEnabled);
+ }
+ return String.format("[%d,%d,%b]", mMinVersionCode, mMaxVersionCode, mEnabled);
+ }
+
+ /** @hide */
+ public static final Creator<PackageOverride> CREATOR =
+ new Creator<PackageOverride>() {
+
+ @Override
+ public PackageOverride createFromParcel(Parcel in) {
+ return new PackageOverride(in);
+ }
+
+ @Override
+ public PackageOverride[] newArray(int size) {
+ return new PackageOverride[size];
+ }
+ };
+
+ /**
+ * Builder to construct a PackageOverride.
+ */
+ public static class Builder {
+ private long mMinVersionCode = Long.MIN_VALUE;
+ private long mMaxVersionCode = Long.MAX_VALUE;
+ private boolean mEnabled;
+
+ /**
+ * Sets the minimum version code the override should apply from.
+ *
+ * default value: {@code Long.MIN_VALUE}.
+ */
+ public Builder setMinVersionCode(long minVersionCode) {
+ mMinVersionCode = minVersionCode;
+ return this;
+ }
+
+ /**
+ * Sets the maximum version code the override should apply to.
+ *
+ * default value: {@code Long.MAX_VALUE}.
+ */
+ public Builder setMaxVersionCode(long maxVersionCode) {
+ mMaxVersionCode = maxVersionCode;
+ return this;
+ }
+
+ /**
+ * Sets whether the override should be enabled for the given version range.
+ *
+ * default value: {@code false}.
+ */
+ public Builder setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ return this;
+ }
+
+ /**
+ * Build the {@link PackageOverride}.
+ *
+ * @throws IllegalArgumentException if {@code minVersionCode} is larger than
+ * {@code maxVersionCode}.
+ */
+ public PackageOverride build() {
+ if (mMinVersionCode > mMaxVersionCode) {
+ throw new IllegalArgumentException("minVersionCode must not be larger than "
+ + "maxVersionCode");
+ }
+ return new PackageOverride(mMinVersionCode, mMaxVersionCode, mEnabled);
+ }
+ };
+}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index a3c3a0e..42d90a7 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -16,6 +16,7 @@
package android.appwidget;
+import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityOptions;
import android.compat.annotation.UnsupportedAppUsage;
@@ -29,6 +30,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
@@ -53,6 +55,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
/**
@@ -89,6 +92,7 @@
int mLayoutId = -1;
private OnClickHandler mOnClickHandler;
private boolean mOnLightBackground;
+ PointF mCurrentSize = null;
private Executor mAsyncExecutor;
private CancellationSignal mLastExecutionSignal;
@@ -268,7 +272,8 @@
* Provide guidance about the size of this widget to the AppWidgetManager. The widths and
* heights should correspond to the full area the AppWidgetHostView is given. Padding added by
* the framework will be accounted for automatically. This information gets embedded into the
- * AppWidget options and causes a callback to the AppWidgetProvider.
+ * AppWidget options and causes a callback to the AppWidgetProvider. In addition, the list of
+ * sizes is explicitly set to an empty list.
* @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
*
* @param newOptions The bundle of options, in addition to the size information,
@@ -277,14 +282,97 @@
* @param minHeight The maximum height in dips that the widget will be displayed at.
* @param maxWidth The maximum width in dips that the widget will be displayed at.
* @param maxHeight The maximum height in dips that the widget will be displayed at.
- *
+ * @deprecated use {@link AppWidgetHostView#updateAppWidgetSize(Bundle, List)} instead.
*/
+ @Deprecated
public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
int maxHeight) {
updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false);
}
/**
+ * Provide guidance about the size of this widget to the AppWidgetManager. The sizes should
+ * correspond to the full area the AppWidgetHostView is given. Padding added by the framework
+ * will be accounted for automatically.
+ *
+ * This method will update the option bundle with the list of sizes and the min/max bounds for
+ * width and height.
+ *
+ * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
+ *
+ * @param newOptions The bundle of options, in addition to the size information.
+ * @param sizes Sizes, in dips, the widget may be displayed at without calling the provider
+ * again. Typically, this will be size of the widget in landscape and portrait.
+ * On some foldables, this might include the size on the outer and inner screens.
+ */
+ public void updateAppWidgetSize(@NonNull Bundle newOptions, @NonNull List<PointF> sizes) {
+ AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
+
+ Rect padding = getDefaultPadding();
+ float density = getResources().getDisplayMetrics().density;
+
+ float xPaddingDips = (padding.left + padding.right) / density;
+ float yPaddingDips = (padding.top + padding.bottom) / density;
+
+ ArrayList<PointF> paddedSizes = new ArrayList<>(sizes.size());
+ float minWidth = Float.MAX_VALUE;
+ float maxWidth = 0;
+ float minHeight = Float.MAX_VALUE;
+ float maxHeight = 0;
+ for (int i = 0; i < sizes.size(); i++) {
+ PointF size = sizes.get(i);
+ PointF paddedPoint = new PointF(Math.max(0.f, size.x - xPaddingDips),
+ Math.max(0.f, size.y - yPaddingDips));
+ paddedSizes.add(paddedPoint);
+ minWidth = Math.min(minWidth, paddedPoint.x);
+ maxWidth = Math.max(maxWidth, paddedPoint.x);
+ minHeight = Math.min(minHeight, paddedPoint.y);
+ maxHeight = Math.max(maxHeight, paddedPoint.y);
+ }
+ if (paddedSizes.equals(
+ widgetManager.getAppWidgetOptions(mAppWidgetId).<PointF>getParcelableArrayList(
+ AppWidgetManager.OPTION_APPWIDGET_SIZES))) {
+ return;
+ }
+ Bundle options = newOptions.deepCopy();
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, (int) minWidth);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, (int) minHeight);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, (int) maxWidth);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, (int) maxHeight);
+ options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes);
+ updateAppWidgetOptions(options);
+ }
+
+ /**
+ * Set the current size of the widget. This should be the full area the AppWidgetHostView is
+ * given. Padding added by the framework will be accounted for automatically.
+ *
+ * This size will be used to choose the appropriate layout the next time the {@link RemoteViews}
+ * is re-inflated, if it was created with {@link RemoteViews#RemoteViews(Map)} .
+ */
+ public void setCurrentSize(@NonNull PointF size) {
+ Rect padding = getDefaultPadding();
+ float density = getResources().getDisplayMetrics().density;
+ float xPaddingDips = (padding.left + padding.right) / density;
+ float yPaddingDips = (padding.top + padding.bottom) / density;
+ PointF newSize = new PointF(size.x - xPaddingDips, size.y - yPaddingDips);
+ if (!newSize.equals(mCurrentSize)) {
+ mCurrentSize = newSize;
+ mLayoutId = -1; // Prevents recycling the view.
+ }
+ }
+
+ /**
+ * Clear the current size, indicating it is not currently known.
+ */
+ public void clearCurrentSize() {
+ if (mCurrentSize != null) {
+ mCurrentSize = null;
+ mLayoutId = -1;
+ }
+ }
+
+ /**
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -322,6 +410,8 @@
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, newMinHeight);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, newMaxWidth);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, newMaxHeight);
+ newOptions.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES,
+ new ArrayList<PointF>());
updateAppWidgetOptions(newOptions);
}
}
@@ -440,7 +530,7 @@
// Try normal RemoteView inflation
if (content == null) {
try {
- content = remoteViews.apply(mContext, this, mOnClickHandler);
+ content = remoteViews.apply(mContext, this, mOnClickHandler, mCurrentSize);
if (LOGD) Log.d(TAG, "had to inflate new layout");
} catch (RuntimeException e) {
exception = e;
@@ -492,7 +582,8 @@
mView,
mAsyncExecutor,
new ViewApplyListener(remoteViews, layoutId, true),
- mOnClickHandler);
+ mOnClickHandler,
+ mCurrentSize);
} catch (Exception e) {
// Reapply failed. Try apply
}
@@ -502,7 +593,8 @@
this,
mAsyncExecutor,
new ViewApplyListener(remoteViews, layoutId, false),
- mOnClickHandler);
+ mOnClickHandler,
+ mCurrentSize);
}
}
@@ -533,7 +625,8 @@
AppWidgetHostView.this,
mAsyncExecutor,
new ViewApplyListener(mViews, mLayoutId, false),
- mOnClickHandler);
+ mOnClickHandler,
+ mCurrentSize);
} else {
applyContent(null, false, e);
}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 37093a1..aac8710 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -217,6 +217,12 @@
public static final String OPTION_APPWIDGET_MAX_HEIGHT = "appWidgetMaxHeight";
/**
+ * A bundle extra ({@code List<PointF>}) that contains the list of possible sizes, in dips, a
+ * widget instance can take.
+ */
+ public static final String OPTION_APPWIDGET_SIZES = "appWidgetSizes";
+
+ /**
* A bundle extra that hints to the AppWidgetProvider the category of host that owns this
* this widget. Can have the value {@link
* AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN} or {@link
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 42214d0..d893a5e 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -179,6 +179,44 @@
public int minResizeHeight;
/**
+ * Maximum width (in dp) which the widget can be resized to. This field has no effect if it is
+ * smaller than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
+ *
+ * <p>This field corresponds to the <code>android:maxResizeWidth</code> attribute in the
+ * AppWidget meta-data file.
+ */
+ @SuppressLint("MutableBareField")
+ public int maxResizeWidth;
+
+ /**
+ * Maximum height (in dp) which the widget can be resized to. This field has no effect if it is
+ * smaller than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
+ *
+ * <p>This field corresponds to the <code>android:maxResizeHeight</code> attribute in the
+ * AppWidget meta-data file.
+ */
+ @SuppressLint("MutableBareField")
+ public int maxResizeHeight;
+
+ /**
+ * The default width of a widget when added to a host, in units of launcher grid cells.
+ *
+ * <p>This field corresponds to the <code>android:targetCellWidth</code> attribute in the
+ * AppWidget meta-data file.
+ */
+ @SuppressLint("MutableBareField")
+ public int targetCellWidth;
+
+ /**
+ * The default height of a widget when added to a host, in units of launcher grid cells.
+ *
+ * <p>This field corresponds to the <code>android:targetCellHeight</code> attribute in the
+ * AppWidget meta-data file.
+ */
+ @SuppressLint("MutableBareField")
+ public int targetCellHeight;
+
+ /**
* How often, in milliseconds, that this AppWidget wants to be updated.
* The AppWidget manager may place a limit on how often a AppWidget is updated.
*
@@ -330,6 +368,10 @@
this.minHeight = in.readInt();
this.minResizeWidth = in.readInt();
this.minResizeHeight = in.readInt();
+ this.maxResizeWidth = in.readInt();
+ this.maxResizeHeight = in.readInt();
+ this.targetCellWidth = in.readInt();
+ this.targetCellHeight = in.readInt();
this.updatePeriodMillis = in.readInt();
this.initialLayout = in.readInt();
this.initialKeyguardLayout = in.readInt();
@@ -440,6 +482,10 @@
out.writeInt(this.minHeight);
out.writeInt(this.minResizeWidth);
out.writeInt(this.minResizeHeight);
+ out.writeInt(this.maxResizeWidth);
+ out.writeInt(this.maxResizeHeight);
+ out.writeInt(this.targetCellWidth);
+ out.writeInt(this.targetCellHeight);
out.writeInt(this.updatePeriodMillis);
out.writeInt(this.initialLayout);
out.writeInt(this.initialKeyguardLayout);
@@ -463,8 +509,12 @@
that.provider = this.provider == null ? null : this.provider.clone();
that.minWidth = this.minWidth;
that.minHeight = this.minHeight;
- that.minResizeWidth = this.minResizeHeight;
+ that.minResizeWidth = this.minResizeWidth;
that.minResizeHeight = this.minResizeHeight;
+ that.maxResizeWidth = this.maxResizeWidth;
+ that.maxResizeHeight = this.maxResizeHeight;
+ that.targetCellWidth = this.targetCellWidth;
+ that.targetCellHeight = this.targetCellHeight;
that.updatePeriodMillis = this.updatePeriodMillis;
that.initialLayout = this.initialLayout;
that.initialKeyguardLayout = this.initialKeyguardLayout;
@@ -512,6 +562,8 @@
minHeight = TypedValue.complexToDimensionPixelSize(minHeight, displayMetrics);
minResizeWidth = TypedValue.complexToDimensionPixelSize(minResizeWidth, displayMetrics);
minResizeHeight = TypedValue.complexToDimensionPixelSize(minResizeHeight, displayMetrics);
+ maxResizeWidth = TypedValue.complexToDimensionPixelSize(maxResizeWidth, displayMetrics);
+ maxResizeHeight = TypedValue.complexToDimensionPixelSize(maxResizeHeight, displayMetrics);
}
/**
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index ea7e5ea..ec46da0 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1654,7 +1654,7 @@
mContext = context;
}
- private String getOpPackageName() {
+ String getOpPackageName() {
// Workaround for legacy API for getting a BluetoothAdapter not
// passing a context
if (mContext != null) {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 3b8dec7..e7661db 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1236,7 +1236,8 @@
return false;
}
try {
- return service.createBond(this, transport, oobData);
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ return service.createBond(this, transport, oobData, adapter.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
@@ -1394,6 +1395,31 @@
}
/**
+ * Checks whether this bluetooth device is associated with CDM and meets the criteria to skip
+ * the bluetooth pairing dialog because it has been already consented by the CDM prompt.
+ *
+ * @return true if we can bond without the dialog, false otherwise
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean canBondWithoutDialog() {
+ final IBluetooth service = sService;
+ if (service == null) {
+ Log.e(TAG, "BT not enabled. Cannot check if we can skip pairing dialog");
+ return false;
+ }
+ try {
+ if (DBG) Log.d(TAG, "canBondWithoutDialog, device: " + this);
+ return service.canBondWithoutDialog(this);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ return false;
+ }
+
+ /**
* Returns whether there is an open connection to this device.
*
* @return True if there is at least one open connection to this device.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 025d777..10b00f2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -93,6 +93,7 @@
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -3520,6 +3521,7 @@
APPWIDGET_SERVICE,
//@hide: VOICE_INTERACTION_MANAGER_SERVICE,
//@hide: BACKUP_SERVICE,
+ REBOOT_READINESS_SERVICE,
ROLLBACK_SERVICE,
DROPBOX_SERVICE,
//@hide: DEVICE_IDLE_CONTROLLER,
@@ -4581,6 +4583,14 @@
public static final String AUTOFILL_MANAGER_SERVICE = "autofill";
/**
+ * Official published name of the (internal) text to speech manager service.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String TEXT_TO_SPEECH_MANAGER_SERVICE = "texttospeech";
+
+ /**
* Official published name of the content capture service.
*
* @hide
@@ -4742,6 +4752,17 @@
public static final String ROLLBACK_SERVICE = "rollback";
/**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.app.scheduling.RebootReadinessManagerService} for communicating
+ * with the reboot readiness detector.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @SystemApi
+ public static final String REBOOT_READINESS_SERVICE = "reboot_readiness";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.os.DropBoxManager} instance for recording
* diagnostic logs.
@@ -5725,6 +5746,32 @@
public abstract int checkUriPermission(Uri uri, int pid, int uid,
@Intent.AccessUriMode int modeFlags);
+ /**
+ * Determine whether a particular process and user ID has been granted
+ * permission to access a list of URIs. This only checks for permissions
+ * that have been explicitly granted -- if the given process/uid has
+ * more general access to the URI's content provider then this check will
+ * always fail.
+ *
+ * @param uris The list of URIs that is being checked.
+ * @param pid The process ID being checked against. Must be > 0.
+ * @param uid The user ID being checked against. A uid of 0 is the root
+ * user, which will pass every permission check.
+ * @param modeFlags The access modes to check for the list of uris
+ *
+ * @return Array of permission grants corresponding to each entry in the list of uris.
+ * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+ * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkCallingUriPermission
+ */
+ @NonNull
+ @PackageManager.PermissionResult
+ public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+ @Intent.AccessUriMode int modeFlags) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
/** @hide */
@SuppressWarnings("HiddenAbstractMethod")
@PackageManager.PermissionResult
@@ -5755,6 +5802,32 @@
public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
/**
+ * Determine whether the calling process and user ID has been
+ * granted permission to access a list of URIs. This is basically
+ * the same as calling {@link #checkUriPermissions(List, int, int, int)}
+ * with the pid and uid returned by {@link
+ * android.os.Binder#getCallingPid} and {@link
+ * android.os.Binder#getCallingUid}. One important difference is
+ * that if you are not currently processing an IPC, this function
+ * will always fail.
+ *
+ * @param uris The list of URIs that is being checked.
+ * @param modeFlags The access modes to check.
+ *
+ * @return Array of permission grants corresponding to each entry in the list of uris.
+ * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+ * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkUriPermission(Uri, int, int, int)
+ */
+ @NonNull
+ @PackageManager.PermissionResult
+ public int[] checkCallingUriPermissions(@NonNull List<Uri> uris,
+ @Intent.AccessUriMode int modeFlags) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Determine whether the calling process of an IPC <em>or you</em> has been granted
* permission to access a specific URI. This is the same as
* {@link #checkCallingUriPermission}, except it grants your own permissions
@@ -5775,6 +5848,28 @@
@Intent.AccessUriMode int modeFlags);
/**
+ * Determine whether the calling process of an IPC <em>or you</em> has been granted
+ * permission to access a list of URIs. This is the same as
+ * {@link #checkCallingUriPermission}, except it grants your own permissions
+ * if you are not currently processing an IPC. Use with care!
+ *
+ * @param uris The list of URIs that is being checked.
+ * @param modeFlags The access modes to check.
+ *
+ * @return Array of permission grants corresponding to each entry in the list of uris.
+ * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+ * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkCallingUriPermission
+ */
+ @NonNull
+ @PackageManager.PermissionResult
+ public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris,
+ @Intent.AccessUriMode int modeFlags) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Check both a Uri and normal permission. This allows you to perform
* both {@link #checkPermission} and {@link #checkUriPermission} in one
* call.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index c1c213e..b71fb27 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -53,6 +53,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -905,6 +906,13 @@
return mBase.checkUriPermission(uri, pid, uid, modeFlags);
}
+ @NonNull
+ @Override
+ public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+ int modeFlags) {
+ return mBase.checkUriPermissions(uris, pid, uid, modeFlags);
+ }
+
/** @hide */
@Override
public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
@@ -916,11 +924,23 @@
return mBase.checkCallingUriPermission(uri, modeFlags);
}
+ @NonNull
+ @Override
+ public int[] checkCallingUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+ return mBase.checkCallingUriPermissions(uris, modeFlags);
+ }
+
@Override
public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
return mBase.checkCallingOrSelfUriPermission(uri, modeFlags);
}
+ @NonNull
+ @Override
+ public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+ return mBase.checkCallingOrSelfUriPermissions(uris, modeFlags);
+ }
+
@Override
public int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission,
@Nullable String writePermission, int pid, int uid, int modeFlags) {
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 0c0e402..80fecc1 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -167,10 +167,17 @@
*/
public static final int FLAG_CACHE_BUBBLE_SHORTCUTS = 1;
+ /**
+ * Cache shortcuts which are used in People Tile.
+ * @hide
+ */
+ public static final int FLAG_CACHE_PEOPLE_TILE_SHORTCUTS = 2;
+
/** @hide */
@IntDef(flag = false, prefix = { "FLAG_CACHE_" }, value = {
FLAG_CACHE_NOTIFICATION_SHORTCUTS,
FLAG_CACHE_BUBBLE_SHORTCUTS,
+ FLAG_CACHE_PEOPLE_TILE_SHORTCUTS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutCacheFlags {}
@@ -1179,6 +1186,7 @@
* <ul>
* <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
* <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+ * <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
* </ul>
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
@@ -1209,6 +1217,7 @@
* <ul>
* <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
* <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+ * <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
* </ul>
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index ce0547f..5f80ba1 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -129,6 +129,12 @@
/** @hide */
public static final int FLAG_HAS_ICON_URI = 1 << 15;
+ /**
+ * TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
+ * need to be aware of the outside world. Replace this with a more extensible solution.
+ * @hide
+ */
+ public static final int FLAG_CACHED_PEOPLE_TILE = 1 << 29;
/**
* TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
@@ -138,7 +144,8 @@
public static final int FLAG_CACHED_BUBBLES = 1 << 30;
/** @hide */
- public static final int FLAG_CACHED_ALL = FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES;
+ public static final int FLAG_CACHED_ALL =
+ FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES | FLAG_CACHED_PEOPLE_TILE;
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
@@ -159,6 +166,7 @@
FLAG_HAS_ICON_URI,
FLAG_CACHED_NOTIFICATIONS,
FLAG_CACHED_BUBBLES,
+ FLAG_CACHED_PEOPLE_TILE
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutFlags {}
diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java
index bf35c4d..bc5d14a 100644
--- a/core/java/android/content/pm/dex/DexMetadataHelper.java
+++ b/core/java/android/content/pm/dex/DexMetadataHelper.java
@@ -22,17 +22,26 @@
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
+import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.jar.StrictJarFile;
+import android.util.JsonReader;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.zip.ZipEntry;
/**
* Helper class used to compute and validate the location of dex metadata files.
@@ -40,6 +49,12 @@
* @hide
*/
public class DexMetadataHelper {
+ public static final String TAG = "DexMetadataHelper";
+ /** $> adb shell 'setprop log.tag.DexMetadataHelper VERBOSE' */
+ public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ /** $> adb shell 'setprop pm.dexopt.dm.require_manifest true' */
+ private static String PROPERTY_DM_JSON_MANIFEST_REQUIRED = "pm.dexopt.dm.require_manifest";
+
private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
private DexMetadataHelper() {}
@@ -147,14 +162,31 @@
/**
* Validate that the given file is a dex metadata archive.
- * This is just a validation that the file is a zip archive.
+ * This is just a validation that the file is a zip archive that contains a manifest.json
+ * with the package name and version code.
*
* @throws PackageParserException if the file is not a .dm file.
*/
- public static void validateDexMetadataFile(String dmaPath) throws PackageParserException {
+ public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode)
+ throws PackageParserException {
+ validateDexMetadataFile(dmaPath, packageName, versionCode,
+ SystemProperties.getBoolean(PROPERTY_DM_JSON_MANIFEST_REQUIRED, false));
+ }
+
+ @VisibleForTesting
+ public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode,
+ boolean requireManifest) throws PackageParserException {
StrictJarFile jarFile = null;
+
+ if (DEBUG) {
+ Log.v(TAG, "validateDexMetadataFile: " + dmaPath + ", " + packageName +
+ ", " + versionCode);
+ }
+
try {
jarFile = new StrictJarFile(dmaPath, false, false);
+ validateDexMetadataManifest(dmaPath, jarFile, packageName, versionCode,
+ requireManifest);
} catch (IOException e) {
throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
"Error opening " + dmaPath, e);
@@ -168,6 +200,72 @@
}
}
+ /** Ensure that packageName and versionCode match the manifest.json in the .dm file */
+ private static void validateDexMetadataManifest(String dmaPath, StrictJarFile jarFile,
+ String packageName, long versionCode, boolean requireManifest)
+ throws IOException, PackageParserException {
+ if (!requireManifest) {
+ if (DEBUG) {
+ Log.v(TAG, "validateDexMetadataManifest: " + dmaPath
+ + " manifest.json check skipped");
+ }
+ return;
+ }
+
+ ZipEntry zipEntry = jarFile.findEntry("manifest.json");
+ if (zipEntry == null) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "Missing manifest.json in " + dmaPath);
+ }
+ InputStream inputStream = jarFile.getInputStream(zipEntry);
+
+ JsonReader reader;
+ try {
+ reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "Error opening manifest.json in " + dmaPath, e);
+ }
+ String jsonPackageName = null;
+ long jsonVersionCode = -1;
+
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ if (name.equals("packageName")) {
+ jsonPackageName = reader.nextString();
+ } else if (name.equals("versionCode")) {
+ jsonVersionCode = reader.nextLong();
+ } else {
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+
+ if (jsonPackageName == null || jsonVersionCode == -1) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "manifest.json in " + dmaPath
+ + " is missing 'packageName' and/or 'versionCode'");
+ }
+
+ if (!jsonPackageName.equals(packageName)) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "manifest.json in " + dmaPath + " has invalid packageName: " + jsonPackageName
+ + ", expected: " + packageName);
+ }
+
+ if (versionCode != jsonVersionCode) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "manifest.json in " + dmaPath + " has invalid versionCode: " + jsonVersionCode
+ + ", expected: " + versionCode);
+ }
+
+ if (DEBUG) {
+ Log.v(TAG, "validateDexMetadataManifest: " + dmaPath + ", " + packageName +
+ ", " + versionCode + ": successful");
+ }
+ }
+
/**
* Validates that all dex metadata paths in the given list have a matching apk.
* (for any foo.dm there should be either a 'foo' of a 'foo.apk' file).
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index af12536..cbb3baa 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -238,8 +238,8 @@
* permissions must be acquired and
* {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
*
- * This will be combined with the verification status and other system state to determine which
- * application is launched to handle an app link.
+ * Enabling an unverified domain will allow an application to open it, but this can only occur
+ * if no other app on the device is approved for the domain.
*
* @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
* @param domains The domains to toggle the state of.
@@ -290,13 +290,15 @@
public static final int REASON_ID_INVALID = 2;
public static final int REASON_SET_NULL_OR_EMPTY = 3;
public static final int REASON_UNKNOWN_DOMAIN = 4;
+ public static final int REASON_UNABLE_TO_APPROVE = 5;
/** @hide */
@IntDef({
REASON_ID_NULL,
REASON_ID_INVALID,
REASON_SET_NULL_OR_EMPTY,
- REASON_UNKNOWN_DOMAIN
+ REASON_UNKNOWN_DOMAIN,
+ REASON_UNABLE_TO_APPROVE
})
public @interface Reason {
}
@@ -313,6 +315,8 @@
case REASON_UNKNOWN_DOMAIN:
return "Domain set contains value that was not declared by the target package "
+ packageName;
+ case REASON_UNABLE_TO_APPROVE:
+ return "Domain set contains value that was owned by another package";
default:
return "Unknown failure";
}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
index 8d16f75..73346ef 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
@@ -17,6 +17,7 @@
package android.content.pm.verify.domain;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Parcelable;
@@ -30,18 +31,18 @@
import java.util.UUID;
/**
- * Contains the user selection state for a package. This means all web HTTP(S) domains
- * declared by a package in its manifest, whether or not they were marked for auto
- * verification.
+ * Contains the user selection state for a package. This means all web HTTP(S) domains declared by a
+ * package in its manifest, whether or not they were marked for auto verification.
* <p>
* By default, all apps are allowed to automatically open links with domains that they've
- * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}.
- * The user can decide to disable this, disallowing the application from opening these
- * links.
+ * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. The user
+ * can decide to disable this, disallowing the application from opening all links. Note that the
+ * toggle affects <b>all</b> links and is not based on the verification state of the domains.
* <p>
- * Separately, independent of this toggle, the user can choose specific domains to allow
- * an app to open, which is reflected as part of {@link #getHostToUserSelectionMap()},
- * which maps the domain name to the true/false state of whether it was enabled by the user.
+ * Assuming the toggle is enabled, the user can also select additional unverified domains to grant
+ * to the application to open, which is reflected in {@link #getHostToUserSelectionMap()}. But only
+ * a single application can be approved for a domain unless the applications are both approved. If
+ * another application is approved, the user will not be allowed to enable the domain.
* <p>
* These values can be changed through the
* {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
@@ -105,7 +106,8 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
+ // /DomainVerificationUserSelection.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -216,7 +218,7 @@
@Override
@DataClass.Generated.Member
- public boolean equals(@android.annotation.Nullable Object o) {
+ public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(DomainVerificationUserSelection other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
@@ -328,9 +330,9 @@
};
@DataClass.Generated(
- time = 1611799495498L,
+ time = 1612829797220L,
codegenVersion = "1.0.22",
- sourceFile = "frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java",
inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Boolean> mHostToUserSelectionMap\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java
new file mode 100644
index 0000000..25758e9
--- /dev/null
+++ b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request for updating or adding a font family on the system.
+ *
+ * <p>You can update or add a font family with custom style parameters. The following example
+ * defines a font family called "roboto" using "Roboto-Regular" font file that is already available
+ * on the system by preloading or {@link FontManager#updateFontFile}.
+ * <pre>
+ * FontManager fm = getContext().getSystemService(FontManager.class);
+ * fm.updateFontFamily(new FontFamilyUpdateRequest.Builder()
+ * .addFontFamily(new FontFamilyUpdateRequest.FontFamily("roboto", Arrays.asList(
+ * new FontFamilyUpdateRequest.Font(
+ * "Roboto-Regular",
+ * new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+ * Collections.emptyList()),
+ * new FontFamilyUpdateRequest.Font(
+ * "Roboto-Regular",
+ * new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC),
+ * Collections.emptyList()))))
+ * .build(), fm.getFontConfig().getConfigVersion());
+ * </pre>
+ *
+ * <p>You can update or add font files in the same request by calling
+ * {@link FontFamilyUpdateRequest.Builder#addFontFileUpdateRequest(FontFileUpdateRequest)}.
+ * The following example adds "YourFont" font file and defines "your-font" font family in the same
+ * request. In this case, the font file represented by {@code yourFontFd} should be an OpenType
+ * compliant font file and have "YourFont" as PostScript name (ID=6) in 'name' table.
+ * <pre>
+ * FontManager fm = getContext().getSystemService(FontManager.class);
+ * fm.updateFontFamily(new FontFamilyUpdateRequest.Builder()
+ * .addFontFileUpdateRequest(new FontFileUpdateRequest(yourFontFd, signature))
+ * .addFontFamily(new FontFamilyUpdateRequest.FontFamily("your-font", Arrays.asList(
+ * new FontFamilyUpdateRequest.Font(
+ * "YourFont",
+ * new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+ * Collections.emptyList()))))
+ * .build(), fm.getFontConfig().getConfigVersion());
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class FontFamilyUpdateRequest {
+
+ /**
+ * A font family definition.
+ */
+ public static final class FontFamily {
+ @NonNull
+ private final String mName;
+ @NonNull
+ private final List<Font> mFonts;
+
+ /**
+ * Constructs a FontFamily.
+ *
+ * <p>A font family has a name to identify the font family. Apps can use
+ * {@link android.graphics.Typeface#create(String, int)} or XML resources to use a specific
+ * font family.
+ *
+ * <p>A font family consists of multiple fonts with different styles. The style information
+ * can be specified by {@link Font}.
+ *
+ * @see android.graphics.Typeface#create(String, int)
+ * @see Font
+ */
+ public FontFamily(@NonNull String name, @NonNull List<Font> fonts) {
+ Objects.requireNonNull(name);
+ Preconditions.checkStringNotEmpty(name);
+ Objects.requireNonNull(fonts);
+ Preconditions.checkCollectionElementsNotNull(fonts, "fonts");
+ Preconditions.checkCollectionNotEmpty(fonts, "fonts");
+ mName = name;
+ mFonts = fonts;
+ }
+
+ /**
+ * Returns the name of this family.
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the fonts in this family.
+ */
+ @NonNull
+ public List<Font> getFonts() {
+ return mFonts;
+ }
+ }
+
+ /**
+ * A single entry in a font family representing a font.
+ */
+ public static final class Font {
+
+ @NonNull
+ private final String mPostScriptName;
+ @NonNull
+ private final FontStyle mStyle;
+ @NonNull
+ private final List<FontVariationAxis> mAxes;
+
+ /**
+ * Constructs a FontStyleVariation.
+ *
+ * <p>A font has a PostScript name to identify the font file to use, a {@link FontStyle}
+ * to specify the style, and a list of {@link FontVariationAxis} to specify axis tags and
+ * values for variable fonts. If the font file identified by {@code postScriptName} is not a
+ * variable font, {@code axes} must be empty.
+ *
+ * @param postScriptName The PostScript name of the font file to use. PostScript name is in
+ * Name ID 6 field in 'name' table, as specified by OpenType
+ * specification.
+ * @param style The style for this font.
+ * @param axes A list of {@link FontVariationAxis} to specify axis tags and values
+ * for variable fonts.
+ */
+ public Font(@NonNull String postScriptName, @NonNull FontStyle style,
+ @NonNull List<FontVariationAxis> axes) {
+ Objects.requireNonNull(postScriptName);
+ Preconditions.checkStringNotEmpty(postScriptName);
+ Objects.requireNonNull(style);
+ Objects.requireNonNull(axes);
+ Preconditions.checkCollectionElementsNotNull(axes, "axes");
+ mPostScriptName = postScriptName;
+ mStyle = style;
+ mAxes = axes;
+ }
+
+ /**
+ * Returns PostScript name of the font file to use.
+ */
+ @NonNull
+ public String getPostScriptName() {
+ return mPostScriptName;
+ }
+
+ /**
+ * Returns the style.
+ */
+ @NonNull
+ public FontStyle getStyle() {
+ return mStyle;
+ }
+
+ /**
+ * Returns the list of {@link FontVariationAxis}.
+ */
+ @NonNull
+ public List<FontVariationAxis> getAxes() {
+ return mAxes;
+ }
+ }
+
+ /**
+ * Builds a {@link FontFamilyUpdateRequest}.
+ */
+ public static final class Builder {
+ @NonNull
+ private final List<FontFileUpdateRequest> mFontFileUpdateRequests = new ArrayList<>();
+ @NonNull
+ private final List<FontFamily> mFontFamilies = new ArrayList<>();
+
+ /**
+ * Constructs a FontFamilyUpdateRequest.Builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Adds a {@link FontFileUpdateRequest} to execute as a part of the constructed
+ * {@link FontFamilyUpdateRequest}.
+ *
+ * @param request A font file update request.
+ * @return This builder object.
+ */
+ @NonNull
+ public Builder addFontFileUpdateRequest(@NonNull FontFileUpdateRequest request) {
+ Objects.requireNonNull(request);
+ mFontFileUpdateRequests.add(request);
+ return this;
+ }
+
+ /**
+ * Adds a font family to update an existing font family in the system font config or
+ * add as a new font family to the system font config.
+ *
+ * @param fontFamily An font family definition to add or update.
+ * @return This builder object.
+ */
+ @NonNull
+ public Builder addFontFamily(@NonNull FontFamily fontFamily) {
+ Objects.requireNonNull(fontFamily);
+ mFontFamilies.add(fontFamily);
+ return this;
+ }
+
+ /**
+ * Builds a {@link FontFamilyUpdateRequest}.
+ */
+ @NonNull
+ public FontFamilyUpdateRequest build() {
+ return new FontFamilyUpdateRequest(mFontFileUpdateRequests, mFontFamilies);
+ }
+ }
+
+ @NonNull
+ private final List<FontFileUpdateRequest> mFontFiles;
+
+ @NonNull
+ private final List<FontFamily> mFontFamilies;
+
+ private FontFamilyUpdateRequest(@NonNull List<FontFileUpdateRequest> fontFiles,
+ @NonNull List<FontFamily> fontFamilies) {
+ mFontFiles = fontFiles;
+ mFontFamilies = fontFamilies;
+ }
+
+ /**
+ * Returns the list of {@link FontFileUpdateRequest} that will be executed as a part of this
+ * request.
+ */
+ @NonNull
+ public List<FontFileUpdateRequest> getFontFileUpdateRequests() {
+ return mFontFiles;
+ }
+
+ /**
+ * Returns the list of {@link FontFamily} that will be updated in this request.
+ */
+ @NonNull
+ public List<FontFamily> getFontFamilies() {
+ return mFontFamilies;
+ }
+}
diff --git a/core/java/android/graphics/fonts/FontFileUpdateRequest.java b/core/java/android/graphics/fonts/FontFileUpdateRequest.java
new file mode 100644
index 0000000..cf1dca9
--- /dev/null
+++ b/core/java/android/graphics/fonts/FontFileUpdateRequest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.ParcelFileDescriptor;
+
+import java.util.Objects;
+
+/**
+ * Request for updating a font file on the system.
+ *
+ * @hide
+ */
+@SystemApi
+public final class FontFileUpdateRequest {
+
+ private final ParcelFileDescriptor mParcelFileDescriptor;
+ private final byte[] mSignature;
+
+ /**
+ * Creates a FontFileUpdateRequest with the given file and signature.
+ *
+ * @param parcelFileDescriptor A file descriptor of the font file.
+ * @param signature A PKCS#7 detached signature for verifying the font file.
+ */
+ public FontFileUpdateRequest(@NonNull ParcelFileDescriptor parcelFileDescriptor,
+ @NonNull byte[] signature) {
+ Objects.requireNonNull(parcelFileDescriptor);
+ Objects.requireNonNull(signature);
+ mParcelFileDescriptor = parcelFileDescriptor;
+ mSignature = signature;
+ }
+
+ /**
+ * Returns the file descriptor of the font file.
+ */
+ @NonNull
+ public ParcelFileDescriptor getParcelFileDescriptor() {
+ return mParcelFileDescriptor;
+ }
+
+ /**
+ * Returns the PKCS#7 detached signature for verifying the font file.
+ */
+ @NonNull
+ public byte[] getSignature() {
+ return mSignature;
+ }
+}
diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java
index abb4f9f..e512cf1 100644
--- a/core/java/android/graphics/fonts/FontManager.java
+++ b/core/java/android/graphics/fonts/FontManager.java
@@ -35,6 +35,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -58,7 +60,7 @@
RESULT_ERROR_VERIFICATION_FAILURE, RESULT_ERROR_VERSION_MISMATCH,
RESULT_ERROR_INVALID_FONT_FILE, RESULT_ERROR_INVALID_FONT_NAME,
RESULT_ERROR_DOWNGRADING, RESULT_ERROR_FAILED_UPDATE_CONFIG,
- RESULT_ERROR_FONT_UPDATER_DISABLED, RESULT_ERROR_REMOTE_EXCEPTION })
+ RESULT_ERROR_FONT_UPDATER_DISABLED, RESULT_ERROR_FONT_NOT_FOUND })
@Retention(RetentionPolicy.SOURCE)
public @interface ResultCode {}
@@ -131,9 +133,10 @@
public static final int RESULT_ERROR_VERSION_MISMATCH = -8;
/**
- * Indicates a failure due to IPC communication.
+ * Indicates a failure occurred because a font with the specified PostScript name could not be
+ * found.
*/
- public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9;
+ public static final int RESULT_ERROR_FONT_NOT_FOUND = -9;
/**
* Indicates a failure of opening font file.
@@ -205,42 +208,40 @@
}
/**
- * Update system installed font file.
+ * Update a system installed font file.
*
* <p>
- * To protect devices, system font updater relies on the Linux Kernel feature called fs-verity.
- * If the device is not ready for fs-verity, {@link #RESULT_ERROR_FONT_UPDATER_DISABLED} will be
+ * To protect devices, system font updater relies on a Linux Kernel feature called fs-verity.
+ * If the device does not support fs-verity, {@link #RESULT_ERROR_FONT_UPDATER_DISABLED} will be
* returned.
*
- * Android only accepts OpenType compliant font files. If other font files are provided,
+ * <p>Android only accepts OpenType compliant font files. If other font files are provided,
* {@link #RESULT_ERROR_INVALID_FONT_FILE} will be returned.
*
- * The font file to be updated is identified by PostScript name stored in name table. If the
- * font file doesn't have PostScript name entry, {@link #RESULT_ERROR_INVALID_FONT_NAME} will be
- * returned.
+ * <p>The font file to be updated is identified by PostScript name stored in the name table. If
+ * the font file doesn't have PostScript name entry, {@link #RESULT_ERROR_INVALID_FONT_NAME}
+ * will be returned.
*
- * The entire font file is verified with the given signature for the system installed
- * certificate. If the system cannot verify the font contents,
+ * <p>The entire font file is verified with the given signature using system installed
+ * certificates. If the system cannot verify the font file contents,
* {@link #RESULT_ERROR_VERIFICATION_FAILURE} will be returned.
*
- * The font file must have newer or equal revision number in the head table. In other words, the
- * downgrading font file is not allowed. If the older font file is provided,
+ * <p>The font file must have a newer revision number in the head table. In other words, it is
+ * not allowed to downgrade a font file. If an older font file is provided,
* {@link #RESULT_ERROR_DOWNGRADING} will be returned.
*
- * The caller must specify the base config version for keeping consist system configuration. If
- * the system configuration is updated for some reason between you get config with
- * {@link #getFontConfig()} and calling this method, {@link #RESULT_ERROR_VERSION_MISMATCH} will
- * be returned. Get the latest font configuration by calling {@link #getFontConfig()} again and
- * try with the latest config version again.
+ * <p>The caller must specify the base config version for keeping the font configuration
+ * consistent. If the font configuration is updated for some reason between the time you get
+ * a configuration with {@link #getFontConfig()} and the time when you call this method,
+ * {@link #RESULT_ERROR_VERSION_MISMATCH} will be returned. Get the latest font configuration by
+ * calling {@link #getFontConfig()} and call this method again with the latest config version.
*
- * @param pfd A file descriptor of the font file.
- * @param signature A PKCS#7 detached signature for verifying entire font files.
- * @param baseVersion A base config version to be updated. You can get latest config version by
- * {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If the
- * system has newer config version, the update will fail with
- * {@link #RESULT_ERROR_VERSION_MISMATCH}. Try to get the latest config and
- * try update again.
- * @return result code.
+ * @param request A {@link FontFileUpdateRequest} to execute.
+ * @param baseVersion A base config version to be updated. You can get the latest config version
+ * by {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If
+ * the system has a newer config version, the update will fail with
+ * {@link #RESULT_ERROR_VERSION_MISMATCH}.
+ * @return A result code.
*
* @see FontConfig#getConfigVersion()
* @see #getFontConfig()
@@ -253,18 +254,88 @@
* @see #RESULT_ERROR_DOWNGRADING
* @see #RESULT_ERROR_FAILED_UPDATE_CONFIG
* @see #RESULT_ERROR_FONT_UPDATER_DISABLED
- * @see #RESULT_ERROR_REMOTE_EXCEPTION
*/
@RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFile(
+ @NonNull FontFileUpdateRequest request, @IntRange(from = 0) int baseVersion) {
+ try {
+ return mIFontManager.updateFontFile(new FontUpdateRequest(
+ request.getParcelFileDescriptor(), request.getSignature()), baseVersion);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @deprecated Use {@link #updateFontFile(FontFileUpdateRequest, int)}
+ */
+ // TODO: Remove this API before Developer Preview 3.
+ @Deprecated
+ @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFile(
@NonNull ParcelFileDescriptor pfd,
@NonNull byte[] signature,
@IntRange(from = 0) int baseVersion
) {
+ return updateFontFile(new FontFileUpdateRequest(pfd, signature), baseVersion);
+ }
+
+
+ /**
+ * Update or add system wide font families.
+ *
+ * <p>This method will update existing font families or add new font families. The updated
+ * font family definitions will be used when creating {@link android.graphics.Typeface} objects
+ * with using {@link android.graphics.Typeface#create(String, int)} specifying the family name,
+ * or through XML resources. Note that system fallback fonts cannot be modified by this method.
+ * Apps must use {@link android.graphics.Typeface.CustomFallbackBuilder} to use custom fallback
+ * fonts.
+ *
+ * <p>Font files can be updated by including {@link FontFileUpdateRequest} to {@code request}
+ * via {@link FontFamilyUpdateRequest.Builder#addFontFileUpdateRequest(FontFileUpdateRequest)}.
+ * The same constraints as {@link #updateFontFile} will apply when updating font files.
+ *
+ * <p>The caller must specify the base config version for keeping the font configuration
+ * consistent. If the font configuration is updated for some reason between the time you get
+ * a configuration with {@link #getFontConfig()} and the time when you call this method,
+ * {@link #RESULT_ERROR_VERSION_MISMATCH} will be returned. Get the latest font configuration by
+ * calling {@link #getFontConfig()} and call this method again with the latest config version.
+ *
+ * @param request A {@link FontFamilyUpdateRequest} to execute.
+ * @param baseVersion A base config version to be updated. You can get the latest config version
+ * by {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If
+ * the system has a newer config version, the update will fail with
+ * {@link #RESULT_ERROR_VERSION_MISMATCH}.
+ * @return A result code.
+ * @see FontConfig#getConfigVersion()
+ * @see #getFontConfig()
+ * @see #RESULT_SUCCESS
+ * @see #RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE
+ * @see #RESULT_ERROR_VERIFICATION_FAILURE
+ * @see #RESULT_ERROR_VERSION_MISMATCH
+ * @see #RESULT_ERROR_INVALID_FONT_FILE
+ * @see #RESULT_ERROR_INVALID_FONT_NAME
+ * @see #RESULT_ERROR_DOWNGRADING
+ * @see #RESULT_ERROR_FAILED_UPDATE_CONFIG
+ * @see #RESULT_ERROR_FONT_UPDATER_DISABLED
+ * @see #RESULT_ERROR_FONT_NOT_FOUND
+ */
+ @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFamily(
+ @NonNull FontFamilyUpdateRequest request, @IntRange(from = 0) int baseVersion) {
+ List<FontUpdateRequest> requests = new ArrayList<>();
+ List<FontFileUpdateRequest> fontFileUpdateRequests = request.getFontFileUpdateRequests();
+ for (int i = 0; i < fontFileUpdateRequests.size(); i++) {
+ FontFileUpdateRequest fontFile = fontFileUpdateRequests.get(i);
+ requests.add(new FontUpdateRequest(fontFile.getParcelFileDescriptor(),
+ fontFile.getSignature()));
+ }
+ List<FontFamilyUpdateRequest.FontFamily> fontFamilies = request.getFontFamilies();
+ for (int i = 0; i < fontFamilies.size(); i++) {
+ FontFamilyUpdateRequest.FontFamily fontFamily = fontFamilies.get(i);
+ requests.add(new FontUpdateRequest(fontFamily.getName(), fontFamily.getFonts()));
+ }
try {
- return mIFontManager.updateFont(baseVersion, new FontUpdateRequest(pfd, signature));
+ return mIFontManager.updateFontFamily(requests, baseVersion);
} catch (RemoteException e) {
- Log.e(TAG, "Failed to call updateFont API", e);
- return RESULT_ERROR_REMOTE_EXCEPTION;
+ throw e.rethrowFromSystemServer();
}
}
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index f551d6a..b79c8f62 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -19,13 +19,17 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.LocaleList;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.text.FontConfig;
+import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* Represents a font update request. Currently only font install request is supported.
@@ -80,6 +84,26 @@
mFontFamily = fontFamily;
}
+ public FontUpdateRequest(@NonNull String postScriptName,
+ @NonNull List<FontFamilyUpdateRequest.Font> variations) {
+ // TODO: Serialize the request directly instead of reusing FontConfig.FontFamily.
+ this(createFontFamily(postScriptName, variations));
+ }
+
+ private static FontConfig.FontFamily createFontFamily(@NonNull String postScriptName,
+ @NonNull List<FontFamilyUpdateRequest.Font> fonts) {
+ List<FontConfig.Font> configFonts = new ArrayList<>(fonts.size());
+ for (FontFamilyUpdateRequest.Font font : fonts) {
+ // TODO: Support .otf.
+ configFonts.add(new FontConfig.Font(new File(font.getPostScriptName() + ".ttf"), null,
+ font.getStyle(), 0 /* index */,
+ FontVariationAxis.toFontVariationSettings(font.getAxes()),
+ null /* fontFamilyName */));
+ }
+ return new FontConfig.FontFamily(configFonts, postScriptName,
+ LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
+ }
+
protected FontUpdateRequest(Parcel in) {
mType = in.readInt();
mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 376503e..1ffd18f 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,11 +16,18 @@
package android.hardware;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -54,6 +61,19 @@
private static final boolean DEBUG_DYNAMIC_SENSOR = true;
private static final int MIN_DIRECT_CHANNEL_BUFFER_SIZE = 104;
private static final int MAX_LISTENER_COUNT = 128;
+ private static final int CAPPED_SAMPLING_PERIOD_US = 5000;
+ private static final int CAPPED_SAMPLING_RATE_LEVEL = SensorDirectChannel.RATE_NORMAL;
+
+ private static final String HIGH_SAMPLING_RATE_SENSORS_PERMISSION =
+ "android.permisison.HIGH_SAMPLING_RATE_SENSORS";
+ /**
+ * For apps targeting S and above, a SecurityException is thrown when they do not have
+ * HIGH_SAMPLING_RATE_SENSORS permission, run in debug mode, and request sampling rates that
+ * are faster than 200 Hz.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ static final long CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION = 136069189L;
private static native void nativeClassInit();
private static native long nativeCreate(String opPackageName);
@@ -98,6 +118,8 @@
// Looper associated with the context in which this instance was created.
private final Looper mMainLooper;
private final int mTargetSdkLevel;
+ private final boolean mIsPackageDebuggable;
+ private final boolean mHasHighSamplingRateSensorsPermission;
private final Context mContext;
private final long mNativeInstance;
@@ -111,9 +133,16 @@
}
mMainLooper = mainLooper;
- mTargetSdkLevel = context.getApplicationInfo().targetSdkVersion;
+ ApplicationInfo appInfo = context.getApplicationInfo();
+ mTargetSdkLevel = appInfo.targetSdkVersion;
mContext = context;
mNativeInstance = nativeCreate(context.getOpPackageName());
+ mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
+ PackageManager packageManager = context.getPackageManager();
+ mHasHighSamplingRateSensorsPermission =
+ (PERMISSION_GRANTED == packageManager.checkPermission(
+ HIGH_SAMPLING_RATE_SENSORS_PERMISSION,
+ appInfo.packageName));
// initialize the sensor list
for (int index = 0;; ++index) {
@@ -542,10 +571,18 @@
}
int sensorHandle = (sensor == null) ? -1 : sensor.getHandle();
+ if (Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)
+ && rate > CAPPED_SAMPLING_RATE_LEVEL
+ && mIsPackageDebuggable
+ && !mHasHighSamplingRateSensorsPermission) {
+ Compatibility.reportChange(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION);
+ throw new SecurityException("To use the sampling rate level " + rate
+ + ", app needs to declare the normal permission"
+ + " HIGH_SAMPLING_RATE_SENSORS.");
+ }
int ret = nativeConfigDirectChannel(
mNativeInstance, channel.getNativeHandle(), sensorHandle, rate);
-
if (rate == SensorDirectChannel.RATE_STOP) {
return (ret == 0) ? 1 : 0;
} else {
@@ -745,6 +782,15 @@
Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
if (mNativeSensorEventQueue == 0) throw new NullPointerException();
if (sensor == null) throw new NullPointerException();
+ if (Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)
+ && rateUs < CAPPED_SAMPLING_PERIOD_US
+ && mManager.mIsPackageDebuggable
+ && !mManager.mHasHighSamplingRateSensorsPermission) {
+ Compatibility.reportChange(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION);
+ throw new SecurityException("To use the sampling rate of " + rateUs
+ + " microseconds, app needs to declare the normal permission"
+ + " HIGH_SAMPLING_RATE_SENSORS.");
+ }
return nativeEnableSensor(mNativeSensorEventQueue, sensor.getHandle(), rateUs,
maxBatchReportLatencyUs);
}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 8bb0b184..10a814a 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -1917,9 +1917,18 @@
(3 << HAL_DATASPACE_TRANSFER_SHIFT) |
(1 << HAL_DATASPACE_RANGE_SHIFT);
- private static final int HAL_DATASPACE_DEPTH = 0x1000;
- private static final int HAL_DATASPACE_DYNAMIC_DEPTH = 0x1002;
- private static final int HAL_DATASPACE_HEIF = 0x1003;
+ /**
+ * @hide
+ */
+ public static final int HAL_DATASPACE_DEPTH = 0x1000;
+ /**
+ * @hide
+ */
+ public static final int HAL_DATASPACE_DYNAMIC_DEPTH = 0x1002;
+ /**
+ * @hide
+ */
+ public static final int HAL_DATASPACE_HEIF = 0x1003;
private static final long DURATION_20FPS_NS = 50000000L;
/**
* @see #getDurations(int, int)
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 43480ab..49beeb3 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -127,6 +127,20 @@
* This function returns RESULT_SUCCESS if the message has reached the HAL, but
* does not guarantee delivery of the message to the target nanoapp.
*
+ * Before sending the first message to your nanoapp, it's recommended that the following
+ * operations should be performed:
+ * 1) Invoke {@link ContextHubManager#queryNanoApps(ContextHubInfo)} to verify the nanoapp is
+ * present.
+ * 2) Validate that you have the permissions to communicate with the nanoapp by looking at
+ * {@link NanoAppState#getNanoAppPermissions}.
+ * 3) If you don't have permissions, send an idempotent message to the nanoapp ensuring any
+ * work your app previously may have asked it to do is stopped. This is useful if your app
+ * restarts due to permission changes and no longer has the permissions when it is started
+ * again.
+ * 4) If you have valid permissions, send a message to your nanoapp to resubscribe so that it's
+ * aware you have restarted or so you can initially subscribe if this is the first time you
+ * have sent it a message.
+ *
* @param message the message object to send
*
* @return the result of sending the message defined as in ContextHubTransaction.Result
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
index b31b85f..7e484dd 100644
--- a/core/java/android/hardware/location/ContextHubClientCallback.java
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -117,10 +117,11 @@
* 4) {@link ContextHubClient} performs any cleanup required with the nanoapp
* 5) Callback invoked with the nanoapp ID and {@link ContextHubManager#AUTHORIZATION_DENIED}.
* At this point, any further attempts of communication between the nanoapp and the
- * {@link ContextHubClient} will be dropped by the contexthub along with
- * {@link ContextHubManager#AUTHORIZATION_DENIED} being sent. The {@link ContextHubClient}
- * should assume no communciation can happen again until
- * {@link ContextHubManager#AUTHORIZATION_GRANTED} is received.
+ * {@link ContextHubClient} will be dropped by the contexthub and a return value of
+ * {@link ContextHubTransaction#RESULT_FAILED_PERMISSION_DENIED} will be used when calling
+ * {@link ContextHubClient#sendMessageToNanoApp}. The {@link ContextHubClient} should assume
+ * no communciation can happen again until {@link ContextHubManager#AUTHORIZATION_GRANTED} is
+ * received.
*
* @param client the client that is associated with this callback
* @param nanoAppId the ID of the nanoapp associated with the new
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index d11e0a9..86f77c0 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -81,7 +81,8 @@
RESULT_FAILED_AT_HUB,
RESULT_FAILED_TIMEOUT,
RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
- RESULT_FAILED_HAL_UNAVAILABLE
+ RESULT_FAILED_HAL_UNAVAILABLE,
+ RESULT_FAILED_PERMISSION_DENIED
})
public @interface Result {}
public static final int RESULT_SUCCESS = 0;
@@ -117,6 +118,11 @@
* Failure mode when the Context Hub HAL was not available.
*/
public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8;
+ /**
+ * Failure mode when the user of the API doesn't have the required permissions to perform the
+ * operation.
+ */
+ public static final int RESULT_FAILED_PERMISSION_DENIED = 9;
/**
* A class describing the response for a ContextHubTransaction.
diff --git a/core/java/android/hardware/location/OWNERS b/core/java/android/hardware/location/OWNERS
index 383321b..bd40409 100644
--- a/core/java/android/hardware/location/OWNERS
+++ b/core/java/android/hardware/location/OWNERS
@@ -4,3 +4,6 @@
wyattriley@google.com
etn@google.com
weiwa@google.com
+
+# ContextHub team
+per-file *ContextHub*,*NanoApp* = file:platform/system/chre:/OWNERS
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index ca79901..7f07af7 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -135,6 +135,12 @@
/* Resets the USB gadget. */
void resetUsbGadget();
+ /* Set USB data on or off */
+ boolean enableUsbDataSignal(boolean enable);
+
+ /* Gets the USB Hal Version. */
+ int getUsbHalVersion();
+
/* Get the functionfs control handle for the given function. Usb
* descriptors will already be written, and the handle will be
* ready to use.
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 5bac481..841fd2f 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -516,6 +516,46 @@
public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024;
/**
+ * The Value for USB hal is not presented.
+ *
+ * {@hide}
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int USB_HAL_NOT_SUPPORTED = -1;
+
+ /**
+ * Value for USB Hal Version v1.0.
+ *
+ * {@hide}
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int USB_HAL_V1_0 = 10;
+
+ /**
+ * Value for USB Hal Version v1.1.
+ *
+ * {@hide}
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int USB_HAL_V1_1 = 11;
+
+ /**
+ * Value for USB Hal Version v1.2.
+ *
+ * {@hide}
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int USB_HAL_V1_2 = 12;
+
+ /**
+ * Value for USB Hal Version v1.3.
+ *
+ * {@hide}
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int USB_HAL_V1_3 = 13;
+
+ /**
* Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
* {@hide}
*/
@@ -617,6 +657,16 @@
})
public @interface UsbGadgetHalVersion {}
+ /** @hide */
+ @IntDef(prefix = { "USB_HAL_" }, value = {
+ USB_HAL_NOT_SUPPORTED,
+ USB_HAL_V1_0,
+ USB_HAL_V1_1,
+ USB_HAL_V1_2,
+ USB_HAL_V1_3,
+ })
+ public @interface UsbHalVersion {}
+
private final Context mContext;
private final IUsbManager mService;
@@ -1076,6 +1126,26 @@
}
/**
+ * Get the Current USB Hal Version.
+ * <p>
+ * This function returns the current USB Hal Version.
+ * </p>
+ *
+ * @return a integer {@code USB_HAL_*} represent hal version.
+ *
+ * {@hide}
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
+ public @UsbHalVersion int getUsbHalVersion() {
+ try {
+ return mService.getUsbHalVersion();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Resets the USB Gadget.
* <p>
* Performs USB data stack reset through USB Gadget HAL.
@@ -1095,6 +1165,28 @@
}
/**
+ * Enable/Disable the USB data signaling.
+ * <p>
+ * Enables/Disables USB data path in all the USB ports.
+ * It will force to stop or restore USB data signaling.
+ * </p>
+ *
+ * @param enable enable or disable USB data signaling
+ * @return true enable or disable USB data successfully
+ * false if something wrong
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
+ public boolean enableUsbDataSignal(boolean enable) {
+ try {
+ return mService.enableUsbDataSignal(enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns a list of physical USB ports on the device.
* <p>
* This list is guaranteed to contain all dual-role USB Type C ports but it might
diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java
index 3bc0f9c..b172ccc 100644
--- a/core/java/android/net/UidRange.java
+++ b/core/java/android/net/UidRange.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import java.util.Collection;
@@ -45,6 +46,14 @@
return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
}
+ /** Creates a UidRange for the specified user. */
+ public static UidRange createForUser(UserHandle user) {
+ final UserHandle nextUser = UserHandle.of(user.getIdentifier() + 1);
+ final int start = UserHandle.getUid(user, 0 /* appId */);
+ final int end = UserHandle.getUid(nextUser, 0) - 1;
+ return new UidRange(start, end);
+ }
+
/** Returns the smallest user Id which is contained in this UidRange */
public int getStartUser() {
return start / PER_USER_RANGE;
diff --git a/packages/Connectivity/framework/src/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/VpnManager.java
rename to core/java/android/net/VpnManager.java
diff --git a/packages/Connectivity/framework/src/android/net/VpnService.java b/core/java/android/net/VpnService.java
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/VpnService.java
rename to core/java/android/net/VpnService.java
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index eac03dc..e3b13f4 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -2566,6 +2566,14 @@
public static native long getDmabufTotalExportedKb();
/**
+ * Return total memory size in kilobytes for DMA-BUFs exported from the DMA-BUF
+ * heaps frameworks or -1 in the case of an error.
+ *
+ * @hide
+ */
+ public static native long getDmabufHeapTotalExportedKb();
+
+ /**
* Return memory size in kilobytes allocated for ION heaps or -1 if
* /sys/kernel/ion/total_heaps_kb could not be read.
*
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index 52f0ce1..4d160da 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -35,4 +35,9 @@
* @see SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries
*/
Map getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries();
+
+ /**
+ * @see SystemConfigManager#getSystemPermissionUids
+ */
+ int[] getSystemPermissionUids(String permissionName);
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 059e932..3774fb5 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1898,7 +1898,8 @@
* These estimates will be displayed on system UI surfaces in place of the system computed
* value.
*
- * Calling this requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+ * Calling this requires either the {@link android.Manifest.permission#DEVICE_POWER} or the
+ * {@link android.Manifest.permission#BATTERY_PREDICTION} permissions.
*
* @param timeRemaining The time remaining as a {@link Duration}.
* @param isPersonalized true if personalized based on device usage history, false otherwise.
@@ -1906,7 +1907,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.BATTERY_PREDICTION,
+ android.Manifest.permission.DEVICE_POWER
+ })
public void setBatteryDischargePrediction(@NonNull Duration timeRemaining,
boolean isPersonalized) {
if (timeRemaining == null) {
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index 3f0632b..9bfa8ad 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -111,4 +111,22 @@
return Collections.emptyMap();
}
}
+
+ /**
+ * Get uids which have been granted given permission in system configuration.
+ *
+ * The uids and assigning permissions are defined on data/etc/platform.xml
+ *
+ * @param permissionName The target permission.
+ * @return The uids have been granted given permission in system configuration.
+ */
+ @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
+ @NonNull
+ public int[] getSystemPermissionUids(@NonNull String permissionName) {
+ try {
+ return mInterface.getSystemPermissionUids(permissionName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef81ed7..e979e13 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1902,6 +1902,18 @@
public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID";
/**
+ * Activity Extra: An {@code Arraylist<String>} of {@link NotificationChannel} field names to
+ * show on the Settings UI.
+ *
+ * <p>
+ * This is an optional extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}. If
+ * included the system will filter out any Settings that doesn't appear in this list that
+ * otherwise would display.
+ */
+ public static final String EXTRA_CHANNEL_FILTER_LIST
+ = "android.provider.extra.CHANNEL_FILTER_LIST";
+
+ /**
* Activity Action: Show notification redaction settings.
*
* @hide
@@ -10780,6 +10792,13 @@
"location_ignore_settings_package_whitelist";
/**
+ * Whether to throttle location when the device is in doze and still.
+ * @hide
+ */
+ public static final String LOCATION_ENABLE_STATIONARY_THROTTLE =
+ "location_enable_stationary_throttle";
+
+ /**
* Whether TV will switch to MHL port when a mobile device is plugged in.
* (0 = false, 1 = true)
* @hide
diff --git a/core/java/android/provider/SimPhonebookContract.java b/core/java/android/provider/SimPhonebookContract.java
index 2efc212..f3a7856 100644
--- a/core/java/android/provider/SimPhonebookContract.java
+++ b/core/java/android/provider/SimPhonebookContract.java
@@ -29,11 +29,8 @@
import android.annotation.WorkerThread;
import android.content.ContentResolver;
import android.content.ContentValues;
-import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
@@ -63,7 +60,6 @@
*
* @hide
*/
- @SystemApi
public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
private SimPhonebookContract() {
@@ -76,7 +72,6 @@
* @hide
*/
@NonNull
- @SystemApi
public static String getEfUriPath(@ElementaryFiles.EfType int efType) {
switch (efType) {
case EF_ADN:
@@ -122,12 +117,12 @@
* The name for this record.
*
* <p>An {@link IllegalArgumentException} will be thrown by insert and update if this
- * exceeds the maximum supported length or contains unsupported characters.
- * {@link #validateName(ContentResolver, int, int, String)} )} can be used to
- * check whether the name is supported.
+ * exceeds the maximum supported length. Use
+ * {@link #getEncodedNameLength(ContentResolver, String)} to check how long the name
+ * will be after encoding.
*
* @see ElementaryFiles#NAME_MAX_LENGTH
- * @see #validateName(ContentResolver, int, int, String) )
+ * @see #getEncodedNameLength(ContentResolver, String)
*/
public static final String NAME = "name";
/**
@@ -149,24 +144,31 @@
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
/**
- * The path segment that is appended to {@link #getContentUri(int, int)} which indicates
- * that the following path segment contains a name to be validated.
- *
- * @hide
- * @see #validateName(ContentResolver, int, int, String)
+ * Value returned from {@link #getEncodedNameLength(ContentResolver, String)} when the name
+ * length could not be determined because the name could not be encoded.
*/
- @SystemApi
- public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
+ public static final int ERROR_NAME_UNSUPPORTED = -1;
/**
- * The key for a cursor extra that contains the result of a validate name query.
+ * The method name used to get the encoded length of a value for {@link SimRecords#NAME}
+ * column.
*
* @hide
- * @see #validateName(ContentResolver, int, int, String)
+ * @see #getEncodedNameLength(ContentResolver, String)
+ * @see ContentResolver#call(String, String, String, Bundle)
*/
- @SystemApi
- public static final String EXTRA_NAME_VALIDATION_RESULT =
- "android.provider.extra.NAME_VALIDATION_RESULT";
+ public static final String GET_ENCODED_NAME_LENGTH_METHOD_NAME = "get_encoded_name_length";
+
+ /**
+ * Extra key used for an integer value that contains the length in bytes of an encoded
+ * name.
+ *
+ * @hide
+ * @see #getEncodedNameLength(ContentResolver, String)
+ * @see #GET_ENCODED_NAME_LENGTH_METHOD_NAME
+ */
+ public static final String EXTRA_ENCODED_NAME_LENGTH =
+ "android.provider.extra.ENCODED_NAME_LENGTH";
/**
@@ -244,32 +246,34 @@
}
/**
- * Validates a value that is being provided for the {@link #NAME} column.
+ * Returns the number of bytes required to encode the specified name when it is stored
+ * on the SIM.
*
- * <p>The return value can be used to check if the name is valid. If it is not valid then
- * inserts and updates to the specified elementary file that use the provided name value
- * will throw an {@link IllegalArgumentException}.
+ * <p>{@link ElementaryFiles#NAME_MAX_LENGTH} is specified in bytes but the encoded name
+ * may require more than 1 byte per character depending on the characters it contains. So
+ * this method can be used to check whether a name exceeds the max length.
*
- * <p>If the specified SIM or elementary file don't exist then
- * {@link NameValidationResult#getMaxEncodedLength()} will be zero and
- * {@link NameValidationResult#isValid()} will return false.
+ * @return the number of bytes required by the encoded name or
+ * {@link #ERROR_NAME_UNSUPPORTED} if the name could not be encoded.
+ * @throws IllegalStateException if the provider fails to return the length.
+ * @see SimRecords#NAME
+ * @see ElementaryFiles#NAME_MAX_LENGTH
*/
- @NonNull
@WorkerThread
- public static NameValidationResult validateName(
- @NonNull ContentResolver resolver, int subscriptionId,
- @ElementaryFiles.EfType int efType,
- @NonNull String name) {
- Bundle queryArgs = new Bundle();
- queryArgs.putString(SimRecords.NAME, name);
- try (Cursor cursor =
- resolver.query(buildContentUri(subscriptionId, efType)
- .appendPath(VALIDATE_NAME_PATH_SEGMENT)
- .build(), null, queryArgs, null)) {
- NameValidationResult result = cursor.getExtras()
- .getParcelable(EXTRA_NAME_VALIDATION_RESULT);
- return result != null ? result : new NameValidationResult(name, "", 0, 0);
+ public static int getEncodedNameLength(
+ @NonNull ContentResolver resolver, @NonNull String name) {
+ name = Objects.requireNonNull(name);
+ Bundle result = resolver.call(AUTHORITY, GET_ENCODED_NAME_LENGTH_METHOD_NAME, name,
+ null);
+ if (result == null || !result.containsKey(EXTRA_ENCODED_NAME_LENGTH)) {
+ throw new IllegalStateException("Provider malfunction: no length was returned.");
}
+ int length = result.getInt(EXTRA_ENCODED_NAME_LENGTH, ERROR_NAME_UNSUPPORTED);
+ if (length < 0 && length != ERROR_NAME_UNSUPPORTED) {
+ throw new IllegalStateException(
+ "Provider malfunction: invalid length was returned.");
+ }
+ return length;
}
private static Uri.Builder buildContentUri(
@@ -281,106 +285,6 @@
.appendPath(getEfUriPath(efType));
}
- /** Contains details about the validity of a value provided for the {@link #NAME} column. */
- public static final class NameValidationResult implements Parcelable {
-
- @NonNull
- public static final Creator<NameValidationResult> CREATOR =
- new Creator<NameValidationResult>() {
-
- @Override
- public NameValidationResult createFromParcel(@NonNull Parcel in) {
- return new NameValidationResult(in);
- }
-
- @NonNull
- @Override
- public NameValidationResult[] newArray(int size) {
- return new NameValidationResult[size];
- }
- };
-
- private final String mName;
- private final String mSanitizedName;
- private final int mEncodedLength;
- private final int mMaxEncodedLength;
-
- /** Creates a new instance from the provided values. */
- public NameValidationResult(@NonNull String name, @NonNull String sanitizedName,
- int encodedLength, int maxEncodedLength) {
- this.mName = Objects.requireNonNull(name);
- this.mSanitizedName = Objects.requireNonNull(sanitizedName);
- this.mEncodedLength = encodedLength;
- this.mMaxEncodedLength = maxEncodedLength;
- }
-
- private NameValidationResult(Parcel in) {
- this(in.readString(), in.readString(), in.readInt(), in.readInt());
- }
-
- /** Returns the original name that is being validated. */
- @NonNull
- public String getName() {
- return mName;
- }
-
- /**
- * Returns a sanitized copy of the original name with all unsupported characters
- * replaced with spaces.
- */
- @NonNull
- public String getSanitizedName() {
- return mSanitizedName;
- }
-
- /**
- * Returns whether the original name isValid.
- *
- * <p>If this returns false then inserts and updates using the name will throw an
- * {@link IllegalArgumentException}
- */
- public boolean isValid() {
- return mMaxEncodedLength > 0 && mEncodedLength <= mMaxEncodedLength
- && Objects.equals(
- mName, mSanitizedName);
- }
-
- /** Returns whether the character at the specified position is supported by the SIM. */
- public boolean isSupportedCharacter(int position) {
- return mName.charAt(position) == mSanitizedName.charAt(position);
- }
-
- /**
- * Returns the number of bytes required to save the name.
- *
- * <p>This may be more than the number of characters in the name.
- */
- public int getEncodedLength() {
- return mEncodedLength;
- }
-
- /**
- * Returns the maximum number of bytes that are supported for the name.
- *
- * @see ElementaryFiles#NAME_MAX_LENGTH
- */
- public int getMaxEncodedLength() {
- return mMaxEncodedLength;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mName);
- dest.writeString(mSanitizedName);
- dest.writeInt(mEncodedLength);
- dest.writeInt(mMaxEncodedLength);
- }
- }
}
/** Constants for metadata about the elementary files of the SIM cards in the phone. */
@@ -446,13 +350,10 @@
*/
public static final int EF_SDN = 3;
/** @hide */
- @SystemApi
public static final String EF_ADN_PATH_SEGMENT = "adn";
/** @hide */
- @SystemApi
public static final String EF_FDN_PATH_SEGMENT = "fdn";
/** @hide */
- @SystemApi
public static final String EF_SDN_PATH_SEGMENT = "sdn";
/** The MIME type of CONTENT_URI providing a directory of ADN-like elementary files. */
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file";
@@ -464,7 +365,6 @@
*
* @hide
*/
- @SystemApi
public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
/** Content URI for the ADN-like elementary files available on the device. */
@@ -480,8 +380,7 @@
* Returns a content uri for a specific elementary file.
*
* <p>If a SIM with the specified subscriptionId is not present an exception will be thrown.
- * If the SIM doesn't support the specified elementary file it will have a zero value for
- * {@link #MAX_RECORDS}.
+ * If the SIM doesn't support the specified elementary file it will return an empty cursor.
*/
@NonNull
public static Uri getItemUri(int subscriptionId, @EfType int efType) {
diff --git a/core/java/android/service/wallpaper/EngineWindowPage.java b/core/java/android/service/wallpaper/EngineWindowPage.java
new file mode 100644
index 0000000..9d2e5b9
--- /dev/null
+++ b/core/java/android/service/wallpaper/EngineWindowPage.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 android.service.wallpaper;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * This class represents a page of a launcher page used by the wallpaper
+ * @hide
+ */
+public class EngineWindowPage {
+ private Bitmap mScreenShot;
+ private volatile long mLastUpdateTime;
+ private Set<RectF> mCallbackAreas = new ArraySet<>();
+ private Map<RectF, WallpaperColors> mRectFColors = new ArrayMap<>();
+
+ /** should be locked extrnally */
+ public void addArea(RectF area) {
+ mCallbackAreas.add(area);
+ }
+
+ /** should be locked extrnally */
+ public void addWallpaperColors(RectF area, WallpaperColors colors) {
+ mCallbackAreas.add(area);
+ mRectFColors.put(area, colors);
+ }
+
+ /** get screenshot bitmap */
+ public Bitmap getBitmap() {
+ if (mScreenShot == null || mScreenShot.isRecycled()) return null;
+ return mScreenShot;
+ }
+
+ /** remove callbacks for an area */
+ public void removeArea(RectF area) {
+ mCallbackAreas.remove(area);
+ mRectFColors.remove(area);
+ }
+
+ /** set the last time the screenshot was updated */
+ public void setLastUpdateTime(long lastUpdateTime) {
+ mLastUpdateTime = lastUpdateTime;
+ }
+
+ /** get last screenshot time */
+ public long getLastUpdateTime() {
+ return mLastUpdateTime;
+ }
+
+ /** get colors for an area */
+ public WallpaperColors getColors(RectF rect) {
+ return mRectFColors.get(rect);
+ }
+
+ /** set the new bitmap version */
+ public void setBitmap(Bitmap screenShot) {
+ mScreenShot = screenShot;
+ }
+
+ /** get areas of interest */
+ public Set<RectF> getAreas() {
+ return mCallbackAreas;
+ }
+
+ /** run operations on this page */
+ public synchronized void execSync(Consumer<EngineWindowPage> run) {
+ run.accept(this);
+ }
+
+ /** nullify the area color */
+ public void removeColor(RectF colorArea) {
+ mRectFColors.remove(colorArea);
+ }
+}
diff --git a/core/java/android/service/wallpaper/IWallpaperConnection.aidl b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
index f334d9d..f81ed34 100644
--- a/core/java/android/service/wallpaper/IWallpaperConnection.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
@@ -16,6 +16,7 @@
package android.service.wallpaper;
+import android.graphics.RectF;
import android.os.ParcelFileDescriptor;
import android.service.wallpaper.IWallpaperEngine;
import android.app.WallpaperColors;
@@ -28,4 +29,5 @@
void engineShown(IWallpaperEngine engine);
ParcelFileDescriptor setWallpaper(String name);
void onWallpaperColorsChanged(in WallpaperColors colors, int displayId);
+ void onLocalWallpaperColorsChanged(in RectF area, in WallpaperColors colors, int displayId);
}
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 90392e6..fbb449d 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -16,7 +16,10 @@
package android.service.wallpaper;
+import android.app.ILocalWallpaperColorConsumer;
+import android.app.WallpaperColors;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.view.MotionEvent;
import android.os.Bundle;
@@ -39,4 +42,6 @@
void destroy();
void setZoomOut(float scale);
void scalePreview(in Rect positionInWindow);
+ void removeLocalColorsAreas(in List<RectF> regions);
+ void addLocalColorsAreas(in List<RectF> regions);
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 82e0b4a..0b447d5 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -23,6 +23,7 @@
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import android.annotation.FloatRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -42,6 +43,7 @@
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
@@ -53,6 +55,8 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.Trace;
+import android.util.ArraySet;
import android.util.Log;
import android.util.MergedConfiguration;
import android.view.Display;
@@ -66,6 +70,8 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.MotionEvent;
+import android.view.PixelCopy;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.View;
@@ -83,7 +89,10 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
/**
@@ -115,7 +124,12 @@
public static final String SERVICE_META_DATA = "android.service.wallpaper";
static final String TAG = "WallpaperService";
- static final boolean DEBUG = false;
+ static final boolean DEBUG = true;
+ static final float MIN_PAGE_ALLOWED_MARGIN = .05f;
+ private static final int MIN_BITMAP_SCREENSHOT_WIDTH = 64;
+ private static final long DEFAULT_UPDATE_SCREENSHOT_DURATION = 60 * 1000; //Once per minute
+ private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+ new RectF(0, 0, 1, 1);
private static final int DO_ATTACH = 10;
private static final int DO_DETACH = 20;
@@ -134,6 +148,8 @@
private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
private static final int MSG_ZOOM = 10100;
private static final int MSG_SCALE_PREVIEW = 10110;
+ private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
+ Float.NEGATIVE_INFINITY);
private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
@@ -158,6 +174,14 @@
*/
public class Engine {
IWallpaperEngineWrapper mIWallpaperEngine;
+ final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
+ final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
+
+ // 2D matrix [x][y] to represent a page of a portion of a window
+ EngineWindowPage[] mWindowPages = new EngineWindowPage[1];
+ Bitmap mLastScreenshot;
+ int mLastWindowPage = -1;
+ float mLastPageOffset = 0;
// Copies from mIWallpaperEngine.
HandlerCaller mCaller;
@@ -409,8 +433,8 @@
*/
@VisibleForTesting
public Engine(Supplier<Long> clockFunction, Handler handler) {
- mClockFunction = clockFunction;
- mHandler = handler;
+ mClockFunction = clockFunction;
+ mHandler = handler;
}
/**
@@ -448,6 +472,19 @@
}
/**
+ * Return whether the wallpaper is capable of extracting local colors in a rectangle area,
+ * Must implement without calling super:
+ * {@link #addLocalColorsAreas(List)}
+ * {@link #removeLocalColorsAreas(List)}
+ * When local colors change, call {@link #notifyLocalColorsChanged(List, List)}
+ * See {@link com.android.systemui.ImageWallpaper} for an example
+ * @hide
+ */
+ public boolean supportsLocalColorExtraction() {
+ return false;
+ }
+
+ /**
* Returns true if this engine is running in preview mode -- that is,
* it is being shown to the user before they select it as the actual
* wallpaper.
@@ -691,6 +728,8 @@
Log.w(TAG, "Can't notify system because wallpaper connection "
+ "was not established.");
}
+ resetWindowPages();
+ processLocalColors(mPendingXOffset, mPendingXOffsetStep);
} catch (RemoteException e) {
Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e);
}
@@ -714,6 +753,28 @@
}
/**
+ * Send the changed local color areas for the connection
+ * @param regions
+ * @param colors
+ * @hide
+ */
+ public void notifyLocalColorsChanged(@NonNull List<RectF> regions,
+ @NonNull List<WallpaperColors> colors)
+ throws RuntimeException {
+ for (int i = 0; i < regions.size() && i < colors.size() && colors.get(i) != null; i++) {
+ try {
+ mConnection.onLocalWallpaperColorsChanged(
+ regions.get(i),
+ colors.get(i),
+ mDisplayContext.getDisplayId()
+ );
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
* Sets internal engine state. Only for testing.
* @param created {@code true} or {@code false}.
* @hide
@@ -1068,7 +1129,9 @@
mIsCreating = false;
mSurfaceCreated = true;
if (redrawNeeded) {
+ resetWindowPages();
mSession.finishDrawing(mWindow, null /* postDrawTransaction */);
+ processLocalColors(mPendingXOffset, mPendingXOffsetStep);
}
reposition();
mIWallpaperEngine.reportShown();
@@ -1209,6 +1272,7 @@
if (!mDestroyed) {
mVisible = visible;
reportVisibility();
+ if (visible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
}
}
@@ -1278,6 +1342,405 @@
} catch (RemoteException e) {
}
}
+
+ // setup local color extraction data
+ processLocalColors(xOffset, xOffsetStep);
+ }
+
+ private void processLocalColors(float xOffset, float xOffsetStep) {
+ // implemented by the wallpaper
+ if (supportsLocalColorExtraction()) return;
+ if (DEBUG) {
+ Log.d(TAG, "processLocalColors " + xOffset + " of step "
+ + xOffsetStep);
+ }
+ //below is the default implementation
+ if (!validStep(xOffsetStep)) {
+ if (DEBUG) {
+ Log.w(TAG, "invalid offset step " + xOffsetStep);
+ }
+ return;
+ }
+
+ int xPage = Math.round(xOffset / xOffsetStep);
+ if (!shouldProcessPage(xPage, xOffset, xOffsetStep)) return;
+ mLastWindowPage = xPage;
+ mLastPageOffset = xOffset;
+ int xPages = Math.round(1 / xOffsetStep);
+ if (DEBUG) {
+ Log.d(TAG, "xPages " + xPages + " xPage " + xPage);
+ Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+ }
+ EngineWindowPage current;
+ synchronized (mLock) {
+ if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
+ mWindowPages = new EngineWindowPage[xPages];
+ initWindowPages(mWindowPages, xOffsetStep);
+ }
+ if (mLocalColorsToAdd.size() != 0) {
+ for (RectF colorArea : mLocalColorsToAdd) {
+ if (!isValid(colorArea)) continue;
+ mLocalColorAreas.add(colorArea);
+ int colorPage = getRectFPage(colorArea, xOffsetStep);
+ EngineWindowPage currentPage = mWindowPages[colorPage];
+ if (currentPage == null) {
+ currentPage = new EngineWindowPage();
+ currentPage.addArea(colorArea);
+ mWindowPages[colorPage] = currentPage;
+ } else {
+ currentPage.setLastUpdateTime(0);
+ currentPage.removeColor(colorArea);
+ }
+ }
+ mLocalColorsToAdd.clear();
+ }
+ if (xPage >= mWindowPages.length) {
+ if (DEBUG) {
+ Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage);
+ Log.e(TAG, "error on page " + xPage + " out of " + xPages);
+ Log.e(TAG,
+ "error on xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+ }
+ xPage = mWindowPages.length - 1;
+ }
+ current = mWindowPages[xPage];
+ if (current == null) {
+ if (DEBUG) Log.d(TAG, "making page " + xPage + " out of " + xPages);
+ if (DEBUG) {
+ Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+ }
+ current = new EngineWindowPage();
+ mWindowPages[xPage] = current;
+ }
+ }
+ updatePage(current, xPage, xPages, xOffsetStep);
+ }
+
+ private boolean shouldProcessPage(int xPage, float xOffset, float xOffsetStep) {
+ float pageCenter = xOffsetStep * xPage + xOffsetStep / 2;
+
+ return (pageCenter > xOffset && pageCenter < mLastPageOffset)
+ || (pageCenter < xOffset && pageCenter > mLastPageOffset);
+ }
+
+ private void initWindowPages(EngineWindowPage[] windowPages, float step) {
+ for (int i = 0; i < windowPages.length; i++) {
+ windowPages[i] = new EngineWindowPage();
+ }
+ mLocalColorAreas.addAll(mLocalColorsToAdd);
+ mLocalColorsToAdd.clear();
+ for (RectF area: mLocalColorAreas) {
+ if (!isValid(area)) {
+ mLocalColorAreas.remove(area);
+ continue;
+ }
+ int pageNum = getRectFPage(area, step);
+ windowPages[pageNum].addArea(area);
+ }
+ }
+
+ void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
+ float xOffsetStep) {
+ // to save creating a runnable, check twice
+ long current = System.nanoTime() / 1_000_000;
+ AtomicLong lapsed = new AtomicLong(current - currentPage.getLastUpdateTime());
+ if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
+ currentPage.execSync((page) -> {
+ // just in case of race double check
+ long currentInner = System.nanoTime() / 1_000_000;
+ lapsed.set(currentInner - page.getLastUpdateTime());
+ page.setLastUpdateTime(currentInner);
+ });
+ long lastUpdate = currentPage.getLastUpdateTime();
+ if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
+ Surface surface = mSurfaceHolder.getSurface();
+ boolean widthIsLarger =
+ mSurfaceControl.getWidth() > mSurfaceControl.getHeight();
+ int smaller = widthIsLarger ? mSurfaceControl.getWidth()
+ : mSurfaceControl.getHeight();
+ float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller;
+ int width = (int) (ratio * mSurfaceControl.getWidth());
+ int height = (int) (ratio * mSurfaceControl.getHeight());
+ if (width <= 0 || height <= 0) {
+ return;
+ }
+ Bitmap screenShot = Bitmap.createBitmap(width, height,
+ Bitmap.Config.ARGB_8888);
+ final Bitmap finalScreenShot = screenShot;
+ Trace.beginSection("WallpaperService#pixelCopy");
+ PixelCopy.request(surface, screenShot, (res) -> {
+ Trace.endSection();
+ if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
+ if (res != PixelCopy.SUCCESS) {
+ currentPage.execSync((p) -> {
+ // reset the time
+ p.setLastUpdateTime(lastUpdate);
+ // assign the last bitmap taken for now
+ p.setBitmap(mLastScreenshot);
+ });
+ return;
+ }
+ mLastScreenshot = finalScreenShot;
+ // going to hold this lock for a while
+ currentPage.execSync((p) -> {
+ p.setBitmap(finalScreenShot);
+ });
+ updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
+ }, mHandler);
+
+ }
+ // locked by the passed page
+ private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages,
+ float xOffsetStep) {
+ if (page.getBitmap() == null) return;
+ if (DEBUG) {
+ Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas "
+ + page.getAreas().size() + " and bitmap size of "
+ + page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight());
+ }
+ for (RectF area: page.getAreas()) {
+ RectF subArea = generateSubRect(area, pageIndx, numPages);
+ Bitmap b = page.getBitmap();
+ int x = Math.round(b.getWidth() * subArea.left);
+ int y = Math.round(b.getHeight() * subArea.top);
+ int width = Math.round(b.getWidth() * subArea.width());
+ int height = Math.round(b.getHeight() * subArea.height());
+ Bitmap target;
+ try {
+ target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height);
+ } catch (Exception e) {
+ Log.e(TAG, "Error creating page local color bitmap", e);
+ continue;
+ }
+ WallpaperColors color = WallpaperColors.fromBitmap(target);
+ target.recycle();
+ WallpaperColors currentColor = page.getColors(area);
+
+ if (DEBUG) {
+ Log.d(TAG, "getting local bitmap area x " + x + " y " + y
+ + " width " + width + " height " + height + " for sub area " + subArea
+ + " and with page " + pageIndx + " of " + numPages);
+
+ }
+ if (currentColor == null || !color.equals(currentColor)) {
+ page.addWallpaperColors(area, color);
+ if (DEBUG) {
+ Log.d(TAG, "onLocalWallpaperColorsChanged"
+ + " local color callback for area" + area + " for page " + pageIndx
+ + " of " + numPages);
+ }
+ try {
+ mConnection.onLocalWallpaperColorsChanged(area, color,
+ mDisplayContext.getDisplayId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
+ }
+ }
+ }
+ }
+
+ private RectF generateSubRect(RectF in, int pageInx, int numPages) {
+ float minLeft = (float) (pageInx) / (float) (numPages);
+ float maxRight = (float) (pageInx + 1) / (float) (numPages);
+ float left = in.left;
+ float right = in.right;
+
+ // bound rect
+ if (left < minLeft) left = minLeft;
+ if (right > maxRight) right = maxRight;
+
+ // scale up the sub area then trim
+ left = (left * (float) numPages) % 1f;
+ right = (right * (float) numPages) % 1f;
+ if (right == 0f) {
+ right = 1f;
+ }
+
+ return new RectF(left, in.top, right, in.bottom);
+ }
+
+ private void resetWindowPages() {
+ if (supportsLocalColorExtraction()) return;
+ mLastWindowPage = -1;
+ synchronized (mLock) {
+ for (int i = 0; i < mWindowPages.length; i++) {
+ EngineWindowPage page = mWindowPages[i];
+ if (page != null) {
+ page.execSync((p) -> {
+ p.setLastUpdateTime(0L);
+ });
+ }
+ }
+ }
+ }
+
+ private int getRectFPage(RectF area, float step) {
+ if (!isValid(area)) return 0;
+ if (!validStep(step)) return 0;
+ int pages = Math.round(1 / step);
+ int page = Math.round(area.centerX() * pages);
+ if (page == pages) return pages - 1;
+ if (page == mWindowPages.length) page = mWindowPages.length - 1;
+ return page;
+ }
+
+ /**
+ * Add local colors areas of interest
+ * @param regions list of areas
+ * @hide
+ */
+ public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+ if (supportsLocalColorExtraction()) return;
+ if (DEBUG) {
+ Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions);
+ }
+ float step = mPendingXOffsetStep;
+
+ List<WallpaperColors> colors = getLocalWallpaperColors(regions);
+ synchronized (mLock) {
+ if (!validStep(step)) {
+ mLocalColorsToAdd.addAll(regions);
+ return;
+ }
+ for (int i = 0; i < regions.size(); i++) {
+ RectF area = regions.get(i);
+ if (!isValid(area)) continue;
+ int pageInx = getRectFPage(area, step);
+ // no page should be null
+ EngineWindowPage page = mWindowPages[pageInx];
+
+ if (page != null) {
+ mLocalColorAreas.add(area);
+ page.addArea(area);
+ WallpaperColors color = colors.get(i);
+ if (color != null && !color.equals(page.getColors(area))) {
+ page.execSync(p -> {
+ p.addWallpaperColors(area, color);
+ });
+ }
+ } else {
+ mLocalColorsToAdd.add(area);
+ }
+ }
+ }
+
+ for (int i = 0; i < colors.size() && colors.get(i) != null; i++) {
+ try {
+ mConnection.onLocalWallpaperColorsChanged(regions.get(i), colors.get(i),
+ mDisplayContext.getDisplayId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Remove local colors areas of interest if they exist
+ * @param regions list of areas
+ * @hide
+ */
+ public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
+ if (supportsLocalColorExtraction()) return;
+ synchronized (mLock) {
+ float step = mPendingXOffsetStep;
+ mLocalColorsToAdd.removeAll(regions);
+ mLocalColorAreas.removeAll(regions);
+ if (!validStep(step)) {
+ return;
+ }
+ for (int i = 0; i < regions.size(); i++) {
+ RectF area = regions.get(i);
+ if (!isValid(area)) continue;
+ int pageInx = getRectFPage(area, step);
+ // no page should be null
+ EngineWindowPage page = mWindowPages[pageInx];
+ if (page != null) {
+ page.execSync(p -> {
+ p.removeArea(area);
+ });
+ }
+ }
+ }
+ }
+
+ private @NonNull List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas) {
+ ArrayList<WallpaperColors> colors = new ArrayList<>(areas.size());
+ float step = mPendingXOffsetStep;
+ if (!validStep(step)) {
+ if (DEBUG) Log.d(TAG, "invalid step size " + step);
+ step = 1.0f;
+ }
+ for (int i = 0; i < areas.size(); i++) {
+ RectF currentArea = areas.get(i);
+ EngineWindowPage page;
+ RectF area;
+ int pageIndx;
+ synchronized (mLock) {
+ pageIndx = getRectFPage(currentArea, step);
+ if (mWindowPages.length == 0 || pageIndx < 0
+ || pageIndx > mWindowPages.length || !isValid(currentArea)) {
+ colors.add(null);
+ continue;
+ }
+ area = generateSubRect(currentArea, pageIndx, mWindowPages.length);
+ page = mWindowPages[pageIndx];
+ }
+ if (page == null) {
+ colors.add(null);
+ continue;
+ }
+ float finalStep = step;
+ int finalPageIndx = pageIndx;
+ Bitmap screenShot = page.getBitmap();
+ if (screenShot == null || screenShot.isRecycled()) {
+ if (DEBUG) {
+ Log.d(TAG, "invalid bitmap " + screenShot
+ + " for page " + finalPageIndx);
+ }
+ page.setLastUpdateTime(0);
+ colors.add(null);
+ continue;
+ }
+ Bitmap b = screenShot;
+ Rect subImage = new Rect(
+ Math.round(area.left * b.getWidth() / finalStep),
+ Math.round(area.top * b.getHeight()),
+ Math.round(area.right * b.getWidth() / finalStep),
+ Math.round(area.bottom * b.getHeight())
+ );
+ subImage = fixRect(b, subImage);
+ if (DEBUG) {
+ Log.d(TAG, "getting subbitmap of " + subImage.toString()
+ + " for RectF " + area.toString()
+ + " screenshot width " + screenShot.getWidth() + " height "
+ + screenShot.getHeight());
+ }
+ Bitmap colorImg = Bitmap.createBitmap(screenShot,
+ subImage.left, subImage.top, subImage.width(), subImage.height());
+ if (DEBUG) {
+ Log.d(TAG, "created bitmap " + colorImg.getWidth() + ", "
+ + colorImg.getHeight());
+ }
+ WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
+ colors.add(color);
+ }
+ return colors;
+ }
+
+ // fix the rect to be included within the bounds of the bitmap
+ private Rect fixRect(Bitmap b, Rect r) {
+ r.left = r.left >= r.right || r.left >= b.getWidth() || r.left > 0
+ ? 0
+ : r.left;
+ r.right = r.left >= r.right || r.right > b.getWidth()
+ ? b.getWidth()
+ : r.right;
+ return r;
+ }
+
+ private boolean validStep(float step) {
+ return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.;
}
void doCommand(WallpaperCommand cmd) {
@@ -1371,6 +1834,16 @@
};
}
+ private boolean isValid(RectF area) {
+ boolean valid = area.bottom > area.top && area.left < area.right
+ && LOCAL_COLOR_BOUNDS.contains(area);
+ return valid;
+ }
+
+ private boolean inRectFRange(float number) {
+ return number >= 0f && number <= 1f;
+ }
+
class IWallpaperEngineWrapper extends IWallpaperEngine.Stub
implements HandlerCaller.Callback {
private final HandlerCaller mCaller;
@@ -1477,6 +1950,14 @@
mCaller.sendMessage(msg);
}
+ public void addLocalColorsAreas(List<RectF> regions) {
+ mEngine.addLocalColorsAreas(regions);
+ }
+
+ public void removeLocalColorsAreas(List<RectF> regions) {
+ mEngine.removeLocalColorsAreas(regions);
+ }
+
public void destroy() {
Message msg = mCaller.obtainMessage(DO_DETACH);
mCaller.sendMessage(msg);
@@ -1516,14 +1997,15 @@
}
switch (message.what) {
case DO_ATTACH: {
+ Engine engine = onCreateEngine();
+ mEngine = engine;
try {
mConnection.attachEngine(this, mDisplayId);
} catch (RemoteException e) {
+ engine.detach();
Log.w(TAG, "Wallpaper host disappeared", e);
return;
}
- Engine engine = onCreateEngine();
- mEngine = engine;
mActiveEngines.add(engine);
engine.attach(this);
return;
diff --git a/core/java/android/speech/tts/ITextToSpeechManager.aidl b/core/java/android/speech/tts/ITextToSpeechManager.aidl
new file mode 100644
index 0000000..e6b63df
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechManager.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.speech.tts;
+
+import android.speech.tts.ITextToSpeechSessionCallback;
+
+/**
+ * TextToSpeechManagerService interface. Allows opening {@link TextToSpeech} session with the
+ * specified provider proxied by the system service.
+ *
+ * @hide
+ */
+oneway interface ITextToSpeechManager {
+ void createSession(in String engine, in ITextToSpeechSessionCallback managerCallback);
+}
diff --git a/core/java/android/speech/tts/ITextToSpeechSession.aidl b/core/java/android/speech/tts/ITextToSpeechSession.aidl
new file mode 100644
index 0000000..b2afeb0
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechSession.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.speech.tts;
+
+/**
+ * TextToSpeech session interface. Allows to control remote TTS service session once connected.
+ *
+ * @see ITextToSpeechManager
+ * @see ITextToSpeechSessionCallback
+ *
+ * {@hide}
+ */
+oneway interface ITextToSpeechSession {
+
+ /**
+ * Disconnects the client from the TTS provider.
+ */
+ void disconnect();
+}
\ No newline at end of file
diff --git a/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
new file mode 100644
index 0000000..545622a
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.speech.tts;
+import android.speech.tts.ITextToSpeechSession;
+
+/**
+ * Callback interface for a session created by {@link ITextToSpeechManager} API.
+ *
+ * @hide
+ */
+oneway interface ITextToSpeechSessionCallback {
+
+ void onConnected(in ITextToSpeechSession session, in IBinder serviceBinder);
+
+ void onDisconnected();
+
+ void onError(in String errorInfo);
+}
\ No newline at end of file
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 7a18538..5d66dc7 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -35,6 +35,7 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
@@ -51,6 +52,7 @@
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
*
@@ -695,6 +697,8 @@
public static final String KEY_FEATURE_NETWORK_RETRIES_COUNT = "networkRetriesCount";
}
+ private static final boolean DEBUG = false;
+
private final Context mContext;
@UnsupportedAppUsage
private Connection mConnectingServiceConnection;
@@ -716,6 +720,9 @@
private final Map<CharSequence, Uri> mUtterances;
private final Bundle mParams = new Bundle();
private final TtsEngines mEnginesHelper;
+ private final boolean mIsSystem;
+ @Nullable private final Executor mInitExecutor;
+
@UnsupportedAppUsage
private volatile String mCurrentEngine = null;
@@ -758,8 +765,21 @@
*/
public TextToSpeech(Context context, OnInitListener listener, String engine,
String packageName, boolean useFallback) {
+ this(context, /* initExecutor= */ null, listener, engine, packageName,
+ useFallback, /* isSystem= */ true);
+ }
+
+ /**
+ * Used internally to instantiate TextToSpeech objects.
+ *
+ * @hide
+ */
+ private TextToSpeech(Context context, @Nullable Executor initExecutor,
+ OnInitListener initListener, String engine, String packageName, boolean useFallback,
+ boolean isSystem) {
mContext = context;
- mInitListener = listener;
+ mInitExecutor = initExecutor;
+ mInitListener = initListener;
mRequestedEngine = engine;
mUseFallback = useFallback;
@@ -768,6 +788,9 @@
mUtteranceProgressListener = null;
mEnginesHelper = new TtsEngines(mContext);
+
+ mIsSystem = isSystem;
+
initTts();
}
@@ -842,10 +865,14 @@
}
private boolean connectToEngine(String engine) {
- Connection connection = new Connection();
- Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
- intent.setPackage(engine);
- boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ Connection connection;
+ if (mIsSystem) {
+ connection = new SystemConnection();
+ } else {
+ connection = new DirectConnection();
+ }
+
+ boolean bound = connection.connect(engine);
if (!bound) {
Log.e(TAG, "Failed to bind to " + engine);
return false;
@@ -857,11 +884,19 @@
}
private void dispatchOnInit(int result) {
- synchronized (mStartLock) {
- if (mInitListener != null) {
- mInitListener.onInit(result);
- mInitListener = null;
+ Runnable onInitCommand = () -> {
+ synchronized (mStartLock) {
+ if (mInitListener != null) {
+ mInitListener.onInit(result);
+ mInitListener = null;
+ }
}
+ };
+
+ if (mInitExecutor != null) {
+ mInitExecutor.execute(onInitCommand);
+ } else {
+ onInitCommand.run();
}
}
@@ -2127,13 +2162,17 @@
return mEnginesHelper.getEngines();
}
- private class Connection implements ServiceConnection {
+ private abstract class Connection implements ServiceConnection {
private ITextToSpeechService mService;
private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
private boolean mEstablished;
+ abstract boolean connect(String engine);
+
+ abstract void disconnect();
+
private final ITextToSpeechCallback.Stub mCallback =
new ITextToSpeechCallback.Stub() {
public void onStop(String utteranceId, boolean isStarted)
@@ -2199,11 +2238,6 @@
};
private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
- private final ComponentName mName;
-
- public SetupConnectionAsyncTask(ComponentName name) {
- mName = name;
- }
@Override
protected Integer doInBackground(Void... params) {
@@ -2227,7 +2261,7 @@
mParams.putString(Engine.KEY_PARAM_VOICE_NAME, defaultVoiceName);
}
- Log.i(TAG, "Set up connection to " + mName);
+ Log.i(TAG, "Setting up the connection to TTS engine...");
return SUCCESS;
} catch (RemoteException re) {
Log.e(TAG, "Error connecting to service, setCallback() failed");
@@ -2249,11 +2283,11 @@
}
@Override
- public void onServiceConnected(ComponentName name, IBinder service) {
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
synchronized(mStartLock) {
mConnectingServiceConnection = null;
- Log.i(TAG, "Connected to " + name);
+ Log.i(TAG, "Connected to TTS engine");
if (mOnSetupConnectionAsyncTask != null) {
mOnSetupConnectionAsyncTask.cancel(false);
@@ -2263,7 +2297,7 @@
mServiceConnection = Connection.this;
mEstablished = false;
- mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
+ mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask();
mOnSetupConnectionAsyncTask.execute();
}
}
@@ -2277,7 +2311,7 @@
*
* @return true if we cancel mOnSetupConnectionAsyncTask in progress.
*/
- private boolean clearServiceConnection() {
+ protected boolean clearServiceConnection() {
synchronized(mStartLock) {
boolean result = false;
if (mOnSetupConnectionAsyncTask != null) {
@@ -2295,8 +2329,8 @@
}
@Override
- public void onServiceDisconnected(ComponentName name) {
- Log.i(TAG, "Asked to disconnect from " + name);
+ public void onServiceDisconnected(ComponentName componentName) {
+ Log.i(TAG, "Disconnected from TTS engine");
if (clearServiceConnection()) {
/* We need to protect against a rare case where engine
* dies just after successful connection - and we process onServiceDisconnected
@@ -2308,11 +2342,6 @@
}
}
- public void disconnect() {
- mContext.unbindService(this);
- clearServiceConnection();
- }
-
public boolean isEstablished() {
return mService != null && mEstablished;
}
@@ -2342,6 +2371,91 @@
}
}
+ // Currently all the clients are routed through the System connection. Direct connection
+ // is left for debugging, testing and benchmarking purposes.
+ // TODO(b/179599071): Remove direct connection once system one is fully tested.
+ private class DirectConnection extends Connection {
+ @Override
+ boolean connect(String engine) {
+ Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+ intent.setPackage(engine);
+ return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ void disconnect() {
+ mContext.unbindService(this);
+ clearServiceConnection();
+ }
+ }
+
+ private class SystemConnection extends Connection {
+
+ @Nullable
+ private volatile ITextToSpeechSession mSession;
+
+ @Override
+ boolean connect(String engine) {
+ IBinder binder = ServiceManager.getService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE);
+
+ ITextToSpeechManager manager = ITextToSpeechManager.Stub.asInterface(binder);
+
+ if (manager == null) {
+ Log.e(TAG, "System service is not available!");
+ return false;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Connecting to engine: " + engine);
+ }
+
+ try {
+ manager.createSession(engine, new ITextToSpeechSessionCallback.Stub() {
+ @Override
+ public void onConnected(ITextToSpeechSession session, IBinder serviceBinder) {
+ mSession = session;
+ onServiceConnected(
+ /* componentName= */ null,
+ serviceBinder);
+ }
+
+ @Override
+ public void onDisconnected() {
+ onServiceDisconnected(/* componentName= */ null);
+ }
+
+ @Override
+ public void onError(String errorInfo) {
+ Log.w(TAG, "System TTS connection error: " + errorInfo);
+ // The connection was not established successfully - handle as
+ // disconnection: clear the state and notify the user.
+ onServiceDisconnected(/* componentName= */ null);
+ }
+ });
+
+ return true;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Error communicating with the System Server: ", ex);
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ void disconnect() {
+ ITextToSpeechSession session = mSession;
+
+ if (session != null) {
+ try {
+ session.disconnect();
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Error disconnecting session", ex);
+ }
+
+ clearServiceConnection();
+ }
+ }
+ }
+
private interface Action<R> {
R run(ITextToSpeechService service) throws RemoteException;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1273b49..ba78f96 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22164,6 +22164,10 @@
* and hardware acceleration.
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
+ // Clear the overscroll effect:
+ // TODO: Use internal API instead of overriding the existing RenderEffect
+ setRenderEffect(null);
+
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
/* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 228ee3c..f1f6786 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1375,6 +1375,7 @@
final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
attrs.getTitle().toString());
+ mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
updateColorModeIfNeeded(attrs.getColorMode());
updateForceDarkMode();
if (mAttachInfo.mThreadedRenderer != null) {
@@ -1941,6 +1942,10 @@
mBlastBufferQueue.destroy();
mBlastBufferQueue = null;
}
+
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.setSurfaceControl(null);
+ }
}
/**
@@ -7651,6 +7656,9 @@
mSurface.transferFrom(blastSurface);
}
}
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
+ }
} else {
destroySurface();
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 7b2bb73..a8fff8b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -749,7 +749,7 @@
@Override
public boolean hasActiveConnection(View view) {
synchronized (mH) {
- if (!hasServedByInputMethodLocked(view)) {
+ if (!hasServedByInputMethodLocked(view) || mCurMethod == null) {
return false;
}
@@ -765,6 +765,17 @@
return mDelegate;
}
+ /**
+ * Checks whether the active input connection (if any) is for the given view.
+ *
+ * @hide
+ * @see ImeFocusController#getImmDelegate()#hasActiveInputConnection(View)
+ */
+ @TestApi
+ public boolean hasActiveInputConnection(@Nullable View view) {
+ return mDelegate.hasActiveConnection(view);
+ }
+
private View getServedViewLocked() {
return mCurRootView != null ? mCurRootView.getImeFocusController().getServedView() : null;
}
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 681fd14..1b62266 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -25,8 +25,12 @@
import android.content.res.TypedArray;
import android.graphics.BlendMode;
import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
import android.graphics.Rect;
+import android.graphics.RenderEffect;
+import android.graphics.RenderNode;
import android.os.Build;
import android.util.AttributeSet;
import android.view.animation.AnimationUtils;
@@ -149,6 +153,8 @@
private float mDisplacement = 0.5f;
private float mTargetDisplacement = 0.5f;
private @EdgeEffectType int mEdgeEffectType = TYPE_GLOW;
+ private Matrix mTmpMatrix = null;
+ private float[] mTmpPoints = null;
/**
* Construct a new EdgeEffect with a theme appropriate for the provided context.
@@ -250,11 +256,18 @@
public void onPull(float deltaDistance, float displacement) {
final long now = AnimationUtils.currentAnimationTimeMillis();
mTargetDisplacement = displacement;
- if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
+ if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration
+ && mEdgeEffectType == TYPE_GLOW) {
return;
}
if (mState != STATE_PULL) {
- mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY);
+ if (mEdgeEffectType == TYPE_STRETCH) {
+ // Restore the mPullDistance to the fraction it is currently showing -- we want
+ // to "catch" the current stretch value.
+ mPullDistance = mDistance;
+ } else {
+ mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY);
+ }
}
mState = STATE_PULL;
@@ -313,6 +326,15 @@
public float onPullDistance(float deltaDistance, float displacement) {
float finalDistance = Math.max(0f, deltaDistance + mDistance);
float delta = finalDistance - mDistance;
+ if (delta == 0f && mDistance == 0f) {
+ return 0f; // No pull, don't do anything.
+ }
+
+ if (mState != STATE_PULL && mState != STATE_PULL_DECAY && mEdgeEffectType == TYPE_GLOW) {
+ // Catch the edge glow in the middle of an animation.
+ mPullDistance = mDistance;
+ mState = STATE_PULL;
+ }
onPull(delta, displacement);
return delta;
}
@@ -466,33 +488,59 @@
* Draw into the provided canvas. Assumes that the canvas has been rotated
* accordingly and the size has been set. The effect will be drawn the full
* width of X=0 to X=width, beginning from Y=0 and extending to some factor <
- * 1.f of height.
+ * 1.f of height. The {@link #TYPE_STRETCH} effect will only be visible on a
+ * hardware canvas, e.g. {@link RenderNode#beginRecording()}.
*
* @param canvas Canvas to draw into
* @return true if drawing should continue beyond this frame to continue the
* animation
*/
public boolean draw(Canvas canvas) {
- update();
+ if (mEdgeEffectType == TYPE_GLOW) {
+ update();
+ final int count = canvas.save();
- final int count = canvas.save();
+ final float centerX = mBounds.centerX();
+ final float centerY = mBounds.height() - mRadius;
- final float centerX = mBounds.centerX();
- final float centerY = mBounds.height() - mRadius;
+ canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0);
- canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0);
+ final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
+ float translateX = mBounds.width() * displacement / 2;
- final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
- float translateX = mBounds.width() * displacement / 2;
+ canvas.clipRect(mBounds);
+ canvas.translate(translateX, 0);
+ mPaint.setAlpha((int) (0xff * mGlowAlpha));
+ canvas.drawCircle(centerX, centerY, mRadius, mPaint);
+ canvas.restoreToCount(count);
+ } else if (canvas instanceof RecordingCanvas) {
+ if (mState != STATE_PULL) {
+ update();
+ }
+ RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
+ if (mTmpMatrix == null) {
+ mTmpMatrix = new Matrix();
+ mTmpPoints = new float[4];
+ }
+ //noinspection deprecation
+ recordingCanvas.getMatrix(mTmpMatrix);
+ mTmpPoints[0] = mBounds.width() * mDisplacement;
+ mTmpPoints[1] = mDistance * mBounds.height();
+ mTmpPoints[2] = mTmpPoints[0];
+ mTmpPoints[3] = 0;
+ mTmpMatrix.mapPoints(mTmpPoints);
+ float x = mTmpPoints[0] - mTmpPoints[2];
+ float y = mTmpPoints[1] - mTmpPoints[3];
- canvas.clipRect(mBounds);
- canvas.translate(translateX, 0);
- mPaint.setAlpha((int) (0xff * mGlowAlpha));
- canvas.drawCircle(centerX, centerY, mRadius, mPaint);
- canvas.restoreToCount(count);
+ RenderNode renderNode = recordingCanvas.mNode;
+
+ // TODO: use stretchy RenderEffect and use internal API when it is ready
+ // TODO: wrap existing RenderEffect
+ renderNode.setRenderEffect(RenderEffect.createOffsetEffect(x, y));
+ }
boolean oneLastFrame = false;
- if (mState == STATE_RECEDE && mGlowScaleY == 0) {
+ if (mState == STATE_RECEDE && mDistance == 0) {
mState = STATE_IDLE;
oneLastFrame = true;
}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 97dbb154..6dedd12 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -89,7 +89,7 @@
*/
@NonNull
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124053130)
- private EdgeEffect mEdgeGlowLeft = new EdgeEffect(getContext());
+ private EdgeEffect mEdgeGlowLeft;
/**
* Tracks the state of the bottom edge glow.
@@ -98,7 +98,7 @@
* setting it via reflection and they need to keep working until they target Q.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124052619)
- private EdgeEffect mEdgeGlowRight = new EdgeEffect(getContext());
+ private EdgeEffect mEdgeGlowRight;
/**
* Position of the last motion event.
@@ -186,6 +186,8 @@
public HorizontalScrollView(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mEdgeGlowLeft = new EdgeEffect(context, attrs);
+ mEdgeGlowRight = new EdgeEffect(context, attrs);
initScrollView();
final TypedArray a = context.obtainStyledAttributes(
@@ -631,7 +633,15 @@
* otherwise don't. mScroller.isFinished should be false when
* being flinged.
*/
- mIsBeingDragged = !mScroller.isFinished();
+ mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowLeft.isFinished()
+ || !mEdgeGlowRight.isFinished();
+ // Catch the edge effect if it is active.
+ if (!mEdgeGlowLeft.isFinished()) {
+ mEdgeGlowLeft.onPullDistance(0f, 1f - ev.getY() / getHeight());
+ }
+ if (!mEdgeGlowRight.isFinished()) {
+ mEdgeGlowRight.onPullDistance(0f, ev.getY() / getHeight());
+ }
break;
}
@@ -675,7 +685,8 @@
if (getChildCount() == 0) {
return false;
}
- if ((mIsBeingDragged = !mScroller.isFinished())) {
+ if ((mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowRight.isFinished()
+ || !mEdgeGlowLeft.isFinished())) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
@@ -721,12 +732,26 @@
mLastMotionX = x;
final int oldX = mScrollX;
- final int oldY = mScrollY;
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
+ final float displacement = ev.getY(activePointerIndex) / getHeight();
+ if (canOverscroll) {
+ int consumed = 0;
+ if (deltaX < 0 && mEdgeGlowRight.getDistance() != 0f) {
+ consumed = Math.round(getHeight()
+ * mEdgeGlowRight.onPullDistance((float) deltaX / getWidth(),
+ displacement));
+ } else if (deltaX > 0 && mEdgeGlowLeft.getDistance() != 0f) {
+ consumed = Math.round(-getHeight()
+ * mEdgeGlowLeft.onPullDistance((float) -deltaX / getWidth(),
+ 1 - displacement));
+ }
+ deltaX -= consumed;
+ }
+
// Calling overScrollBy will call onOverScrolled, which
// calls onScrollChanged if applicable.
if (overScrollBy(deltaX, 0, mScrollX, 0, range, 0,
@@ -735,17 +760,17 @@
mVelocityTracker.clear();
}
- if (canOverscroll) {
+ if (canOverscroll && deltaX != 0f) {
final int pulledToX = oldX + deltaX;
if (pulledToX < 0) {
- mEdgeGlowLeft.onPull((float) deltaX / getWidth(),
- 1.f - ev.getY(activePointerIndex) / getHeight());
+ mEdgeGlowLeft.onPullDistance((float) -deltaX / getWidth(),
+ 1.f - displacement);
if (!mEdgeGlowRight.isFinished()) {
mEdgeGlowRight.onRelease();
}
} else if (pulledToX > range) {
- mEdgeGlowRight.onPull((float) deltaX / getWidth(),
- ev.getY(activePointerIndex) / getHeight());
+ mEdgeGlowRight.onPullDistance((float) deltaX / getWidth(),
+ displacement);
if (!mEdgeGlowLeft.isFinished()) {
mEdgeGlowLeft.onRelease();
}
diff --git a/core/java/android/widget/ImeAwareEditText.java b/core/java/android/widget/ImeAwareEditText.java
index 9cd4585..0d98085 100644
--- a/core/java/android/widget/ImeAwareEditText.java
+++ b/core/java/android/widget/ImeAwareEditText.java
@@ -80,7 +80,7 @@
public void scheduleShowSoftInput() {
final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
- if (imm.isActive(this)) {
+ if (imm.hasActiveInputConnection(this)) {
// This means that ImeAwareEditText is already connected to the IME.
// InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
mHasPendingShowSoftInputRequest = false;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 6cb4b81..4e3d99b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -48,6 +48,7 @@
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Outline;
+import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -99,6 +100,8 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Stack;
@@ -171,6 +174,11 @@
*/
private static final int MAX_NESTED_VIEWS = 10;
+ /**
+ * Maximum number of RemoteViews that can be specified in constructor.
+ */
+ private static final int MAX_INIT_VIEW_COUNT = 16;
+
// The unique identifiers for each custom {@link Action}.
private static final int SET_ON_CLICK_RESPONSE_TAG = 1;
private static final int REFLECTION_ACTION_TAG = 2;
@@ -290,7 +298,7 @@
* The resource ID of the layout file. (Added to the parcel)
*/
@UnsupportedAppUsage
- private final int mLayoutId;
+ private int mLayoutId;
/**
* The resource ID of the layout file in dark text mode. (Added to the parcel)
@@ -322,6 +330,7 @@
*/
private static final int MODE_NORMAL = 0;
private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1;
+ private static final int MODE_HAS_SIZED_REMOTEVIEWS = 2;
/**
* Used in conjunction with the special constructor
@@ -331,12 +340,26 @@
private RemoteViews mLandscape = null;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private RemoteViews mPortrait = null;
+ /**
+ * List of RemoteViews with their ideal size. There must be at least two if the map is not null.
+ *
+ * The smallest remote view is always the last element in the list.
+ */
+ private List<RemoteViews> mSizedRemoteViews = null;
+
+ /**
+ * Ideal size for this RemoteViews.
+ *
+ * Only to be used on children views used in a {@link RemoteViews} with
+ * {@link RemoteViews#hasSizedRemoteViews()}.
+ */
+ private PointF mIdealSize = null;
@ApplyFlags
private int mApplyFlags = 0;
/** Class cookies of the Parcel this instance was read from. */
- private final Map<Class, Object> mClassCookies;
+ private Map<Class, Object> mClassCookies;
private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = (view, pendingIntent, response)
-> startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
@@ -2768,23 +2791,50 @@
mClassCookies = null;
}
+ private boolean hasMultipleLayouts() {
+ return hasLandscapeAndPortraitLayouts() || hasSizedRemoteViews();
+ }
+
private boolean hasLandscapeAndPortraitLayouts() {
return (mLandscape != null) && (mPortrait != null);
}
+ private boolean hasSizedRemoteViews() {
+ return mSizedRemoteViews != null;
+ }
+
+ private @Nullable PointF getIdealSize() {
+ return mIdealSize;
+ }
+
+ private void setIdealSize(@Nullable PointF size) {
+ mIdealSize = size;
+ }
+
+ /**
+ * Finds the smallest view in {@code mSizedRemoteViews}.
+ * This method must not be called if {@code mSizedRemoteViews} is null.
+ */
+ private RemoteViews findSmallestRemoteView() {
+ return mSizedRemoteViews.get(mSizedRemoteViews.size() - 1);
+ }
+
/**
* Create a new RemoteViews object that will inflate as the specified
* landspace or portrait RemoteViews, depending on the current configuration.
*
* @param landscape The RemoteViews to inflate in landscape configuration
* @param portrait The RemoteViews to inflate in portrait configuration
+ * @throws IllegalArgumentException if either landscape or portrait are null or if they are
+ * not from the same application
*/
public RemoteViews(RemoteViews landscape, RemoteViews portrait) {
if (landscape == null || portrait == null) {
- throw new RuntimeException("Both RemoteViews must be non-null");
+ throw new IllegalArgumentException("Both RemoteViews must be non-null");
}
if (!landscape.hasSameAppInfo(portrait.mApplication)) {
- throw new RuntimeException("Both RemoteViews must share the same package and user");
+ throw new IllegalArgumentException(
+ "Both RemoteViews must share the same package and user");
}
mApplication = portrait.mApplication;
mLayoutId = portrait.mLayoutId;
@@ -2802,9 +2852,84 @@
}
/**
+ * Create a new RemoteViews object that will inflate the layout with the closest size
+ * specification.
+ *
+ * The default remote views in that case is always the smallest one provided.
+ *
+ * @param remoteViews Mapping of size to layout.
+ * @throws IllegalArgumentException if the map is empty, there are more than
+ * MAX_INIT_VIEW_COUNT layouts or the remote views are not all from the same application.
+ */
+ public RemoteViews(@NonNull Map<PointF, RemoteViews> remoteViews) {
+ if (remoteViews.isEmpty()) {
+ throw new IllegalArgumentException("The set of RemoteViews cannot be empty");
+ }
+ if (remoteViews.size() > MAX_INIT_VIEW_COUNT) {
+ throw new IllegalArgumentException("Too many RemoteViews in constructor");
+ }
+ if (remoteViews.size() == 1) {
+ initializeFrom(remoteViews.values().iterator().next());
+ return;
+ }
+ mBitmapCache = new BitmapCache();
+ mClassCookies = initializeSizedRemoteViews(
+ remoteViews.entrySet().stream().map(
+ entry -> {
+ entry.getValue().setIdealSize(entry.getKey());
+ return entry.getValue();
+ }
+ ).iterator()
+ );
+
+ RemoteViews smallestView = findSmallestRemoteView();
+ mApplication = smallestView.mApplication;
+ mLayoutId = smallestView.mLayoutId;
+ mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
+ }
+
+ // Initialize mSizedRemoteViews and return the class cookies.
+ private Map<Class, Object> initializeSizedRemoteViews(Iterator<RemoteViews> remoteViews) {
+ List<RemoteViews> sizedRemoteViews = new ArrayList<>();
+ Map<Class, Object> classCookies = null;
+ float viewArea = Float.MAX_VALUE;
+ RemoteViews smallestView = null;
+ while (remoteViews.hasNext()) {
+ RemoteViews view = remoteViews.next();
+ PointF size = view.getIdealSize();
+ float newViewArea = size.x * size.y;
+ if (smallestView != null && !view.hasSameAppInfo(smallestView.mApplication)) {
+ throw new IllegalArgumentException(
+ "All RemoteViews must share the same package and user");
+ }
+ if (smallestView == null || newViewArea < viewArea) {
+ if (smallestView != null) {
+ sizedRemoteViews.add(smallestView);
+ }
+ viewArea = newViewArea;
+ smallestView = view;
+ } else {
+ sizedRemoteViews.add(view);
+ }
+ configureRemoteViewsAsChild(view);
+ view.setIdealSize(size);
+ if (classCookies == null) {
+ classCookies = view.mClassCookies;
+ }
+ }
+ sizedRemoteViews.add(smallestView);
+ mSizedRemoteViews = sizedRemoteViews;
+ return classCookies;
+ }
+
+ /**
* Creates a copy of another RemoteViews.
*/
public RemoteViews(RemoteViews src) {
+ initializeFrom(src);
+ }
+
+ private void initializeFrom(RemoteViews src) {
mBitmapCache = src.mBitmapCache;
mApplication = src.mApplication;
mIsRoot = src.mIsRoot;
@@ -2812,12 +2937,20 @@
mLightBackgroundLayoutId = src.mLightBackgroundLayoutId;
mApplyFlags = src.mApplyFlags;
mClassCookies = src.mClassCookies;
+ mIdealSize = src.mIdealSize;
if (src.hasLandscapeAndPortraitLayouts()) {
mLandscape = new RemoteViews(src.mLandscape);
mPortrait = new RemoteViews(src.mPortrait);
}
+ if (src.hasSizedRemoteViews()) {
+ mSizedRemoteViews = new ArrayList<>(src.mSizedRemoteViews.size());
+ for (RemoteViews srcView : src.mSizedRemoteViews) {
+ mSizedRemoteViews.add(new RemoteViews(srcView));
+ }
+ }
+
if (src.mActions != null) {
Parcel p = Parcel.obtain();
p.putClassCookies(mClassCookies);
@@ -2867,10 +3000,29 @@
if (mode == MODE_NORMAL) {
mApplication = parcel.readInt() == 0 ? info :
ApplicationInfo.CREATOR.createFromParcel(parcel);
+ mIdealSize = parcel.readInt() == 0 ? null : PointF.CREATOR.createFromParcel(parcel);
mLayoutId = parcel.readInt();
mLightBackgroundLayoutId = parcel.readInt();
readActionsFromParcel(parcel, depth);
+ } else if (mode == MODE_HAS_SIZED_REMOTEVIEWS) {
+ int numViews = parcel.readInt();
+ if (numViews > MAX_INIT_VIEW_COUNT) {
+ throw new IllegalArgumentException(
+ "Too many views in mapping from size to RemoteViews.");
+ }
+ List<RemoteViews> remoteViews = new ArrayList<>(numViews);
+ for (int i = 0; i < numViews; i++) {
+ RemoteViews view = new RemoteViews(parcel, mBitmapCache, info, depth,
+ mClassCookies);
+ info = view.mApplication;
+ remoteViews.add(view);
+ }
+ initializeSizedRemoteViews(remoteViews.iterator());
+ RemoteViews smallestView = findSmallestRemoteView();
+ mApplication = smallestView.mApplication;
+ mLayoutId = smallestView.mLayoutId;
+ mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
} else {
// MODE_HAS_LANDSCAPE_AND_PORTRAIT
mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies);
@@ -2990,16 +3142,20 @@
*/
private void setBitmapCache(BitmapCache bitmapCache) {
mBitmapCache = bitmapCache;
- if (!hasLandscapeAndPortraitLayouts()) {
+ if (hasSizedRemoteViews()) {
+ for (RemoteViews remoteView : mSizedRemoteViews) {
+ remoteView.setBitmapCache(bitmapCache);
+ }
+ } else if (hasLandscapeAndPortraitLayouts()) {
+ mLandscape.setBitmapCache(bitmapCache);
+ mPortrait.setBitmapCache(bitmapCache);
+ } else {
if (mActions != null) {
final int count = mActions.size();
- for (int i= 0; i < count; ++i) {
+ for (int i = 0; i < count; ++i) {
mActions.get(i).setBitmapCache(bitmapCache);
}
}
- } else {
- mLandscape.setBitmapCache(bitmapCache);
- mPortrait.setBitmapCache(bitmapCache);
}
}
@@ -3018,10 +3174,10 @@
* @param a The action to add
*/
private void addAction(Action a) {
- if (hasLandscapeAndPortraitLayouts()) {
- throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
- " layouts cannot be modified. Instead, fully configure the landscape and" +
- " portrait layouts individually before constructing the combined layout.");
+ if (hasMultipleLayouts()) {
+ throw new RuntimeException("RemoteViews specifying separate layouts for orientation"
+ + " or size cannot be modified. Instead, fully configure each layouts"
+ + " individually before constructing the combined layout.");
}
if (mActions == null) {
mActions = new ArrayList<>();
@@ -4100,14 +4256,79 @@
int orientation = context.getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
return mLandscape;
- } else {
- return mPortrait;
}
+ return mPortrait;
+ }
+ if (hasSizedRemoteViews()) {
+ return findSmallestRemoteView();
}
return this;
}
/**
+ * Returns the square distance between two points.
+ *
+ * This is particularly useful when we only care about the ordering of the distances.
+ */
+ private static float squareDistance(PointF p1, PointF p2) {
+ float dx = p1.x - p2.x;
+ float dy = p1.y - p2.y;
+ return dx * dx + dy * dy;
+ }
+
+ /**
+ * Returns whether the layout fits in the space available to the widget.
+ *
+ * A layout fits on a widget if the widget size is known (i.e. not null) and both dimensions
+ * are smaller than the ones of the widget, adding some padding to account for rounding errors.
+ */
+ private static boolean fitsIn(PointF sizeLayout, @Nullable PointF sizeWidget) {
+ return sizeWidget != null && (Math.ceil(sizeWidget.x) + 1 > sizeLayout.x)
+ && (Math.ceil(sizeWidget.y) + 1 > sizeLayout.y);
+ }
+
+ /**
+ * Returns the most appropriate {@link RemoteViews} given the context and, if not null, the
+ * size of the widget.
+ *
+ * If {@link RemoteViews#hasSizedRemoteViews()} returns true, the most appropriate view is
+ * the one that fits in the widget (according to {@link RemoteViews#fitsIn}) and has the
+ * diagonal the most similar to the widget. If no layout fits or the size of the widget is
+ * not specified, the one with the smallest area will be chosen.
+ */
+ private RemoteViews getRemoteViewsToApply(@NonNull Context context,
+ @Nullable PointF widgetSize) {
+ if (!hasSizedRemoteViews()) {
+ // If there isn't multiple remote views, fall back on the previous methods.
+ return getRemoteViewsToApply(context);
+ }
+ // Find the better remote view
+ RemoteViews bestFit = null;
+ float bestSqDist = Float.MAX_VALUE;
+ for (RemoteViews layout : mSizedRemoteViews) {
+ PointF layoutSize = layout.getIdealSize();
+ if (fitsIn(layoutSize, widgetSize)) {
+ if (bestFit == null) {
+ bestFit = layout;
+ bestSqDist = squareDistance(layoutSize, widgetSize);
+ } else {
+ float newSqDist = squareDistance(layoutSize, widgetSize);
+ if (newSqDist < bestSqDist) {
+ bestFit = layout;
+ bestSqDist = newSqDist;
+ }
+ }
+ }
+ }
+ if (bestFit == null) {
+ Log.w(LOG_TAG, "Could not find a RemoteViews fitting the current size: " + widgetSize);
+ return findSmallestRemoteView();
+ }
+ return bestFit;
+ }
+
+
+ /**
* Inflates the view hierarchy represented by this object and applies
* all of the actions.
*
@@ -4124,7 +4345,13 @@
/** @hide */
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
- RemoteViews rvToApply = getRemoteViewsToApply(context);
+ return apply(context, parent, handler, null);
+ }
+
+ /** @hide */
+ public View apply(@NonNull Context context, @NonNull ViewGroup parent,
+ @Nullable OnClickHandler handler, @Nullable PointF size) {
+ RemoteViews rvToApply = getRemoteViewsToApply(context, size);
View result = inflateView(context, rvToApply, parent);
rvToApply.performApply(result, parent, handler);
@@ -4132,9 +4359,17 @@
}
/** @hide */
- public View applyWithTheme(Context context, ViewGroup parent, OnClickHandler handler,
+ public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
+ @Nullable OnClickHandler handler,
@StyleRes int applyThemeResId) {
- RemoteViews rvToApply = getRemoteViewsToApply(context);
+ return applyWithTheme(context, parent, handler, applyThemeResId, null);
+ }
+
+ /** @hide */
+ public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
+ @Nullable OnClickHandler handler,
+ @StyleRes int applyThemeResId, @Nullable PointF size) {
+ RemoteViews rvToApply = getRemoteViewsToApply(context, size);
View result = inflateView(context, rvToApply, parent, applyThemeResId);
rvToApply.performApply(result, parent, handler);
@@ -4219,12 +4454,26 @@
/** @hide */
public CancellationSignal applyAsync(Context context, ViewGroup parent,
Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
- return getAsyncApplyTask(context, parent, listener, handler).startTaskOnExecutor(executor);
+ return applyAsync(context, parent, executor, listener, handler, null);
+ }
+
+ /** @hide */
+ public CancellationSignal applyAsync(Context context, ViewGroup parent,
+ Executor executor, OnViewAppliedListener listener, OnClickHandler handler,
+ PointF size) {
+ return getAsyncApplyTask(context, parent, listener, handler, size).startTaskOnExecutor(
+ executor);
}
private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
OnViewAppliedListener listener, OnClickHandler handler) {
- return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener,
+ return getAsyncApplyTask(context, parent, listener, handler, null);
+ }
+
+ private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
+ OnViewAppliedListener listener, OnClickHandler handler, PointF size) {
+ return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context,
+ listener,
handler, null);
}
@@ -4341,12 +4590,18 @@
/** @hide */
public void reapply(Context context, View v, OnClickHandler handler) {
- RemoteViews rvToApply = getRemoteViewsToApply(context);
+ reapply(context, v, handler, null);
+ }
- // In the case that a view has this RemoteViews applied in one orientation, is persisted
- // across orientation change, and has the RemoteViews re-applied in the new orientation,
- // we throw an exception, since the layouts may be completely unrelated.
- if (hasLandscapeAndPortraitLayouts()) {
+ /** @hide */
+ public void reapply(Context context, View v, OnClickHandler handler, PointF size) {
+ RemoteViews rvToApply = getRemoteViewsToApply(context, size);
+
+ // In the case that a view has this RemoteViews applied in one orientation or size, is
+ // persisted across change, and has the RemoteViews re-applied in a different situation
+ // (orientation or size), we throw an exception, since the layouts may be completely
+ // unrelated.
+ if (hasMultipleLayouts()) {
if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
" that does not share the same root layout id.");
@@ -4377,12 +4632,18 @@
/** @hide */
public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
OnViewAppliedListener listener, OnClickHandler handler) {
- RemoteViews rvToApply = getRemoteViewsToApply(context);
+ return reapplyAsync(context, v, executor, listener, handler, null);
+ }
+
+ /** @hide */
+ public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
+ OnViewAppliedListener listener, OnClickHandler handler, PointF size) {
+ RemoteViews rvToApply = getRemoteViewsToApply(context, size);
// In the case that a view has this RemoteViews applied in one orientation, is persisted
// across orientation change, and has the RemoteViews re-applied in the new orientation,
// we throw an exception, since the layouts may be completely unrelated.
- if (hasLandscapeAndPortraitLayouts()) {
+ if (hasMultipleLayouts()) {
if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
" that does not share the same root layout id.");
@@ -4466,7 +4727,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- if (!hasLandscapeAndPortraitLayouts()) {
+ if (!hasMultipleLayouts()) {
dest.writeInt(MODE_NORMAL);
// We only write the bitmap cache if we are the root RemoteViews, as this cache
// is shared by all children.
@@ -4479,9 +4740,26 @@
dest.writeInt(1);
mApplication.writeToParcel(dest, flags);
}
+ if (mIsRoot || mIdealSize == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ mIdealSize.writeToParcel(dest, flags);
+ }
dest.writeInt(mLayoutId);
dest.writeInt(mLightBackgroundLayoutId);
writeActionsToParcel(dest);
+ } else if (hasSizedRemoteViews()) {
+ dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
+ if (mIsRoot) {
+ mBitmapCache.writeBitmapsToParcel(dest, flags);
+ }
+ int childFlags = flags;
+ dest.writeInt(mSizedRemoteViews.size());
+ for (RemoteViews view : mSizedRemoteViews) {
+ view.writeToParcel(dest, childFlags);
+ childFlags |= PARCELABLE_ELIDE_DUPLICATES;
+ }
} else {
dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
// We only write the bitmap cache if we are the root RemoteViews, as this cache
@@ -4735,11 +5013,9 @@
* before starting the intent.
*
* @param fillIntent The intent which will be combined with the parent's PendingIntent in
- * order to determine the behavior of the response
- *
+ * order to determine the behavior of the response
* @see RemoteViews#setPendingIntentTemplate(int, PendingIntent)
* @see RemoteViews#setOnClickFillInIntent(int, Intent)
- * @return
*/
@NonNull
public static RemoteResponse fromFillInIntent(@NonNull Intent fillIntent) {
@@ -4754,9 +5030,8 @@
* the epicenter for the exit Transition. The position of the associated shared element in
* the launched Activity will be the epicenter of its entering Transition.
*
- * @param viewId The id of the view to be shared as part of the transition
+ * @param viewId The id of the view to be shared as part of the transition
* @param sharedElementName The shared element name for this view
- *
* @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])
*/
@NonNull
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index f3de982..64d09de 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -98,7 +98,7 @@
*/
@NonNull
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768600)
- private EdgeEffect mEdgeGlowTop = new EdgeEffect(getContext());
+ private EdgeEffect mEdgeGlowTop;
/**
* Tracks the state of the bottom edge glow.
@@ -108,7 +108,7 @@
*/
@NonNull
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769386)
- private EdgeEffect mEdgeGlowBottom = new EdgeEffect(getContext());
+ private EdgeEffect mEdgeGlowBottom;
/**
* Position of the last motion event.
@@ -213,6 +213,8 @@
public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mEdgeGlowTop = new EdgeEffect(context, attrs);
+ mEdgeGlowBottom = new EdgeEffect(context, attrs);
initScrollView();
final TypedArray a = context.obtainStyledAttributes(
@@ -679,7 +681,15 @@
* isFinished() is correct.
*/
mScroller.computeScrollOffset();
- mIsBeingDragged = !mScroller.isFinished();
+ mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowBottom.isFinished()
+ || !mEdgeGlowTop.isFinished();
+ // Catch the edge effect if it is active.
+ if (!mEdgeGlowTop.isFinished()) {
+ mEdgeGlowTop.onPullDistance(0f, ev.getX() / getWidth());
+ }
+ if (!mEdgeGlowBottom.isFinished()) {
+ mEdgeGlowBottom.onPullDistance(0f, 1f - ev.getX() / getWidth());
+ }
if (mIsBeingDragged && mScrollStrictSpan == null) {
mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
}
@@ -732,7 +742,8 @@
if (getChildCount() == 0) {
return false;
}
- if ((mIsBeingDragged = !mScroller.isFinished())) {
+ if ((mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowTop.isFinished()
+ || !mEdgeGlowBottom.isFinished())) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
@@ -793,6 +804,21 @@
boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
+ final float displacement = ev.getX(activePointerIndex) / getWidth();
+ if (canOverscroll) {
+ int consumed = 0;
+ if (deltaY < 0 && mEdgeGlowBottom.getDistance() != 0f) {
+ consumed = Math.round(getHeight()
+ * mEdgeGlowBottom.onPullDistance((float) deltaY / getHeight(),
+ 1 - displacement));
+ } else if (deltaY > 0 && mEdgeGlowTop.getDistance() != 0f) {
+ consumed = Math.round(-getHeight()
+ * mEdgeGlowTop.onPullDistance((float) -deltaY / getHeight(),
+ displacement));
+ }
+ deltaY -= consumed;
+ }
+
// Calling overScrollBy will call onOverScrolled, which
// calls onScrollChanged if applicable.
if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
@@ -807,17 +833,17 @@
mLastMotionY -= mScrollOffset[1];
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
- } else if (canOverscroll) {
+ } else if (canOverscroll && deltaY != 0f) {
final int pulledToY = oldY + deltaY;
if (pulledToY < 0) {
- mEdgeGlowTop.onPull((float) deltaY / getHeight(),
- ev.getX(activePointerIndex) / getWidth());
+ mEdgeGlowTop.onPullDistance((float) -deltaY / getHeight(),
+ displacement);
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
} else if (pulledToY > range) {
- mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
- 1.f - ev.getX(activePointerIndex) / getWidth());
+ mEdgeGlowBottom.onPullDistance((float) deltaY / getHeight(),
+ 1.f - displacement);
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl
new file mode 100644
index 0000000..5d02a29
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.compat;
+
+parcelable CompatibilityOverrideConfig;
diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
new file mode 100644
index 0000000..1c222a7
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.compat;
+
+
+import android.app.compat.PackageOverride;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Parcelable containing compat config overrides for a given application.
+ * @hide
+ */
+public final class CompatibilityOverrideConfig implements Parcelable {
+ public final Map<Long, PackageOverride> overrides;
+
+ public CompatibilityOverrideConfig(Map<Long, PackageOverride> overrides) {
+ this.overrides = overrides;
+ }
+
+ private CompatibilityOverrideConfig(Parcel in) {
+ int keyCount = in.readInt();
+ overrides = new HashMap<>();
+ for (int i = 0; i < keyCount; i++) {
+ long key = in.readLong();
+ PackageOverride override = in.readParcelable(PackageOverride.class.getClassLoader());
+ overrides.put(key, override);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(overrides.size());
+ for (Long key : overrides.keySet()) {
+ dest.writeLong(key);
+ dest.writeParcelable(overrides.get(key), 0);
+ }
+ }
+
+ public static final Creator<CompatibilityOverrideConfig> CREATOR =
+ new Creator<CompatibilityOverrideConfig>() {
+
+ @Override
+ public CompatibilityOverrideConfig createFromParcel(Parcel in) {
+ return new CompatibilityOverrideConfig(in);
+ }
+
+ @Override
+ public CompatibilityOverrideConfig[] newArray(int size) {
+ return new CompatibilityOverrideConfig[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 7aca36a..249c134 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -21,6 +21,7 @@
import java.util.Map;
parcelable CompatibilityChangeConfig;
+parcelable CompatibilityOverrideConfig;
parcelable CompatibilityChangeInfo;
/**
* Platform private API for talking with the PlatformCompat service.
@@ -152,6 +153,17 @@
/**
* Adds overrides to compatibility changes.
*
+ * <p>Kills the app to allow the changes to take effect.
+ *
+ * @param overrides parcelable containing the compat change overrides to be applied
+ * @param packageName the package name of the app whose changes will be overridden
+ * @throws SecurityException if overriding changes is not permitted
+ */
+ void setOverridesFromInstaller(in CompatibilityOverrideConfig overrides, in String packageName);
+
+ /**
+ * Adds overrides to compatibility changes.
+ *
* <p>Does not kill the app, to be only used in tests.
*
* @param overrides parcelable containing the compat change overrides to be applied
diff --git a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
index dfcc914..1c7eca8 100644
--- a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
+++ b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
@@ -20,6 +20,8 @@
import android.graphics.fonts.FontUpdateRequest;
import android.text.FontConfig;
+import java.util.List;
+
/**
* System private interface for talking with
* {@link com.android.server.graphics.fonts.FontManagerService}.
@@ -28,5 +30,7 @@
interface IFontManager {
FontConfig getFontConfig();
- int updateFont(int baseVersion, in FontUpdateRequest request);
+ int updateFontFile(in FontUpdateRequest request, int baseVersion);
+
+ int updateFontFamily(in List<FontUpdateRequest> request, int baseVersion);
}
diff --git a/core/java/com/android/internal/os/KernelCpuBpfTracking.java b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
new file mode 100644
index 0000000..2852547
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/** CPU tracking using eBPF. */
+public final class KernelCpuBpfTracking {
+ private KernelCpuBpfTracking() {
+ }
+
+ /** Returns whether CPU tracking using eBPF is supported. */
+ public static native boolean isSupported();
+}
diff --git a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
index 50331e3..06760e1 100644
--- a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
+++ b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
@@ -23,9 +23,6 @@
private KernelCpuTotalBpfMapReader() {
}
- /** Returns whether total CPU time is measured. */
- public static native boolean isSupported();
-
/** Reads total CPU time from bpf map. */
public static native boolean read(Callback callback);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 9510f27..6983d35c 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -184,6 +184,7 @@
"com_android_internal_net_NetworkUtilsInternal.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
+ "com_android_internal_os_KernelCpuBpfTracking.cpp",
"com_android_internal_os_KernelCpuTotalBpfMapReader.cpp",
"com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
"com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 8879111..38bcc0f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -190,6 +190,7 @@
extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
+extern int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv* env);
extern int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv* env);
extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env);
extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env);
@@ -1586,6 +1587,7 @@
REG_JNI(register_android_security_Scrypt),
REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
REG_JNI(register_com_android_internal_os_FuseAppLoop),
+ REG_JNI(register_com_android_internal_os_KernelCpuBpfTracking),
REG_JNI(register_com_android_internal_os_KernelCpuTotalBpfMapReader),
REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
REG_JNI(register_com_android_internal_os_KernelSingleProcessCpuThreadReader),
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index a7950f2..8dcb210 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -829,6 +829,16 @@
return dmabufTotalSizeKb;
}
+static jlong android_os_Debug_getDmabufHeapTotalExportedKb(JNIEnv* env, jobject clazz) {
+ jlong dmabufHeapTotalSizeKb = -1;
+ uint64_t size;
+
+ if (meminfo::ReadDmabufHeapTotalExportedKb(&size)) {
+ dmabufHeapTotalSizeKb = size;
+ }
+ return dmabufHeapTotalSizeKb;
+}
+
static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) {
jlong poolsSizeKb = -1;
uint64_t size;
@@ -986,6 +996,8 @@
(void*)android_os_Debug_getDmabufTotalExportedKb },
{ "getGpuDmaBufUsageKb", "()J",
(void*)android_os_Debug_getGpuDmaBufUsageKb },
+ { "getDmabufHeapTotalExportedKb", "()J",
+ (void*)android_os_Debug_getDmabufHeapTotalExportedKb },
{ "getIonPoolsSizeKb", "()J",
(void*)android_os_Debug_getIonPoolsSizeKb },
{ "getDmabufMappedSizeKb", "()J",
diff --git a/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp
new file mode 100644
index 0000000..e6a82f6
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core_jni_helpers.h"
+
+#include <cputimeinstate.h>
+
+namespace android {
+
+static jboolean KernelCpuBpfTracking_isSupported(JNIEnv *, jobject) {
+ return android::bpf::isTrackingUidTimesSupported() ? JNI_TRUE : JNI_FALSE;
+}
+
+static const JNINativeMethod methods[] = {
+ {"isSupported", "()Z", (void *)KernelCpuBpfTracking_isSupported},
+};
+
+int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv *env) {
+ return RegisterMethodsOrDie(env, "com/android/internal/os/KernelCpuBpfTracking", methods,
+ NELEM(methods));
+}
+
+} // namespace android
diff --git a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
index d8446ca..7249238 100644
--- a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
+++ b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
@@ -20,10 +20,6 @@
namespace android {
-static jboolean KernelCpuTotalBpfMapReader_isSupported(JNIEnv *, jobject) {
- return android::bpf::isTrackingUidTimesSupported() ? JNI_TRUE : JNI_FALSE;
-}
-
static jboolean KernelCpuTotalBpfMapReader_read(JNIEnv *env, jobject, jobject callback) {
jclass callbackClass = env->GetObjectClass(callback);
jmethodID callbackMethod = env->GetMethodID(callbackClass, "accept", "(IIJ)V");
@@ -51,7 +47,6 @@
static const JNINativeMethod methods[] = {
{"read", "(Lcom/android/internal/os/KernelCpuTotalBpfMapReader$Callback;)Z",
(void *)KernelCpuTotalBpfMapReader_read},
- {"isSupported", "()Z", (void *)KernelCpuTotalBpfMapReader_isSupported},
};
int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv *env) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ba43ee7..6456e4e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1403,6 +1403,15 @@
android:description="@string/permgroupdesc_sensors"
android:priority="800" />
+ <!-- Allows an app to access sensor data with a sampling rate greater than 200 Hz.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS"
+ android:permissionGroup="android.permission-group.SENSORS"
+ android:label="@string/permlab_highSamplingRateSensors"
+ android:description="@string/permdesc_highSamplingRateSensors"
+ android:protectionLevel="normal" />
+
<!-- Allows an application to access data from sensors that the user uses to
measure what is happening inside their body, such as heart rate.
<p>Protection level: dangerous -->
@@ -4372,6 +4381,12 @@
<permission android:name="android.permission.POWER_SAVER"
android:protectionLevel="signature|privileged" />
+ <!-- Allows providing the system with battery predictions.
+ Superseded by DEVICE_POWER permission. @hide @SystemApi
+ -->
+ <permission android:name="android.permission.BATTERY_PREDICTION"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows access to the PowerManager.userActivity function.
<p>Not for use by third-party applications. @hide @SystemApi -->
<permission android:name="android.permission.USER_ACTIVITY"
@@ -5477,6 +5492,9 @@
<permission android:name="android.permission.SIGNAL_REBOOT_READINESS"
android:protectionLevel="signature|privileged" />
+ <!-- @hide Allows an application to get a People Tile preview for a given shortcut. -->
+ <permission android:name="android.permission.GET_PEOPLE_TILE_PREVIEW"
+ android:protectionLevel="signature|recents" />
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
diff --git a/core/res/res/drawable/btn_borderless_material.xml b/core/res/res/drawable/btn_borderless_material.xml
index 08e1060..1a0912e 100644
--- a/core/res/res/drawable/btn_borderless_material.xml
+++ b/core/res/res/drawable/btn_borderless_material.xml
@@ -15,7 +15,8 @@
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="?attr/colorControlHighlight">
+ android:color="?attr/colorControlHighlight"
+ android:rippleStyle="?attr/rippleStyle">
<item android:id="@id/mask"
android:drawable="@drawable/btn_default_mtrl_shape" />
</ripple>
diff --git a/core/res/res/drawable/btn_colored_material.xml b/core/res/res/drawable/btn_colored_material.xml
index 7ba21e8..5274ef2 100644
--- a/core/res/res/drawable/btn_colored_material.xml
+++ b/core/res/res/drawable/btn_colored_material.xml
@@ -19,7 +19,8 @@
android:insetTop="@dimen/button_inset_vertical_material"
android:insetRight="@dimen/button_inset_horizontal_material"
android:insetBottom="@dimen/button_inset_vertical_material">
- <ripple android:color="?attr/colorControlHighlight">
+ <ripple android:color="?attr/colorControlHighlight"
+ android:rippleStyle="?attr/rippleStyle">
<item>
<shape android:shape="rectangle"
android:tint="@color/btn_colored_background_material">
diff --git a/core/res/res/drawable/btn_default_material.xml b/core/res/res/drawable/btn_default_material.xml
index ed2b5aa..6a9e621 100644
--- a/core/res/res/drawable/btn_default_material.xml
+++ b/core/res/res/drawable/btn_default_material.xml
@@ -15,6 +15,7 @@
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="?attr/colorControlHighlight">
+ android:color="?attr/colorControlHighlight"
+ android:rippleStyle="?attr/rippleStyle">
<item android:drawable="@drawable/btn_default_mtrl_shape" />
</ripple>
diff --git a/core/res/res/drawable/btn_toggle_material.xml b/core/res/res/drawable/btn_toggle_material.xml
index 8b19e4a..7cee382 100644
--- a/core/res/res/drawable/btn_toggle_material.xml
+++ b/core/res/res/drawable/btn_toggle_material.xml
@@ -21,7 +21,8 @@
android:insetBottom="@dimen/button_inset_vertical_material">
<layer-list android:paddingMode="stack">
<item>
- <ripple android:color="?attr/colorControlHighlight">
+ <ripple android:color="?attr/colorControlHighlight"
+ android:rippleStyle="?attr/rippleStyle">
<item>
<shape android:shape="rectangle"
android:tint="?attr/colorButtonNormal">
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 927a8b4..69bb20c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6389,6 +6389,13 @@
<!-- The radius of the ripple when fully expanded. By default, the
radius is computed based on the size of the ripple's container. -->
<attr name="radius" />
+ <!-- The style of the ripple drawable is solid by default -->
+ <attr name="rippleStyle">
+ <!-- Solid is the default style -->
+ <enum name="solid" value="0" />
+ <!-- Patterned style-->
+ <enum name="patterned" value="1" />
+ </attr>
</declare-styleable>
<declare-styleable name="ScaleDrawable">
@@ -8003,6 +8010,14 @@
<attr name="minResizeWidth" format="dimension"/>
<!-- Minimum height that the AppWidget can be resized to. -->
<attr name="minResizeHeight" format="dimension"/>
+ <!-- Maximum width that the AppWidget can be resized to. -->
+ <attr name="maxResizeWidth" format="dimension"/>
+ <!-- Maximum height that the AppWidget can be resized to. -->
+ <attr name="maxResizeHeight" format="dimension"/>
+ <!-- Default width of the AppWidget in units of launcher grid cells. -->
+ <attr name="targetCellWidth" format="integer"/>
+ <!-- Default height of the AppWidget in units of launcher grid cells. -->
+ <attr name="targetCellHeight" format="integer"/>
<!-- Update period in milliseconds, or 0 if the AppWidget will update itself. -->
<attr name="updatePeriodMillis" format="integer" />
<!-- A resource id of a layout. -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 5546621..e7c4947 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -245,19 +245,19 @@
<color name="system_main_0">#ffffff</color>
<!-- Shade of the main system color at 95% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_main_50">#f0f0f0</color>
+ <color name="system_main_50">#f2f2f2</color>
<!-- Shade of the main system color at 90% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_main_100">#e2e2e2</color>
+ <color name="system_main_100">#e3e3e3</color>
<!-- Shade of the main system color at 80% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_main_200">#c6c6c6</color>
+ <color name="system_main_200">#c7c7c7</color>
<!-- Shade of the main system color at 70% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_main_300">#ababab</color>
<!-- Shade of the main system color at 60% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_main_400">#909090</color>
+ <color name="system_main_400">#8f8f8f</color>
<!-- Shade of the main system color at 50% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_main_500">#757575</color>
@@ -266,13 +266,13 @@
<color name="system_main_600">#5e5e5e</color>
<!-- Shade of the main system color at 30% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_main_700">#464646</color>
+ <color name="system_main_700">#474747</color>
<!-- Shade of the main system color at 20% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_main_800">#303030</color>
<!-- Shade of the main system color at 10% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_main_900">#1b1b1b</color>
+ <color name="system_main_900">#1f1f1f</color>
<!-- Darkest shade of the main color used by the system. Black.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_main_1000">#000000</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b7c755e..dd048f3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1946,6 +1946,8 @@
<string name="config_systemAutomotiveProjection" translatable="false"></string>
<!-- The name of the package that will hold the system cluster service role. -->
<string name="config_systemAutomotiveCluster" translatable="false"></string>
+ <!-- The name of the package that will hold the system shell role. -->
+ <string name="config_systemShell" translatable="false">com.android.shell</string>
<!-- The name of the package that will be allowed to change its components' label/icon. -->
<string name="config_overrideComponentUiPackage" translatable="false"></string>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index debdab0..7066419 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -926,4 +926,12 @@
<dimen name="controls_thumbnail_image_max_height">140dp</dimen>
<!-- The maximum width of a thumbnail in a ThumbnailTemplate. The image will be reduced to that width in case they are bigger.-->
<dimen name="controls_thumbnail_image_max_width">280dp</dimen>
+
+ <!-- System-provided radius for the background view of app widgets. The resolved value of this resource may change at runtime. -->
+ <dimen name="system_app_widget_background_radius">16dp</dimen>
+ <!-- System-provided radius for inner views on app widgets. The resolved value of this resource may change at runtime. -->
+ <dimen name="system_app_widget_inner_radius">8dp</dimen>
+ <!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. -->
+ <dimen name="system_app_widget_internal_padding">16dp</dimen>
+
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 278708a..1ebcf77 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3071,6 +3071,11 @@
<public name="windowSplashScreenAnimationDuration"/>
<public name="windowSplashScreenBrandingImage"/>
<public name="splashScreenTheme" />
+ <public name="rippleStyle" />
+ <public name="maxResizeWidth" />
+ <public name="maxResizeHeight" />
+ <public name="targetCellWidth" />
+ <public name="targetCellHeight" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
@@ -3111,6 +3116,11 @@
<public-group type="dimen" first-id="0x01050008">
<!-- dimension definitions go here -->
+
+ <!-- System-provided dimensions for app widgets. -->
+ <public name="system_app_widget_background_radius" />
+ <public name="system_app_widget_inner_radius" />
+ <public name="system_app_widget_internal_padding" />
</public-group>
<public-group type="bool" first-id="0x01110007">
@@ -3126,6 +3136,8 @@
<public name="config_systemAutomotiveCluster" />
<!-- @hide @SystemApi @TestApi -->
<public name="config_systemAutomotiveProjection" />
+ <!-- @hide @SystemApi -->
+ <public name="config_systemShell" />
</public-group>
<public-group type="id" first-id="0x01020055">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index af5e406..9ebe23a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1830,6 +1830,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_startViewPermissionUsage">Allows the holder to start the permission usage for an app. Should never be needed for normal apps.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permlab_highSamplingRateSensors">access sensor data at a high sampling rate</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+ <string name="permdesc_highSamplingRateSensors">Allows the app to sample sensor data at a rate greater than 200 Hz</string>
+
<!-- Policy administration -->
<!-- Title of policy access to limiting the user's password choices -->
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index ce4ee87..e7e049da 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -224,6 +224,9 @@
<item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
<item name="colorForeground">@color/foreground_device_default_dark</item>
<item name="colorForegroundInverse">@color/foreground_device_default_light</item>
+
+ <!-- Ripple style-->
+ <item name="rippleStyle">solid</item>
</style>
<style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase" />
diff --git a/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
index be382600..bc7be1b 100644
--- a/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
+++ b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
@@ -16,14 +16,8 @@
package android.provider;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.testng.Assert.assertThrows;
-import android.content.ContentValues;
-import android.os.Parcel;
-import android.provider.SimPhonebookContract.SimRecords.NameValidationResult;
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
@@ -71,50 +65,5 @@
SimPhonebookContract.ElementaryFiles.EF_ADN, -1)
);
}
-
- @Test
- public void nameValidationResult_isValid_validNames() {
- assertThat(new NameValidationResult("", "", 0, 1).isValid()).isTrue();
- assertThat(new NameValidationResult("a", "a", 1, 1).isValid()).isTrue();
- assertThat(new NameValidationResult("First Last", "First Last", 10, 10).isValid()).isTrue();
- assertThat(
- new NameValidationResult("First Last", "First Last", 10, 100).isValid()).isTrue();
- }
-
- @Test
- public void nameValidationResult_isValid_invalidNames() {
- assertThat(new NameValidationResult("", "", 0, 0).isValid()).isFalse();
- assertThat(new NameValidationResult("ab", "ab", 2, 1).isValid()).isFalse();
- NameValidationResult unsupportedCharactersResult = new NameValidationResult("A_b_c",
- "A b c", 5, 5);
- assertThat(unsupportedCharactersResult.isValid()).isFalse();
- assertThat(unsupportedCharactersResult.isSupportedCharacter(0)).isTrue();
- assertThat(unsupportedCharactersResult.isSupportedCharacter(1)).isFalse();
- assertThat(unsupportedCharactersResult.isSupportedCharacter(2)).isTrue();
- assertThat(unsupportedCharactersResult.isSupportedCharacter(3)).isFalse();
- assertThat(unsupportedCharactersResult.isSupportedCharacter(4)).isTrue();
- }
-
- @Test
- public void nameValidationResult_parcel() {
- ContentValues values = new ContentValues();
- values.put("name", "Name");
- values.put("phone_number", "123");
-
- NameValidationResult result;
- Parcel parcel = Parcel.obtain();
- try {
- parcel.writeParcelable(new NameValidationResult("name", "sanitized name", 1, 2), 0);
- parcel.setDataPosition(0);
- result = parcel.readParcelable(NameValidationResult.class.getClassLoader());
- } finally {
- parcel.recycle();
- }
-
- assertThat(result.getName()).isEqualTo("name");
- assertThat(result.getSanitizedName()).isEqualTo("sanitized name");
- assertThat(result.getEncodedLength()).isEqualTo(1);
- assertThat(result.getMaxEncodedLength()).isEqualTo(2);
- }
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 403dc65..fe4d0cf 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -316,6 +316,8 @@
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.ACCESS_LOWPAN_STATE"/>
<permission name="android.permission.BACKUP"/>
+ <!-- Needed for test only -->
+ <permission name="android.permission.BATTERY_PREDICTION"/>
<permission name="android.permission.BATTERY_STATS"/>
<permission name="android.permission.BIND_APPWIDGET"/>
<permission name="android.permission.CHANGE_APP_IDLE_STATE"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ded4a27..84da930 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1075,12 +1075,6 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
},
- "-856025122": {
- "message": "SURFACE transparentRegionHint=%s: %s",
- "level": "INFO",
- "group": "WM_SHOW_TRANSACTIONS",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"-855366859": {
"message": " merging children in from %s: %s",
"level": "VERBOSE",
@@ -2821,12 +2815,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "1217926207": {
- "message": "Activity not running, resuming next.",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"1219600119": {
"message": "addWindow: win=%s Callers=%s",
"level": "DEBUG",
@@ -3331,6 +3319,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/Task.java"
},
+ "1847414670": {
+ "message": "Activity not running or entered PiP, resuming next.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"1853793312": {
"message": "Notify removed startingWindow %s",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index b70fa0e..88cf96a 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -39,6 +39,7 @@
import android.view.NativeVectorDrawableAnimator;
import android.view.PixelCopy;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.animation.AnimationUtils;
@@ -314,6 +315,16 @@
}
/**
+ * Sets the SurfaceControl to be used internally inside render thread
+ * @hide
+ * @param surfaceControl The surface control to pass to render thread in hwui.
+ * If null, any previous references held in render thread will be discarded.
+ */
+ public void setSurfaceControl(@Nullable SurfaceControl surfaceControl) {
+ nSetSurfaceControl(mNativeProxy, surfaceControl != null ? surfaceControl.mNativeObject : 0);
+ }
+
+ /**
* Sets the parameters that can be used to control a render request for a
* {@link HardwareRenderer}. This is not thread-safe and must not be held on to for longer
* than a single frame request.
@@ -1216,6 +1227,8 @@
private static native void nSetSurface(long nativeProxy, Surface window, boolean discardBuffer);
+ private static native void nSetSurfaceControl(long nativeProxy, long nativeSurfaceControl);
+
private static native boolean nPause(long nativeProxy);
private static native void nSetStopped(long nativeProxy, boolean stopped);
diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
new file mode 100644
index 0000000..80f65f9
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
@@ -0,0 +1,284 @@
+/*
+ * 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.graphics.drawable;
+
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
+import android.graphics.animation.RenderNodeAnimator;
+import android.util.ArraySet;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.function.Consumer;
+
+/**
+ * @hide
+ */
+public final class RippleAnimationSession {
+ private static final int ENTER_ANIM_DURATION = 350;
+ private static final int EXIT_ANIM_OFFSET = ENTER_ANIM_DURATION;
+ private static final int EXIT_ANIM_DURATION = 350;
+ private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+ // Matches R.interpolator.fast_out_slow_in but as we have no context we can't just import that
+ private static final TimeInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
+
+ private Consumer<RippleAnimationSession> mOnSessionEnd;
+ private AnimationProperties<Float, Paint> mProperties;
+ private AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> mCanvasProperties;
+ private Runnable mOnUpdate;
+ private long mStartTime;
+ private boolean mForceSoftware;
+ private ArraySet<Animator> mActiveAnimations = new ArraySet(3);
+
+ RippleAnimationSession(@NonNull AnimationProperties<Float, Paint> properties,
+ boolean forceSoftware) {
+ mProperties = properties;
+ mForceSoftware = forceSoftware;
+ }
+
+ void end() {
+ for (Animator anim: mActiveAnimations) {
+ if (anim != null) anim.end();
+ }
+ mActiveAnimations.clear();
+ }
+
+ @NonNull RippleAnimationSession enter(Canvas canvas) {
+ if (isHwAccelerated(canvas)) {
+ enterHardware((RecordingCanvas) canvas);
+ } else {
+ enterSoftware();
+ }
+ mStartTime = System.nanoTime();
+ return this;
+ }
+
+ @NonNull RippleAnimationSession exit(Canvas canvas) {
+ if (isHwAccelerated(canvas)) exitHardware((RecordingCanvas) canvas);
+ else exitSoftware();
+ return this;
+ }
+
+ private void onAnimationEnd(Animator anim) {
+ mActiveAnimations.remove(anim);
+ }
+
+ @NonNull RippleAnimationSession setOnSessionEnd(
+ @Nullable Consumer<RippleAnimationSession> onSessionEnd) {
+ mOnSessionEnd = onSessionEnd;
+ return this;
+ }
+
+ RippleAnimationSession setOnAnimationUpdated(@Nullable Runnable run) {
+ mOnUpdate = run;
+ mProperties.setOnChange(mOnUpdate);
+ return this;
+ }
+
+ private boolean isHwAccelerated(Canvas canvas) {
+ return canvas.isHardwareAccelerated() && !mForceSoftware;
+ }
+
+ private void exitSoftware() {
+ ValueAnimator expand = ValueAnimator.ofFloat(.5f, 1f);
+ expand.setDuration(EXIT_ANIM_DURATION);
+ expand.setStartDelay(computeDelay());
+ expand.addUpdateListener(updatedAnimation -> {
+ notifyUpdate();
+ mProperties.getShader().setProgress((Float) expand.getAnimatedValue());
+ });
+ expand.addListener(new AnimatorListener(this) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ Consumer<RippleAnimationSession> onEnd = mOnSessionEnd;
+ if (onEnd != null) onEnd.accept(RippleAnimationSession.this);
+ }
+ });
+ expand.setInterpolator(LINEAR_INTERPOLATOR);
+ expand.start();
+ mActiveAnimations.add(expand);
+ }
+
+ private long computeDelay() {
+ long currentTime = System.nanoTime();
+ long timePassed = (currentTime - mStartTime) / 1_000_000;
+ long difference = EXIT_ANIM_OFFSET;
+ return Math.max(difference - timePassed, 0);
+ }
+ private void notifyUpdate() {
+ Runnable onUpdate = mOnUpdate;
+ if (onUpdate != null) onUpdate.run();
+ }
+
+ RippleAnimationSession setForceSoftwareAnimation(boolean forceSw) {
+ mForceSoftware = forceSw;
+ return this;
+ }
+
+
+ private void exitHardware(RecordingCanvas canvas) {
+ AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>>
+ props = getCanvasProperties();
+ RenderNodeAnimator exit =
+ new RenderNodeAnimator(props.getProgress(), 1f);
+ exit.setDuration(EXIT_ANIM_DURATION);
+ exit.addListener(new AnimatorListener(this) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ Consumer<RippleAnimationSession> onEnd = mOnSessionEnd;
+ if (onEnd != null) onEnd.accept(RippleAnimationSession.this);
+ }
+ });
+ exit.setTarget(canvas);
+ exit.setInterpolator(DECELERATE_INTERPOLATOR);
+
+ long delay = computeDelay();
+ exit.setStartDelay(delay);
+ exit.start();
+ mActiveAnimations.add(exit);
+ }
+
+ private void enterHardware(RecordingCanvas can) {
+ AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>>
+ props = getCanvasProperties();
+ RenderNodeAnimator expand =
+ new RenderNodeAnimator(props.getProgress(), .5f);
+ expand.setTarget(can);
+ expand.setDuration(ENTER_ANIM_DURATION);
+ expand.addListener(new AnimatorListener(this));
+ expand.setInterpolator(LINEAR_INTERPOLATOR);
+ expand.start();
+ mActiveAnimations.add(expand);
+ }
+
+ private void enterSoftware() {
+ ValueAnimator expand = ValueAnimator.ofFloat(0f, 0.5f);
+ expand.addUpdateListener(updatedAnimation -> {
+ notifyUpdate();
+ mProperties.getShader().setProgress((Float) expand.getAnimatedValue());
+ });
+ expand.addListener(new AnimatorListener(this));
+ expand.setInterpolator(LINEAR_INTERPOLATOR);
+ expand.start();
+ mActiveAnimations.add(expand);
+ }
+
+ @NonNull AnimationProperties<Float, Paint> getProperties() {
+ return mProperties;
+ }
+
+ @NonNull AnimationProperties getCanvasProperties() {
+ if (mCanvasProperties == null) {
+ mCanvasProperties = new AnimationProperties<>(
+ CanvasProperty.createFloat(mProperties.getX()),
+ CanvasProperty.createFloat(mProperties.getY()),
+ CanvasProperty.createFloat(mProperties.getMaxRadius()),
+ CanvasProperty.createPaint(mProperties.getPaint()),
+ CanvasProperty.createFloat(mProperties.getProgress()),
+ mProperties.getShader());
+ }
+ return mCanvasProperties;
+ }
+
+ private static class AnimatorListener implements Animator.AnimatorListener {
+ private final RippleAnimationSession mSession;
+
+ AnimatorListener(RippleAnimationSession session) {
+ mSession = session;
+ }
+ @Override
+ public void onAnimationStart(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mSession.onAnimationEnd(animation);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ }
+
+ static class AnimationProperties<FloatType, PaintType> {
+ private final FloatType mY;
+ private FloatType mProgress;
+ private FloatType mMaxRadius;
+ private final PaintType mPaint;
+ private final FloatType mX;
+ private final RippleShader mShader;
+ private Runnable mOnChange;
+
+ private void onChange() {
+ if (mOnChange != null) mOnChange.run();
+ }
+
+ private void setOnChange(Runnable onChange) {
+ mOnChange = onChange;
+ }
+
+ AnimationProperties(FloatType x, FloatType y, FloatType maxRadius,
+ PaintType paint, FloatType progress, RippleShader shader) {
+ mY = y;
+ mX = x;
+ mMaxRadius = maxRadius;
+ mPaint = paint;
+ mShader = shader;
+ mProgress = progress;
+ }
+
+ FloatType getProgress() {
+ return mProgress;
+ }
+
+ FloatType getX() {
+ return mX;
+ }
+
+ FloatType getY() {
+ return mY;
+ }
+
+ FloatType getMaxRadius() {
+ return mMaxRadius;
+ }
+
+ PaintType getPaint() {
+ return mPaint;
+ }
+
+ RippleShader getShader() {
+ return mShader;
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index bab80ce..5024875 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -16,6 +16,14 @@
package android.graphics.drawable;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -27,17 +35,21 @@
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
import android.graphics.Color;
+import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.Shader;
import android.os.Build;
import android.util.AttributeSet;
+import android.view.animation.LinearInterpolator;
import com.android.internal.R;
@@ -45,6 +57,9 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
import java.util.Arrays;
/**
@@ -94,6 +109,17 @@
* </pre>
*
* @attr ref android.R.styleable#RippleDrawable_color
+ *
+ * To change the ripple style, assign the value of "solid" or "patterned" to the android:rippleStyle
+ * attribute.
+ *
+ * <pre>
+ * <code><!-- A red ripple masked against an opaque rectangle. --/>
+ * <ripple android:rippleStyle="patterned">
+ * </ripple></code>
+ * </pre>
+ *
+ * @attr ref android.R.styleable#RippleDrawable_rippleStyle
*/
public class RippleDrawable extends LayerDrawable {
/**
@@ -102,6 +128,29 @@
*/
public static final int RADIUS_AUTO = -1;
+ /**
+ * Ripple style where a solid circle is drawn. This is also the default style
+ * @see #setRippleStyle(int)
+ */
+ public static final int STYLE_SOLID = 0;
+ /**
+ * Ripple style where a circle shape with a patterned,
+ * noisy interior expands from the hotspot to the bounds".
+ * @see #setRippleStyle(int)
+ */
+ public static final int STYLE_PATTERNED = 1;
+
+ /**
+ * Ripple drawing style
+ * @hide
+ */
+ @Retention(SOURCE)
+ @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+ @IntDef({STYLE_SOLID, STYLE_PATTERNED})
+ public @interface RippleStyle {
+ }
+
+ private static final int BACKGROUND_OPACITY_DURATION = 80;
private static final int MASK_UNKNOWN = -1;
private static final int MASK_NONE = 0;
private static final int MASK_CONTENT = 1;
@@ -109,6 +158,7 @@
/** The maximum number of ripples supported. */
private static final int MAX_RIPPLES = 10;
+ private static final LinearInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
private final Rect mTempRect = new Rect();
@@ -172,6 +222,14 @@
*/
private boolean mForceSoftware;
+ // Patterned
+ private float mTargetBackgroundOpacity;
+ private ValueAnimator mBackgroundAnimation;
+ private float mBackgroundOpacity;
+ private boolean mRunBackgroundAnimation;
+ private boolean mExitingAnimation;
+ private ArrayList<RippleAnimationSession> mRunningAnimations = new ArrayList<>();
+
/**
* Constructor used for drawable inflation.
*/
@@ -235,7 +293,7 @@
Arrays.fill(ripples, 0, count, null);
}
mExitingRipplesCount = 0;
-
+ mExitingAnimation = true;
// Always draw an additional "clean" frame after canceling animations.
invalidateSelf(false);
}
@@ -276,21 +334,37 @@
private void setRippleActive(boolean active) {
if (mRippleActive != active) {
mRippleActive = active;
+ }
+ if (mState.mRippleStyle == STYLE_SOLID) {
if (active) {
tryRippleEnter();
} else {
tryRippleExit();
}
+ } else {
+ if (active) {
+ startPatternedAnimation();
+ } else {
+ exitPatternedAnimation();
+ }
}
}
private void setBackgroundActive(boolean hovered, boolean focused, boolean pressed) {
- if (mBackground == null && (hovered || focused)) {
- mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
- mBackground.setup(mState.mMaxRadius, mDensity);
- }
- if (mBackground != null) {
- mBackground.setState(focused, hovered, pressed);
+ if (mState.mRippleStyle == STYLE_SOLID) {
+ if (mBackground == null && (hovered || focused)) {
+ mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
+ mBackground.setup(mState.mMaxRadius, mDensity);
+ }
+ if (mBackground != null) {
+ mBackground.setState(focused, hovered, pressed);
+ }
+ } else {
+ if (focused || hovered) {
+ enterPatternedBackgroundAnimation(focused, hovered);
+ } else {
+ exitPatternedBackgroundAnimation();
+ }
}
}
@@ -317,6 +391,9 @@
mRipple.onBoundsChange();
}
+ mState.mMaxRadius = mState.mMaxRadius <= 0 && mState.mRippleStyle != STYLE_SOLID
+ ? (int) computeRadius()
+ : mState.mMaxRadius;
invalidateSelf();
}
@@ -330,7 +407,11 @@
// If we just became visible, ensure the background and ripple
// visibilities are consistent with their internal states.
if (mRippleActive) {
- tryRippleEnter();
+ if (mState.mRippleStyle == STYLE_SOLID) {
+ tryRippleEnter();
+ } else {
+ invalidateSelf();
+ }
}
// Skip animations, just show the correct final states.
@@ -489,6 +570,8 @@
mState.mMaxRadius = a.getDimensionPixelSize(
R.styleable.RippleDrawable_radius, mState.mMaxRadius);
+
+ mState.mRippleStyle = a.getInteger(R.styleable.RippleDrawable_rippleStyle, STYLE_SOLID);
}
private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
@@ -535,9 +618,9 @@
@Override
public void setHotspot(float x, float y) {
+ mPendingX = x;
+ mPendingY = y;
if (mRipple == null || mBackground == null) {
- mPendingX = x;
- mPendingY = y;
mHasPending = true;
}
@@ -665,6 +748,14 @@
*/
@Override
public void draw(@NonNull Canvas canvas) {
+ if (mState.mRippleStyle == STYLE_SOLID) {
+ drawSolid(canvas);
+ } else {
+ drawPatterned(canvas);
+ }
+ }
+
+ private void drawSolid(Canvas canvas) {
pruneRipples();
// Clip to the dirty bounds, which will be the drawable bounds if we
@@ -681,6 +772,178 @@
canvas.restoreToCount(saveCount);
}
+ private void exitPatternedBackgroundAnimation() {
+ mTargetBackgroundOpacity = 0;
+ if (mBackgroundAnimation != null) mBackgroundAnimation.cancel();
+ // after cancel
+ mRunBackgroundAnimation = true;
+ invalidateSelf(false);
+ }
+
+ private void startPatternedAnimation() {
+ mRippleActive = true;
+ invalidateSelf(false);
+ }
+
+ private void exitPatternedAnimation() {
+ mExitingAnimation = true;
+ invalidateSelf(false);
+ }
+
+ private void enterPatternedBackgroundAnimation(boolean focused, boolean hovered) {
+ mBackgroundOpacity = 0;
+ mTargetBackgroundOpacity = focused ? .6f : hovered ? .2f : 0f;
+ if (mBackgroundAnimation != null) mBackgroundAnimation.cancel();
+ // after cancel
+ mRunBackgroundAnimation = true;
+ invalidateSelf(false);
+ }
+
+ private void startBackgroundAnimation() {
+ mRunBackgroundAnimation = false;
+ mBackgroundAnimation = ValueAnimator.ofFloat(mBackgroundOpacity, mTargetBackgroundOpacity);
+ mBackgroundAnimation.setInterpolator(LINEAR_INTERPOLATOR);
+ mBackgroundAnimation.setDuration(BACKGROUND_OPACITY_DURATION);
+ mBackgroundAnimation.addUpdateListener(update -> {
+ mBackgroundOpacity = (float) update.getAnimatedValue();
+ invalidateSelf(false);
+ });
+ mBackgroundAnimation.start();
+ }
+
+ private void drawPatterned(@NonNull Canvas canvas) {
+ final Rect bounds = getBounds();
+ final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ boolean useCanvasProps = shouldUseCanvasProps(canvas);
+ boolean changedHotspotBounds = !bounds.equals(mHotspotBounds);
+ if (isBounded()) {
+ canvas.clipRect(mHotspotBounds);
+ }
+ float x, y;
+ if (changedHotspotBounds) {
+ x = mHotspotBounds.exactCenterX();
+ y = mHotspotBounds.exactCenterY();
+ useCanvasProps = false;
+ } else {
+ x = mPendingX;
+ y = mPendingY;
+ }
+ boolean shouldAnimate = mRippleActive;
+ boolean shouldExit = mExitingAnimation;
+ mRippleActive = false;
+ mExitingAnimation = false;
+ getRipplePaint();
+ drawContent(canvas);
+ drawPatternedBackground(canvas);
+ if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) {
+ RippleAnimationSession.AnimationProperties<Float, Paint> properties =
+ createAnimationProperties(x, y);
+ mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps)
+ .setOnAnimationUpdated(useCanvasProps ? null : () -> invalidateSelf(false))
+ .setOnSessionEnd(session -> {
+ mRunningAnimations.remove(session);
+ })
+ .setForceSoftwareAnimation(!useCanvasProps)
+ .enter(canvas));
+ }
+ if (shouldExit) {
+ for (int i = 0; i < mRunningAnimations.size(); i++) {
+ RippleAnimationSession s = mRunningAnimations.get(i);
+ s.exit(canvas);
+ }
+ }
+ for (int i = 0; i < mRunningAnimations.size(); i++) {
+ RippleAnimationSession s = mRunningAnimations.get(i);
+ if (useCanvasProps) {
+ RippleAnimationSession.AnimationProperties<CanvasProperty<Float>,
+ CanvasProperty<Paint>>
+ p = s.getCanvasProperties();
+ RecordingCanvas can = (RecordingCanvas) canvas;
+ can.drawRipple(p.getX(), p.getY(), p.getMaxRadius(), p.getPaint(),
+ p.getProgress(), p.getShader());
+ } else {
+ RippleAnimationSession.AnimationProperties<Float, Paint> p =
+ s.getProperties();
+ float posX, posY;
+ if (changedHotspotBounds) {
+ posX = x;
+ posY = y;
+ if (p.getPaint().getShader() instanceof RippleShader) {
+ RippleShader shader = (RippleShader) p.getPaint().getShader();
+ shader.setOrigin(posX, posY);
+ }
+ } else {
+ posX = p.getX();
+ posY = p.getY();
+ }
+ float radius = p.getMaxRadius();
+ canvas.drawCircle(posX, posY, radius, p.getPaint());
+ }
+ }
+ canvas.restoreToCount(saveCount);
+ }
+
+ private void drawPatternedBackground(Canvas c) {
+ if (mRunBackgroundAnimation) {
+ startBackgroundAnimation();
+ }
+ if (mBackgroundOpacity == 0) return;
+ Paint p = mRipplePaint;
+ float newOpacity = mBackgroundOpacity;
+ final int origAlpha = p.getAlpha();
+ final int alpha = Math.min((int) (origAlpha * newOpacity + 0.5f), 255);
+ if (alpha > 0) {
+ ColorFilter origFilter = p.getColorFilter();
+ p.setColorFilter(mMaskColorFilter);
+ p.setAlpha(alpha);
+ Rect b = mHotspotBounds;
+ c.drawCircle(b.centerX(), b.centerY(), mState.mMaxRadius, p);
+ p.setAlpha(origAlpha);
+ p.setColorFilter(origFilter);
+ }
+ }
+
+ private float computeRadius() {
+ Rect b = getDirtyBounds();
+ float gap = 0;
+ float radius = (float) Math.sqrt(b.width() * b.width() + b.height() * b.height()) / 2 + gap;
+ return radius;
+ }
+
+ @NonNull
+ private RippleAnimationSession.AnimationProperties<Float, Paint> createAnimationProperties(
+ float x, float y) {
+ Paint p = new Paint(mRipplePaint);
+ float radius = mState.mMaxRadius;
+ RippleAnimationSession.AnimationProperties<Float, Paint> properties;
+ RippleShader shader = new RippleShader();
+ int color = mMaskColorFilter == null
+ ? mState.mColor.getColorForState(getState(), Color.BLACK)
+ : mMaskColorFilter.getColor();
+ color = color | 0xFF000000;
+ shader.setColor(color);
+ shader.setOrigin(x, y);
+ shader.setRadius(radius);
+ shader.setProgress(.0f);
+ properties = new RippleAnimationSession.AnimationProperties<>(
+ x, y, radius, p, 0f,
+ shader);
+ if (mMaskShader == null) {
+ shader.setHasMask(false);
+ } else {
+ shader.setShader(mMaskShader);
+ shader.setHasMask(true);
+ }
+ p.setShader(shader);
+ p.setColorFilter(null);
+ p.setColor(color);
+ return properties;
+ }
+
+ private boolean shouldUseCanvasProps(Canvas c) {
+ return !mForceSoftware && c.isHardwareAccelerated();
+ }
+
@Override
public void invalidateSelf() {
invalidateSelf(true);
@@ -774,18 +1037,23 @@
// Draw the appropriate mask anchored to (0,0).
final int left = bounds.left;
final int top = bounds.top;
- mMaskCanvas.translate(-left, -top);
+ if (mState.mRippleStyle == STYLE_SOLID) {
+ mMaskCanvas.translate(-left, -top);
+ }
if (maskType == MASK_EXPLICIT) {
drawMask(mMaskCanvas);
} else if (maskType == MASK_CONTENT) {
drawContent(mMaskCanvas);
}
- mMaskCanvas.translate(left, top);
+ if (mState.mRippleStyle == STYLE_SOLID) {
+ mMaskCanvas.translate(left, top);
+ }
}
private int getMaskType() {
if (mRipple == null && mExitingRipplesCount <= 0
- && (mBackground == null || !mBackground.isVisible())) {
+ && (mBackground == null || !mBackground.isVisible())
+ && mState.mRippleStyle == STYLE_SOLID) {
// We might need a mask later.
return MASK_UNKNOWN;
}
@@ -874,7 +1142,7 @@
updateMaskShaderIfNeeded();
// Position the shader to account for canvas translation.
- if (mMaskShader != null) {
+ if (mMaskShader != null && mState.mRippleStyle == STYLE_SOLID) {
final Rect bounds = getBounds();
mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
mMaskShader.setLocalMatrix(mMaskMatrix);
@@ -973,6 +1241,31 @@
return this;
}
+ /**
+ * Sets the visual style of the ripple.
+ *
+ * @see #STYLE_SOLID
+ * @see #STYLE_PATTERNED
+ *
+ * @param style The style of the ripple
+ */
+ public void setRippleStyle(@RippleStyle int style) throws IllegalArgumentException {
+ if (style == STYLE_SOLID || style == STYLE_PATTERNED) {
+ mState.mRippleStyle = style;
+ } else {
+ throw new IllegalArgumentException("Invalid style value " + style);
+ }
+ }
+
+ /**
+ * Get the current ripple style
+ * @return Ripple style
+ */
+ public @RippleStyle int getRippleStyle() {
+ return mState.mRippleStyle;
+ }
+
+
@Override
RippleState createConstantState(LayerState state, Resources res) {
return new RippleState(state, this, res);
@@ -983,6 +1276,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA);
int mMaxRadius = RADIUS_AUTO;
+ int mRippleStyle = STYLE_SOLID;
public RippleState(LayerState orig, RippleDrawable owner, Resources res) {
super(orig, owner, res);
@@ -992,6 +1286,7 @@
mTouchThemeAttrs = origs.mTouchThemeAttrs;
mColor = origs.mColor;
mMaxRadius = origs.mMaxRadius;
+ mRippleStyle = origs.mRippleStyle;
if (origs.mDensity != mDensity) {
applyDensityScaling(orig.mDensity, mDensity);
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
new file mode 100644
index 0000000..500efdd
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.graphics.Color;
+import android.graphics.RuntimeShader;
+import android.graphics.Shader;
+
+final class RippleShader extends RuntimeShader {
+ private static final String SHADER = "uniform float2 in_origin;\n"
+ + "uniform float in_maxRadius;\n"
+ + "uniform float in_progress;\n"
+ + "uniform float in_hasMask;\n"
+ + "uniform float4 in_color;\n"
+ + "uniform shader in_shader;\n"
+ + "float dist2(float2 p0, float2 pf) { return sqrt((pf.x - p0.x) * (pf.x - p0.x) + "
+ + "(pf.y - p0.y) * (pf.y - p0.y)); }\n"
+ + "float mod2(float a, float b) { return a - (b * floor(a / b)); }\n"
+ + "float rand(float2 src) { return fract(sin(dot(src.xy, float2(12.9898, 78.233))) * "
+ + "43758.5453123); }\n"
+ + "float4 main(float2 p)\n"
+ + "{\n"
+ + " float fraction = in_progress;\n"
+ + " float2 fragCoord = p;//sk_FragCoord.xy;\n"
+ + " float maxDist = in_maxRadius;\n"
+ + " float fragDist = dist2(in_origin, fragCoord.xy);\n"
+ + " float circleRadius = maxDist * fraction;\n"
+ + " float colorVal = (fragDist - circleRadius) / maxDist;\n"
+ + " float d = fragDist < circleRadius \n"
+ + " ? 1. - abs(colorVal * 3. * smoothstep(0., 1., fraction)) \n"
+ + " : 1. - abs(colorVal * 5.);\n"
+ + " d = smoothstep(0., 1., d);\n"
+ + " float divider = 2.;\n"
+ + " float x = floor(fragCoord.x / divider);\n"
+ + " float y = floor(fragCoord.y / divider);\n"
+ + " float density = .95;\n"
+ + " d = rand(float2(x, y)) > density ? d : d * .2;\n"
+ + " d = d * rand(float2(fraction, x * y));\n"
+ + " float alpha = 1. - pow(fraction, 2.);\n"
+ + " if (in_hasMask != 0.) {return sample(in_shader).a * in_color * d * alpha;}\n"
+ + " return in_color * d * alpha;\n"
+ + "}\n";
+
+ RippleShader() {
+ super(SHADER, false);
+ }
+
+ public void setShader(@NonNull Shader s) {
+ setInputShader("in_shader", s);
+ }
+
+ public void setRadius(float radius) {
+ setUniform("in_maxRadius", radius);
+ }
+
+ public void setOrigin(float x, float y) {
+ setUniform("in_origin", new float[] {x, y});
+ }
+
+ public void setProgress(float progress) {
+ setUniform("in_progress", progress);
+ }
+
+ public void setHasMask(boolean hasMask) {
+ setUniform("in_hasMask", hasMask ? 1 : 0);
+ }
+
+ public void setColor(@ColorInt int colorIn) {
+ Color color = Color.valueOf(colorIn);
+ this.setUniform("in_color", new float[] {color.red(),
+ color.green(), color.blue(), color.alpha()});
+ }
+}
diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java
index 7bd5817..d1fe2cd 100644
--- a/graphics/java/android/graphics/fonts/FontVariationAxis.java
+++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java
@@ -23,6 +23,7 @@
import android.text.TextUtils;
import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
@@ -189,6 +190,17 @@
return TextUtils.join(",", axes);
}
+ /**
+ * Stringify the array of FontVariationAxis.
+ * @hide
+ */
+ public static @NonNull String toFontVariationSettings(@Nullable List<FontVariationAxis> axes) {
+ if (axes == null) {
+ return "";
+ }
+ return TextUtils.join(",", axes);
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (o == this) {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index e92eaca..2b0d7e5 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -236,6 +236,47 @@
* keyStore.load(null);
* key = (SecretKey) keyStore.getKey("key2", null);
* }</pre>
+ *
+ * <p><h3 id="example:ecdh">Example: EC key for ECDH key agreement</h3>
+ * This example illustrates how to generate an elliptic curve key pair, used to establish a shared
+ * secret with another party using ECDH key agreement.
+ * <pre> {@code
+ * KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
+ * KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
+ * keyPairGenerator.initialize(
+ * new KeyGenParameterSpec.Builder(
+ * "eckeypair",
+ * KeyProperties.PURPOSE_AGREE_KEY)
+ * .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
+ * .build());
+ * KeyPair myKeyPair = keyPairGenerator.generateKeyPair();
+ *
+ * // Exchange public keys with server. A new ephemeral key MUST be used for every message.
+ * PublicKey serverEphemeralPublicKey; // Ephemeral key received from server.
+ *
+ * // Create a shared secret based on our private key and the other party's public key.
+ * KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "AndroidKeyStore");
+ * keyAgreement.init(myKeyPair.getPrivate());
+ * keyAgreement.doPhase(serverEphemeralPublicKey, true);
+ * byte[] sharedSecret = keyAgreement.generateSecret();
+ *
+ * // sharedSecret cannot safely be used as a key yet. We must run it through a key derivation
+ * // function with some other data: "salt" and "info". Salt is an optional random value,
+ * // omitted in this example. It's good practice to include both public keys and any other
+ * // key negotiation data in info. Here we use the public keys and a label that indicates
+ * // messages encrypted with this key are coming from the server.
+ * byte[] salt = {};
+ * ByteArrayOutputStream info = new ByteArrayOutputStream();
+ * info.write("ECDH secp256r1 AES-256-GCM-SIV\0".getBytes(StandardCharsets.UTF_8));
+ * info.write(myKeyPair.getPublic().getEncoded());
+ * info.write(serverEphemeralPublicKey.getEncoded());
+ *
+ * // This example uses the Tink library and the HKDF key derivation function.
+ * AesGcmSiv key = new AesGcmSiv(Hkdf.computeHkdf(
+ * "HMACSHA256", sharedSecret, salt, info.toByteArray(), 32));
+ * byte[] associatedData = {};
+ * return key.decrypt(ciphertext, associatedData);
+ * }
*/
public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs {
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 3ebca6a..293ab05 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -100,6 +100,15 @@
/**
* Purpose of key: creating a shared ECDH secret through key agreement.
+ *
+ * <p>A key having this purpose can be combined with the elliptic curve public key of another
+ * party to establish a shared secret over an insecure channel. It should be used as a
+ * parameter to {@link javax.crypto.KeyAgreement#init(java.security.Key)} (a complete example is
+ * available <a
+ * href="{@docRoot}reference/android/security/keystore/KeyGenParameterSpec#example:ecdh"
+ * >here</a>).
+ * See <a href="https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman">this
+ * article</a> for a more detailed explanation.
*/
public static final int PURPOSE_AGREE_KEY = 1 << 6;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
index 0bf6965..5c91cf41 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
@@ -72,7 +72,7 @@
private List<ExtensionDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
List<ExtensionDisplayFeature> features = new ArrayList<>();
- int displayId = activity.getDisplayId();
+ int displayId = activity.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) {
Log.w(TAG, "This sample doesn't support display features on secondary displays");
return features;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 1094a0e..d3700f8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -84,7 +84,7 @@
private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>();
- int displayId = activity.getDisplayId();
+ int displayId = activity.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) {
Log.w(TAG, "This sample doesn't support display features on secondary displays");
return features;
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 96e0559..0540aee 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -71,27 +71,6 @@
"$(locations :wm_shell-sources)",
out: ["wm_shell_protolog.json"],
}
-
-filegroup {
- name: "wm_shell_protolog.json",
- srcs: ["res/raw/wm_shell_protolog.json"],
-}
-
-genrule {
- name: "checked-wm_shell_protolog.json",
- srcs: [
- ":generate-wm_shell_protolog.json",
- ":wm_shell_protolog.json",
- ],
- cmd: "cp $(location :generate-wm_shell_protolog.json) $(out) && " +
- "{ ! (diff $(out) $(location :wm_shell_protolog.json) | grep -q '^<') || " +
- "{ echo -e '\\n\\n################################################################\\n#\\n" +
- "# ERROR: ProtoLog viewer config is stale. To update it, run:\\n#\\n" +
- "# cp $(location :generate-wm_shell_protolog.json) " +
- "$(location :wm_shell_protolog.json)\\n#\\n" +
- "################################################################\\n\\n' >&2 && false; } }",
- out: ["wm_shell_protolog.json"],
-}
// End ProtoLog
java_library {
@@ -115,6 +94,9 @@
resource_dirs: [
"res",
],
+ java_resources: [
+ ":generate-wm_shell_protolog.json"
+ ],
static_libs: [
"androidx.appcompat_appcompat",
"androidx.arch.core_core-runtime",
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
new file mode 100644
index 0000000..cd31531
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.wm.shell.sizecompatui.SizeCompatRestartButton
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageButton
+ android:id="@+id/size_compat_restart_button"
+ android:layout_width="@dimen/size_compat_button_size"
+ android:layout_height="@dimen/size_compat_button_size"
+ android:layout_gravity="center"
+ android:src="@drawable/size_compat_restart_button"
+ android:contentDescription="@string/restart_button_description"/>
+
+</com.android.wm.shell.sizecompatui.SizeCompatRestartButton>
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
deleted file mode 100644
index 9c3d84e..0000000
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ /dev/null
@@ -1,286 +0,0 @@
-{
- "version": "1.0.0",
- "messages": {
- "-2076257741": {
- "message": "Transition requested: %s %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
- },
- "-1683614271": {
- "message": "Existing task: id=%d component=%s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
- },
- "-1671119352": {
- "message": " Delegate animation for %s to %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
- },
- "-1501874464": {
- "message": "Fullscreen Task Appeared: #%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
- },
- "-1382704050": {
- "message": "Display removed: %d",
- "level": "VERBOSE",
- "group": "WM_SHELL_DRAG_AND_DROP",
- "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
- },
- "-1362429294": {
- "message": "%s onTaskAppeared Primary taskId=%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
- },
- "-1340279385": {
- "message": "Remove listener=%s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
- },
- "-1325223370": {
- "message": "Task appeared taskId=%d listener=%s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
- },
- "-1312360667": {
- "message": "createRootTask() displayId=%d winMode=%d listener=%s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
- },
- "-1308483871": {
- "message": " try handler %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
- },
- "-1297259344": {
- "message": " animated by %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
- },
- "-1269886472": {
- "message": "Transition %s doesn't have explicit remote, search filters for match for %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
- },
- "-1006733970": {
- "message": "Display added: %d",
- "level": "VERBOSE",
- "group": "WM_SHELL_DRAG_AND_DROP",
- "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
- },
- "-1000962629": {
- "message": "Animate bounds: from=%s to=%s",
- "level": "VERBOSE",
- "group": "WM_SHELL_DRAG_AND_DROP",
- "at": "com\/android\/wm\/shell\/draganddrop\/DropOutlineDrawable.java"
- },
- "-880817403": {
- "message": "Task vanished taskId=%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
- },
- "-742394458": {
- "message": "pair task1=%d task2=%d in AppPair=%s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/apppairs\/AppPair.java"
- },
- "-710770147": {
- "message": "Add target: %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_DRAG_AND_DROP",
- "at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
- },
- "-298656957": {
- "message": "%s onTaskAppeared unknown taskId=%d winMode=%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
- },
- "-234284913": {
- "message": "unpair taskId=%d pair=%s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/apppairs\/AppPairsController.java"
- },
- "138343607": {
- "message": " try firstHandler %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
- },
- "157713005": {
- "message": "Task info changed taskId=%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
- },
- "214412327": {
- "message": "RemoteTransition directly requested for %s: %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
- },
- "274140888": {
- "message": "Animate alpha: from=%d to=%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_DRAG_AND_DROP",
- "at": "com\/android\/wm\/shell\/draganddrop\/DropOutlineDrawable.java"
- },
- "325110414": {
- "message": "Transition animations finished, notifying core %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
- },
- "375908576": {
- "message": "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
- "level": "VERBOSE",
- "group": "WM_SHELL_DRAG_AND_DROP",
- "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
- },
- "410592459": {
- "message": "Invalid root leash (%s): %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
- },
- "473543554": {
- "message": "%s onTaskAppeared Supported",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
- },
- "481673835": {
- "message": "addListenerForTaskId taskId=%s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
- },
- "564235578": {
- "message": "Fullscreen Task Vanished: #%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
- },
- "580605218": {
- "message": "Registering organizer",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
- },
- "707170340": {
- "message": " animated by firstHandler",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
- },
- "900599280": {
- "message": "Can't pair unresizeable tasks task1.isResizeable=%b task1.isResizeable=%b",
- "level": "ERROR",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/apppairs\/AppPair.java"
- },
- "950299522": {
- "message": "taskId %d isn't isn't in an app-pair.",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/apppairs\/AppPairsController.java"
- },
- "980952660": {
- "message": "Task root back pressed taskId=%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
- },
- "982027396": {
- "message": "%s onTaskAppeared Secondary taskId=%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
- },
- "990371881": {
- "message": " Checking filter %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
- },
- "1070270131": {
- "message": "onTransitionReady %s: %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TRANSITIONS",
- "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
- },
- "1079041527": {
- "message": "incrementPool size=%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
- },
- "1184615936": {
- "message": "Set drop target window visibility: displayId=%d visibility=%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_DRAG_AND_DROP",
- "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
- },
- "1481772149": {
- "message": "Current target: %s",
- "level": "VERBOSE",
- "group": "WM_SHELL_DRAG_AND_DROP",
- "at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
- },
- "1862198614": {
- "message": "Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f",
- "level": "VERBOSE",
- "group": "WM_SHELL_DRAG_AND_DROP",
- "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
- },
- "1891981945": {
- "message": "release entry.taskId=%s listener=%s size=%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
- },
- "1990759023": {
- "message": "addListenerForType types=%s listener=%s",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
- },
- "2006473416": {
- "message": "acquire entry.taskId=%s listener=%s size=%d",
- "level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
- },
- "2057038970": {
- "message": "Display changed: %d",
- "level": "VERBOSE",
- "group": "WM_SHELL_DRAG_AND_DROP",
- "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
- }
- },
- "groups": {
- "WM_SHELL_DRAG_AND_DROP": {
- "tag": "WindowManagerShell"
- },
- "WM_SHELL_TASK_ORG": {
- "tag": "WindowManagerShell"
- },
- "WM_SHELL_TRANSITIONS": {
- "tag": "WindowManagerShell"
- }
- }
-}
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index cb2aada..583964b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -174,6 +174,9 @@
<!-- The width/height of the icon view on staring surface. -->
<dimen name="starting_surface_icon_size">108dp</dimen>
+ <!-- The width/height of the size compat restart button. -->
+ <dimen name="size_compat_button_size">48dp</dimen>
+
<!-- The width of the brand image on staring surface. -->
<dimen name="starting_surface_brand_image_width">200dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index afe523a..6984ea45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -95,9 +95,16 @@
}
@Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ if (!mLeashByTaskId.contains(taskId)) {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ b.setParent(mLeashByTaskId.get(taskId));
+ }
+
+ @Override
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
pw.println(innerPrefix + mLeashByTaskId.size() + " Tasks");
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 0372417..efc55c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -82,6 +82,16 @@
default void onTaskInfoChanged(RunningTaskInfo taskInfo) {}
default void onTaskVanished(RunningTaskInfo taskInfo) {}
default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
+ /** Whether this task listener supports size compat UI. */
+ default boolean supportSizeCompatUI() {
+ // All TaskListeners should support size compat except PIP.
+ return true;
+ }
+ /** Attaches the a child window surface to the task surface. */
+ default void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ throw new IllegalStateException(
+ "This task listener doesn't support child surface attachment.");
+ }
default void dump(@NonNull PrintWriter pw, String prefix) {};
}
@@ -358,8 +368,10 @@
return;
}
- // The task is vanished, notify to remove size compat UI on this Task if there is any.
- if (taskListener == null) {
+ // The task is vanished or doesn't support size compat UI, notify to remove size compat UI
+ // on this Task if there is any.
+ if (taskListener == null || !taskListener.supportSizeCompatUI()
+ || !taskInfo.topActivityInSizeCompat) {
mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
null /* taskConfig */, null /* sizeCompatActivity*/,
null /* taskListener */);
@@ -367,10 +379,7 @@
}
mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
- taskInfo.configuration.windowConfiguration.getBounds(),
- // null if the top activity not in size compat.
- taskInfo.topActivityInSizeCompat ? taskInfo.topActivityToken : null,
- taskListener);
+ taskInfo.configuration, taskInfo.topActivityToken, taskListener);
}
private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index bb8a973..5992447 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -301,6 +301,14 @@
}
@Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ if (mTaskInfo.taskId != taskId) {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ b.setParent(mTaskLeash);
+ }
+
+ @Override
public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index bab5140..79f9dcd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -214,6 +214,19 @@
}
@Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ if (getRootTaskId() == taskId) {
+ b.setParent(mRootTaskLeash);
+ } else if (getTaskId1() == taskId) {
+ b.setParent(mTaskLeash1);
+ } else if (getTaskId2() == taskId) {
+ b.setParent(mTaskLeash2);
+ } else {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ }
+
+ @Override
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
index 5a493c2..0552601 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
@@ -323,6 +323,14 @@
}
@Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ if (!mLeashByTaskId.contains(taskId)) {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ b.setParent(mLeashByTaskId.get(taskId));
+ }
+
+ @Override
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 3064af6..71331df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -66,7 +66,6 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.transition.Transitions;
@@ -585,6 +584,12 @@
}
@Override
+ public boolean supportSizeCompatUI() {
+ // PIP doesn't support size compat.
+ return false;
+ }
+
+ @Override
public void onFixedRotationStarted(int displayId, int newRotation) {
mNextRotation = newRotation;
mWaitForFixedRotation = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index ae53005..725f87d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -375,17 +375,29 @@
}
/**
- * Hides the menu activity.
+ * Hides the menu view.
*/
public void hideMenu() {
+ hideMenu(true /* animate */, true /* resize */);
+ }
+
+ /**
+ * Hides the menu view.
+ *
+ * @param animate whether to animate the menu fadeout
+ * @param resize whether or not to resize the PiP with the state change
+ */
+ public void hideMenu(boolean animate, boolean resize) {
final boolean isMenuVisible = isMenuVisible();
if (DEBUG) {
Log.d(TAG, "hideMenu() state=" + mMenuState
+ " isMenuVisible=" + isMenuVisible
+ + " animate=" + animate
+ + " resize=" + resize
+ " callers=\n" + Debug.getCallers(5, " "));
}
if (isMenuVisible) {
- mPipMenuView.hideMenu();
+ mPipMenuView.hideMenu(animate, resize);
}
}
@@ -404,15 +416,6 @@
}
/**
- * Preemptively mark the menu as invisible, used when we are directly manipulating the pinned
- * stack and don't want to trigger a resize which can animate the stack in a conflicting way
- * (ie. when manually expanding or dismissing).
- */
- public void hideMenuWithoutResize() {
- onMenuStateChanged(MENU_STATE_NONE, false /* resize */, null /* callback */);
- }
-
- /**
* Sets the menu actions to the actions provided by the current PiP menu.
*/
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 962c467..1cf3a48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -89,7 +89,6 @@
private static final boolean ENABLE_RESIZE_HANDLE = false;
private int mMenuState;
- private boolean mResize = true;
private boolean mAllowMenuTimeout = true;
private boolean mAllowTouches = true;
@@ -329,16 +328,21 @@
hideMenu(null);
}
+ void hideMenu(boolean animate, boolean resize) {
+ hideMenu(null, true /* notifyMenuVisibility */, animate, resize);
+ }
+
void hideMenu(Runnable animationEndCallback) {
- hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */);
+ hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */,
+ true /* resize */);
}
private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
- boolean animate) {
+ boolean animate, boolean resize) {
if (mMenuState != MENU_STATE_NONE) {
cancelDelayedHide();
if (notifyMenuVisibility) {
- notifyMenuStateChange(MENU_STATE_NONE, mResize, null);
+ notifyMenuStateChange(MENU_STATE_NONE, resize, null);
}
mMenuContainerAnimator = new AnimatorSet();
ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
@@ -469,7 +473,8 @@
private void expandPip() {
// Do not notify menu visibility when hiding the menu, the controller will do this when it
// handles the message
- hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */);
+ hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */,
+ true /* resize */);
}
private void dismissPip() {
@@ -479,7 +484,8 @@
final boolean animate = mMenuState != MENU_STATE_CLOSE;
// Do not notify menu visibility when hiding the menu, the controller will do this when it
// handles the message
- hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate);
+ hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate,
+ true /* resize */);
}
private void showSettings() {
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 b19dcae..eae8945 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
@@ -17,8 +17,7 @@
package com.android.wm.shell.pip.phone;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -325,7 +324,7 @@
+ " callers=\n" + Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
- mMenuController.hideMenuWithoutResize();
+ mMenuController.hideMenu(false /* animate */, false /* resize */);
mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION);
}
@@ -338,7 +337,7 @@
Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
- mMenuController.hideMenuWithoutResize();
+ mMenuController.hideMenu(true /* animate*/, false /* resize */);
mPipTaskOrganizer.removePip();
}
@@ -371,9 +370,9 @@
/**
* Stash PiP to the closest edge. We set velocityY to 0 to limit pure horizontal motion.
*/
- void stashToEdge(float velocityX, @Nullable Runnable postBoundsUpdateCallback) {
- mPipBoundsState.setStashed(velocityX < 0 ? STASH_TYPE_LEFT : STASH_TYPE_RIGHT);
- movetoTarget(velocityX, 0 /* velocityY */, postBoundsUpdateCallback, true /* isStash */);
+ void stashToEdge(float velX, float velY, @Nullable Runnable postBoundsUpdateCallback) {
+ velY = mPipBoundsState.getStashedState() == STASH_TYPE_NONE ? 0 : velY;
+ movetoTarget(velX, velY, postBoundsUpdateCallback, true /* isStash */);
}
private void movetoTarget(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 1ef9ffa..78ee186 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -106,7 +106,7 @@
// For pinch-resize
private boolean mThresholdCrossed0;
private boolean mThresholdCrossed1;
- private boolean mUsingPinchToZoom = false;
+ private boolean mUsingPinchToZoom = true;
private float mAngle = 0;
int mFirstIndex = -1;
int mSecondIndex = -1;
@@ -139,7 +139,7 @@
mEnablePinchResize = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
PIP_PINCH_RESIZE,
- /* defaultValue = */ false);
+ /* defaultValue = */ true);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
mMainExecutor,
new DeviceConfig.OnPropertiesChangedListener() {
@@ -147,7 +147,7 @@
public void onPropertiesChanged(DeviceConfig.Properties properties) {
if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) {
mEnablePinchResize = properties.getBoolean(
- PIP_PINCH_RESIZE, /* defaultValue = */ false);
+ PIP_PINCH_RESIZE, /* defaultValue = */ true);
}
}
});
@@ -516,8 +516,8 @@
}
if (mThresholdCrossed) {
if (mPhonePipMenuController.isMenuVisible()) {
- mPhonePipMenuController.hideMenuWithoutResize();
- mPhonePipMenuController.hideMenu();
+ mPhonePipMenuController.hideMenu(false /* animate */,
+ false /* resize */);
}
final Rect currentPipBounds = mPipBoundsState.getBounds();
mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
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 e69c6f2..afc7b52 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
@@ -19,7 +19,9 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
@@ -63,8 +65,9 @@
* the PIP.
*/
public class PipTouchHandler {
+ @VisibleForTesting static final float MINIMUM_SIZE_PERCENT = 0.4f;
+
private static final String TAG = "PipTouchHandler";
- private static final float MINIMUM_SIZE_PERCENT = 0.4f;
private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
// Allow PIP to resize to a slightly bigger state upon touch
@@ -809,7 +812,6 @@
}
if (touchState.startedDragging()) {
- mPipBoundsState.setStashed(STASH_TYPE_NONE);
mSavedSnapFraction = -1f;
mPipDismissTargetHandler.showDismissTargetMaybe();
}
@@ -862,10 +864,10 @@
// Reset the touch state on up before the fling settles
mTouchState.reset();
- if (mEnableStash && !mPipBoundsState.isStashed()
- && shouldStash(vel, getPossiblyMotionBounds())) {
- mMotionHelper.stashToEdge(vel.x, this::stashEndAction /* endAction */);
+ if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
+ mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */);
} else {
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
mMotionHelper.flingToSnapTarget(vel.x, vel.y,
this::flingEndAction /* endAction */);
}
@@ -876,6 +878,9 @@
< mPipBoundsState.getMaxSize().x
&& mPipBoundsState.getBounds().height()
< mPipBoundsState.getMaxSize().y;
+ if (mMenuController.isMenuVisible()) {
+ mMenuController.hideMenu(false /* animate */, false /* resize */);
+ }
if (toExpand) {
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
animateToMaximizedState(null);
@@ -916,6 +921,11 @@
&& mPipExclusionBoundsChangeListener.get() != null) {
mPipExclusionBoundsChangeListener.get().accept(mPipBoundsState.getBounds());
}
+ if (mPipBoundsState.getBounds().left < 0) {
+ mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+ } else {
+ mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+ }
}
private void flingEndAction() {
@@ -933,12 +943,12 @@
private boolean shouldStash(PointF vel, Rect motionBounds) {
// If user flings the PIP window above the minimum velocity, stash PIP.
- // Only allow stashing to the edge if the user starts dragging the PIP from the
- // opposite edge.
+ // Only allow stashing to the edge if PIP wasn't previously stashed on the opposite
+ // edge.
final boolean stashFromFlingToEdge = ((vel.x < -mStashVelocityThreshold
- && mDownSavedFraction > 1f && mDownSavedFraction < 2f)
+ && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT)
|| (vel.x > mStashVelocityThreshold
- && mDownSavedFraction > 3f && mDownSavedFraction < 4f));
+ && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT));
// If User releases the PIP window while it's out of the display bounds, put
// PIP into stashed mode.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 4f4e7da..2b0a0cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -26,9 +26,9 @@
public enum ShellProtoLogGroup implements IProtoLogGroup {
// NOTE: Since we enable these from the same WM ShellCommand, these names should not conflict
// with those in the framework ProtoLogGroup
- WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
- WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index 66ecf45..552ebde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -28,6 +28,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import org.json.JSONException;
@@ -109,10 +110,10 @@
return sServiceInstance;
}
- public int startTextLogging(Context context, String[] groups, PrintWriter pw) {
- try {
- mViewerConfig.loadViewerConfig(
- context.getResources().openRawResource(R.raw.wm_shell_protolog));
+ public int startTextLogging(String[] groups, PrintWriter pw) {
+ try (InputStream is =
+ getClass().getClassLoader().getResourceAsStream("wm_shell_protolog.json")){
+ mViewerConfig.loadViewerConfig(is);
return setLogging(true /* setTextLogging */, true, pw, groups);
} catch (IOException e) {
Log.i(TAG, "Unable to load log definitions: IOException while reading "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
index e47e1ac..9094d7d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
@@ -16,101 +16,80 @@
package com.android.wm.shell.sizecompatui;
-import android.app.ActivityClient;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.Gravity;
+import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
+import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
/** Button to restart the size compat activity. */
-class SizeCompatRestartButton extends ImageButton implements View.OnClickListener,
+public class SizeCompatRestartButton extends FrameLayout implements View.OnClickListener,
View.OnLongClickListener {
- private static final String TAG = "SizeCompatRestartButton";
- final WindowManager.LayoutParams mWinParams;
- final boolean mShouldShowHint;
- final int mDisplayId;
- final int mPopupOffsetX;
- final int mPopupOffsetY;
+ private SizeCompatUILayout mLayout;
+ private ImageButton mRestartButton;
+ @VisibleForTesting
+ PopupWindow mShowingHint;
+ private WindowManager.LayoutParams mWinParams;
- private IBinder mLastActivityToken;
- private PopupWindow mShowingHint;
-
- SizeCompatRestartButton(Context context, int displayId, boolean hasShownHint) {
+ public SizeCompatRestartButton(@NonNull Context context) {
super(context);
- mDisplayId = displayId;
- mShouldShowHint = !hasShownHint;
- final Drawable drawable = context.getDrawable(R.drawable.size_compat_restart_button);
- setImageDrawable(drawable);
- setContentDescription(context.getString(R.string.restart_button_description));
+ }
- final int drawableW = drawable.getIntrinsicWidth();
- final int drawableH = drawable.getIntrinsicHeight();
- mPopupOffsetX = drawableW / 2;
- mPopupOffsetY = drawableH * 2;
+ public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+ public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ void inject(SizeCompatUILayout layout) {
+ mLayout = layout;
+ mWinParams = layout.getWindowLayoutParams();
+ }
+
+ void remove() {
+ dismissHint();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mRestartButton = findViewById(R.id.size_compat_restart_button);
final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
final GradientDrawable mask = new GradientDrawable();
mask.setShape(GradientDrawable.OVAL);
mask.setColor(color);
- setBackground(new RippleDrawable(color, null /* content */, mask));
- setOnClickListener(this);
- setOnLongClickListener(this);
-
- mWinParams = new WindowManager.LayoutParams();
- mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection());
- mWinParams.width = drawableW * 2;
- mWinParams.height = drawableH * 2;
- mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
- mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- mWinParams.format = PixelFormat.TRANSLUCENT;
- mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- mWinParams.setTitle(SizeCompatRestartButton.class.getSimpleName()
- + context.getDisplayId());
- }
-
- void updateLastTargetActivity(IBinder activityToken) {
- mLastActivityToken = activityToken;
- }
-
- /** @return {@code false} if the target display is invalid. */
- boolean show() {
- try {
- getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
- } catch (WindowManager.InvalidDisplayException e) {
- // The target display may have been removed when the callback has just arrived.
- Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
- return false;
- }
- return true;
- }
-
- void remove() {
- if (mShowingHint != null) {
- mShowingHint.dismiss();
- }
- getContext().getSystemService(WindowManager.class).removeViewImmediate(this);
+ mRestartButton.setBackground(new RippleDrawable(color, null /* content */, mask));
+ mRestartButton.setOnClickListener(this);
+ mRestartButton.setOnLongClickListener(this);
}
@Override
public void onClick(View v) {
- ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken);
+ mLayout.onRestartButtonClicked();
}
@Override
@@ -122,20 +101,26 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- if (mShouldShowHint) {
+ if (mLayout.mShouldShowHint) {
+ mLayout.mShouldShowHint = false;
showHint();
}
}
@Override
+ public void setVisibility(@Visibility int visibility) {
+ if (visibility == View.GONE && mShowingHint != null) {
+ // Also dismiss the popup.
+ dismissHint();
+ }
+ super.setVisibility(visibility);
+ }
+
+ @Override
public void setLayoutDirection(int layoutDirection) {
- final int gravity = getGravity(layoutDirection);
+ final int gravity = SizeCompatUILayout.getGravity(layoutDirection);
if (mWinParams.gravity != gravity) {
mWinParams.gravity = gravity;
- if (mShowingHint != null) {
- mShowingHint.dismiss();
- showHint();
- }
getContext().getSystemService(WindowManager.class).updateViewLayout(this,
mWinParams);
}
@@ -147,8 +132,10 @@
return;
}
+ // TODO: popup is not attached to the button surface. Need to handle this differently for
+ // non-fullscreen task.
final View popupView = LayoutInflater.from(getContext()).inflate(
- R.layout.size_compat_mode_hint, null /* root */);
+ R.layout.size_compat_mode_hint, null);
final PopupWindow popupWindow = new PopupWindow(popupView,
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
popupWindow.setWindowLayoutType(mWinParams.type);
@@ -161,12 +148,15 @@
final Button gotItButton = popupView.findViewById(R.id.got_it);
gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
null /* content */, null /* mask */));
- gotItButton.setOnClickListener(view -> popupWindow.dismiss());
- popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY);
+ gotItButton.setOnClickListener(view -> dismissHint());
+ popupWindow.showAtLocation(mRestartButton, mWinParams.gravity, mLayout.mPopupOffsetX,
+ mLayout.mPopupOffsetY);
}
- private static int getGravity(int layoutDirection) {
- return Gravity.BOTTOM
- | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
+ void dismissHint() {
+ if (mShowingHint != null) {
+ mShowingHint.dismiss();
+ mShowingHint = null;
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
index 48ee86c..a3880f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -18,134 +18,166 @@
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Rect;
+import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.IBinder;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
-import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
/**
- * Shows a restart-activity button on Task when the foreground activity is in size compatibility
- * mode.
+ * Controls to show/update restart-activity buttons on Tasks based on whether the foreground
+ * activities are in size compatibility mode.
*/
public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener,
DisplayImeController.ImePositionProcessor {
- private static final String TAG = "SizeCompatUI";
+ private static final String TAG = "SizeCompatUIController";
+
+ /** Whether the IME is shown on display id. */
+ private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1);
/** The showing buttons by task id. */
- private final SparseArray<SizeCompatRestartButton> mActiveButtons = new SparseArray<>(1);
+ private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0);
+
/** Avoid creating display context frequently for non-default display. */
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
- @VisibleForTesting
private final Context mContext;
- private final ShellExecutor mMainExecutor;
private final DisplayController mDisplayController;
private final DisplayImeController mImeController;
+ private final SyncTransactionQueue mSyncQueue;
/** Only show once automatically in the process life. */
private boolean mHasShownHint;
- @VisibleForTesting
public SizeCompatUIController(Context context,
DisplayController displayController,
DisplayImeController imeController,
- ShellExecutor mainExecutor) {
+ SyncTransactionQueue syncQueue) {
mContext = context;
- mMainExecutor = mainExecutor;
mDisplayController = displayController;
mImeController = imeController;
+ mSyncQueue = syncQueue;
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
}
- public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
- @Nullable IBinder sizeCompatActivity,
+ /**
+ * Called when the Task info changed. Creates and updates the restart button if there is an
+ * activity in size compat, or removes the restart button if there is no size compat activity.
+ *
+ * @param displayId display the task and activity are in.
+ * @param taskId task the activity is in.
+ * @param taskConfig task config to place the restart button with.
+ * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
+ * top activity in this Task is not in size compat.
+ * @param taskListener listener to handle the Task Surface placement.
+ */
+ public void onSizeCompatInfoChanged(int displayId, int taskId,
+ @Nullable Configuration taskConfig, @Nullable IBinder sizeCompatActivity,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
- // TODO Draw button on Task surface
- if (taskBounds == null || sizeCompatActivity == null || taskListener == null) {
+ if (taskConfig == null || sizeCompatActivity == null || taskListener == null) {
// Null token means the current foreground activity is not in size compatibility mode.
- removeRestartButton(taskId);
+ removeLayout(taskId);
+ } else if (mActiveLayouts.contains(taskId)) {
+ // Button already exists, update the button layout.
+ updateLayout(taskId, taskConfig, sizeCompatActivity, taskListener);
} else {
- updateRestartButton(displayId, taskId, sizeCompatActivity);
+ // Create a new restart button.
+ createLayout(displayId, taskId, taskConfig, sizeCompatActivity, taskListener);
}
}
@Override
public void onDisplayRemoved(int displayId) {
mDisplayContextCache.remove(displayId);
- for (int i = 0; i < mActiveButtons.size(); i++) {
- final int taskId = mActiveButtons.keyAt(i);
- final SizeCompatRestartButton button = mActiveButtons.get(taskId);
- if (button != null && button.mDisplayId == displayId) {
- removeRestartButton(taskId);
- }
+
+ // Remove all buttons on the removed display.
+ final List<Integer> toRemoveTaskIds = new ArrayList<>();
+ forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId()));
+ for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) {
+ removeLayout(toRemoveTaskIds.get(i));
}
}
@Override
- public void onImeVisibilityChanged(int displayId, boolean isShowing) {
- final int newVisibility = isShowing ? View.GONE : View.VISIBLE;
- for (int i = 0; i < mActiveButtons.size(); i++) {
- final int taskId = mActiveButtons.keyAt(i);
- final SizeCompatRestartButton button = mActiveButtons.get(taskId);
- if (button == null || button.mDisplayId != displayId) {
- continue;
- }
-
- // Hide the button when input method is showing.
- if (button.getVisibility() != newVisibility) {
- button.setVisibility(newVisibility);
- }
- }
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
+ forAllLayoutsOnDisplay(displayId, layout -> layout.updateDisplayLayout(displayLayout));
}
- private void updateRestartButton(int displayId, int taskId, IBinder activityToken) {
- SizeCompatRestartButton restartButton = mActiveButtons.get(taskId);
- if (restartButton != null) {
- restartButton.updateLastTargetActivity(activityToken);
- return;
+ @Override
+ public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+ if (isShowing) {
+ mDisplaysWithIme.add(displayId);
+ } else {
+ mDisplaysWithIme.remove(displayId);
}
+ // Hide the button when input method is showing.
+ forAllLayoutsOnDisplay(displayId, layout -> layout.updateImeVisibility(isShowing));
+ }
+
+ private boolean isImeShowingOnDisplay(int displayId) {
+ return mDisplaysWithIme.contains(displayId);
+ }
+
+ private void createLayout(int displayId, int taskId, Configuration taskConfig,
+ IBinder activityToken, ShellTaskOrganizer.TaskListener taskListener) {
final Context context = getOrCreateDisplayContext(displayId);
if (context == null) {
- Log.i(TAG, "Cannot get context for display " + displayId);
+ Log.e(TAG, "Cannot get context for display " + displayId);
return;
}
- restartButton = createRestartButton(context, displayId);
- restartButton.updateLastTargetActivity(activityToken);
- if (restartButton.show()) {
- mActiveButtons.append(taskId, restartButton);
- } else {
- onDisplayRemoved(displayId);
- }
+ final SizeCompatUILayout layout = createLayout(context, displayId, taskId, taskConfig,
+ activityToken, taskListener);
+ mActiveLayouts.put(taskId, layout);
+ layout.createSizeCompatButton(isImeShowingOnDisplay(displayId));
}
@VisibleForTesting
- SizeCompatRestartButton createRestartButton(Context context, int displayId) {
- final SizeCompatRestartButton button = new SizeCompatRestartButton(context, displayId,
+ SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
+ Configuration taskConfig, IBinder activityToken,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ final SizeCompatUILayout layout = new SizeCompatUILayout(mSyncQueue, context, taskConfig,
+ taskId, activityToken, taskListener, mDisplayController.getDisplayLayout(displayId),
mHasShownHint);
// Only show hint for the first time.
mHasShownHint = true;
- return button;
+ return layout;
}
- private void removeRestartButton(int taskId) {
- final SizeCompatRestartButton button = mActiveButtons.get(taskId);
- if (button != null) {
- button.remove();
- mActiveButtons.remove(taskId);
+ private void updateLayout(int taskId, Configuration taskConfig,
+ IBinder sizeCompatActivity,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+ if (layout == null) {
+ return;
+ }
+ layout.updateSizeCompatInfo(taskConfig, sizeCompatActivity, taskListener,
+ isImeShowingOnDisplay(layout.getDisplayId()));
+ }
+
+ private void removeLayout(int taskId) {
+ final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+ if (layout != null) {
+ layout.release();
+ mActiveLayouts.remove(taskId);
}
}
@@ -167,4 +199,14 @@
}
return context;
}
+
+ private void forAllLayoutsOnDisplay(int displayId, Consumer<SizeCompatUILayout> callback) {
+ for (int i = 0; i < mActiveLayouts.size(); i++) {
+ final int taskId = mActiveLayouts.keyAt(i);
+ final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+ if (layout != null && layout.getDisplayId() == displayId) {
+ callback.accept(layout);
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
new file mode 100644
index 0000000..5924b53
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sizecompatui;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.app.ActivityClient;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.Gravity;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Records and handles layout of size compat UI on a task with size compat activity. Helps to
+ * calculate proper bounds when configuration or button position changes.
+ */
+class SizeCompatUILayout {
+ private static final String TAG = "SizeCompatUILayout";
+
+ private final SyncTransactionQueue mSyncQueue;
+ private Context mContext;
+ private Configuration mTaskConfig;
+ private final int mDisplayId;
+ private final int mTaskId;
+ private IBinder mActivityToken;
+ private ShellTaskOrganizer.TaskListener mTaskListener;
+ private DisplayLayout mDisplayLayout;
+ @VisibleForTesting
+ final SizeCompatUIWindowManager mWindowManager;
+
+ @VisibleForTesting
+ @Nullable
+ SizeCompatRestartButton mButton;
+ final int mButtonSize;
+ final int mPopupOffsetX;
+ final int mPopupOffsetY;
+ boolean mShouldShowHint;
+
+ SizeCompatUILayout(SyncTransactionQueue syncQueue, Context context, Configuration taskConfig,
+ int taskId, IBinder activityToken, ShellTaskOrganizer.TaskListener taskListener,
+ DisplayLayout displayLayout, boolean hasShownHint) {
+ mSyncQueue = syncQueue;
+ mContext = context.createConfigurationContext(taskConfig);
+ mTaskConfig = taskConfig;
+ mDisplayId = mContext.getDisplayId();
+ mTaskId = taskId;
+ mActivityToken = activityToken;
+ mTaskListener = taskListener;
+ mDisplayLayout = displayLayout;
+ mShouldShowHint = !hasShownHint;
+ mWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this);
+
+ mButtonSize =
+ mContext.getResources().getDimensionPixelSize(R.dimen.size_compat_button_size);
+ mPopupOffsetX = mButtonSize / 4;
+ mPopupOffsetY = mButtonSize;
+ }
+
+ /** Creates the button window. */
+ void createSizeCompatButton(boolean isImeShowing) {
+ if (isImeShowing || mButton != null) {
+ // When ime is showing, wait until ime is dismiss to create UI.
+ return;
+ }
+ mButton = mWindowManager.createSizeCompatUI();
+ updateSurfacePosition();
+ }
+
+ /** Releases the button window. */
+ void release() {
+ mButton.remove();
+ mButton = null;
+ mWindowManager.release();
+ }
+
+ /** Called when size compat info changed. */
+ void updateSizeCompatInfo(Configuration taskConfig, IBinder activityToken,
+ ShellTaskOrganizer.TaskListener taskListener, boolean isImeShowing) {
+ final Configuration prevTaskConfig = mTaskConfig;
+ final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
+ mTaskConfig = taskConfig;
+ mActivityToken = activityToken;
+ mTaskListener = taskListener;
+
+ // Update configuration.
+ mContext = mContext.createConfigurationContext(taskConfig);
+ mWindowManager.setConfiguration(taskConfig);
+
+ if (mButton == null || prevTaskListener != taskListener) {
+ // TaskListener changed, recreate the button for new surface parent.
+ release();
+ createSizeCompatButton(isImeShowing);
+ return;
+ }
+
+ if (!taskConfig.windowConfiguration.getBounds()
+ .equals(prevTaskConfig.windowConfiguration.getBounds())) {
+ // Reposition the button surface.
+ updateSurfacePosition();
+ }
+
+ if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) {
+ // Update layout for RTL.
+ mButton.setLayoutDirection(taskConfig.getLayoutDirection());
+ updateSurfacePosition();
+ }
+ }
+
+ /** Called when display layout changed. */
+ void updateDisplayLayout(DisplayLayout displayLayout) {
+ if (displayLayout == mDisplayLayout) {
+ return;
+ }
+
+ final Rect prevStableBounds = new Rect();
+ final Rect curStableBounds = new Rect();
+ mDisplayLayout.getStableBounds(prevStableBounds);
+ displayLayout.getStableBounds(curStableBounds);
+ mDisplayLayout = displayLayout;
+ if (!prevStableBounds.equals(curStableBounds)) {
+ // Stable bounds changed, update button surface position.
+ updateSurfacePosition();
+ }
+ }
+
+ /** Called when IME visibility changed. */
+ void updateImeVisibility(boolean isImeShowing) {
+ if (mButton == null) {
+ // Button may not be created because ime is previous showing.
+ createSizeCompatButton(isImeShowing);
+ return;
+ }
+
+ final int newVisibility = isImeShowing ? View.GONE : View.VISIBLE;
+ if (mButton.getVisibility() != newVisibility) {
+ mButton.setVisibility(newVisibility);
+ }
+ }
+
+ /** Gets the layout params for restart button. */
+ WindowManager.LayoutParams getWindowLayoutParams() {
+ final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
+ mButtonSize, mButtonSize,
+ TYPE_APPLICATION_OVERLAY,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
+ PixelFormat.TRANSLUCENT);
+ winParams.gravity = getGravity(getLayoutDirection());
+ winParams.token = new Binder();
+ winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + mContext.getDisplayId());
+ winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ return winParams;
+ }
+
+ /** Called when it is ready to be placed button surface button. */
+ void attachToParentSurface(SurfaceControl.Builder b) {
+ mTaskListener.attachChildSurfaceToTask(mTaskId, b);
+ }
+
+ /** Called when the restart button is clicked. */
+ void onRestartButtonClicked() {
+ ActivityClient.getInstance().restartActivityProcessIfVisible(mActivityToken);
+ }
+
+ @VisibleForTesting
+ void updateSurfacePosition() {
+ if (mButton == null || mWindowManager.getSurfaceControl() == null) {
+ return;
+ }
+ // The hint popup won't be at the correct position.
+ mButton.dismissHint();
+
+ // Use stable bounds to prevent the button from overlapping with system bars.
+ final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
+ final Rect stableBounds = new Rect();
+ mDisplayLayout.getStableBounds(stableBounds);
+ stableBounds.intersect(taskBounds);
+
+ // Position of the button in the container coordinate.
+ final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? stableBounds.left - taskBounds.left
+ : stableBounds.right - taskBounds.left - mButtonSize;
+ final int positionY = stableBounds.bottom - taskBounds.top - mButtonSize;
+
+ mSyncQueue.runInSync(t ->
+ t.setPosition(mWindowManager.getSurfaceControl(), positionX, positionY));
+ }
+
+ int getDisplayId() {
+ return mDisplayId;
+ }
+
+ int getTaskId() {
+ return mTaskId;
+ }
+
+ private int getLayoutDirection() {
+ return mContext.getResources().getConfiguration().getLayoutDirection();
+ }
+
+ static int getGravity(int layoutDirection) {
+ return Gravity.BOTTOM
+ | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
new file mode 100644
index 0000000..a7ad982
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sizecompatui;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.WindowlessWindowManager;
+
+import com.android.wm.shell.R;
+
+/**
+ * Holds view hierarchy of a root surface and helps to inflate {@link SizeCompatRestartButton}.
+ */
+class SizeCompatUIWindowManager extends WindowlessWindowManager {
+
+ private Context mContext;
+ private final SizeCompatUILayout mLayout;
+
+ @Nullable
+ private SurfaceControlViewHost mViewHost;
+ @Nullable
+ private SurfaceControl mLeash;
+
+ SizeCompatUIWindowManager(Context context, Configuration config, SizeCompatUILayout layout) {
+ super(config, null /* rootSurface */, null /* hostInputToken */);
+ mContext = context;
+ mLayout = layout;
+ }
+
+ @Override
+ public void setConfiguration(Configuration configuration) {
+ super.setConfiguration(configuration);
+ mContext = mContext.createConfigurationContext(configuration);
+ }
+
+ @Override
+ protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName("SizeCompatUILeash")
+ .setHidden(false)
+ .setCallsite("SizeCompatUIWindowManager#attachToParentSurface");
+ mLayout.attachToParentSurface(builder);
+ mLeash = builder.build();
+ b.setParent(mLeash);
+ }
+
+ /** Inflates {@link SizeCompatRestartButton} on to the root surface. */
+ SizeCompatRestartButton createSizeCompatUI() {
+ if (mViewHost == null) {
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ }
+
+ final SizeCompatRestartButton button = (SizeCompatRestartButton)
+ LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null);
+ button.inject(mLayout);
+ mViewHost.setView(button, mLayout.getWindowLayoutParams());
+ return button;
+ }
+
+ /** Releases the surface control and tears down the view hierarchy. */
+ void release() {
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+
+ if (mLeash != null) {
+ new SurfaceControl.Transaction().remove(mLeash).apply();
+ mLeash = null;
+ }
+ }
+
+ /**
+ * Gets {@link SurfaceControl} of the surface holding size compat UI view. @return {@code null}
+ * if not feasible.
+ */
+ @Nullable
+ SurfaceControl getSurfaceControl() {
+ return mLeash;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 6532993..10c742b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -130,6 +130,17 @@
}
}
+ @Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ if (mRootTaskInfo.taskId == taskId) {
+ b.setParent(mRootLeash);
+ } else if (mChildrenLeashes.contains(taskId)) {
+ b.setParent(mChildrenLeashes.get(taskId));
+ } else {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ }
+
void setBounds(Rect bounds, WindowContainerTransaction wct) {
wct.setBounds(mRootTaskInfo.token, bounds);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index c815e65..a0e9f43 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -31,6 +31,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -282,10 +283,10 @@
// sizeCompatActivity is null if top activity is not in size compat.
verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
- taskInfo1.configuration.windowConfiguration.getBounds(),
- null /* sizeCompatActivity*/ , taskListener);
+ null /* taskConfig */, null /* sizeCompatActivity*/, null /* taskListener */);
// sizeCompatActivity is non-null if top activity is in size compat.
+ clearInvocations(mSizeCompatUI);
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo2.displayId = taskInfo1.displayId;
@@ -293,14 +294,12 @@
taskInfo2.topActivityInSizeCompat = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
- taskInfo1.configuration.windowConfiguration.getBounds(),
- taskInfo1.topActivityToken,
- taskListener);
+ taskInfo1.configuration, taskInfo1.topActivityToken, taskListener);
+ clearInvocations(mSizeCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
- null /* taskConfig */, null /* sizeCompatActivity*/,
- null /* taskListener */);
+ null /* taskConfig */, null /* sizeCompatActivity*/, null /* taskListener */);
}
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
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 449ad88..19930485 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
@@ -22,16 +22,14 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.graphics.Point;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.util.Size;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -59,6 +57,9 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class PipTouchHandlerTest extends ShellTestCase {
+ private static final int INSET = 10;
+ private static final int PIP_LENGTH = 100;
+
private PipTouchHandler mPipTouchHandler;
@Mock
@@ -85,8 +86,9 @@
private PipMotionHelper mMotionHelper;
private PipResizeGestureHandler mPipResizeGestureHandler;
+ private DisplayLayout mDisplayLayout;
private Rect mInsetBounds;
- private Rect mMinBounds;
+ private Rect mPipBounds;
private Rect mCurBounds;
private boolean mFromImeAdjustment;
private boolean mFromShelfAdjustment;
@@ -109,12 +111,18 @@
mPipTouchHandler.setPipMotionHelper(mMotionHelper);
mPipTouchHandler.setPipResizeGestureHandler(mPipResizeGestureHandler);
- // Assume a display of 1000 x 1000
- // inset of 10
- mInsetBounds = new Rect(10, 10, 990, 990);
+ mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
+ mPipBoundsState.setDisplayLayout(mDisplayLayout);
+ mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET,
+ mPipBoundsState.getDisplayBounds().top + INSET,
+ mPipBoundsState.getDisplayBounds().right - INSET,
+ mPipBoundsState.getDisplayBounds().bottom - INSET);
// minBounds of 100x100 bottom right corner
- mMinBounds = new Rect(890, 890, 990, 990);
- mCurBounds = new Rect(mMinBounds);
+ mPipBounds = new Rect(mPipBoundsState.getDisplayBounds().right - INSET - PIP_LENGTH,
+ mPipBoundsState.getDisplayBounds().bottom - INSET - PIP_LENGTH,
+ mPipBoundsState.getDisplayBounds().right - INSET,
+ mPipBoundsState.getDisplayBounds().bottom - INSET);
+ mCurBounds = new Rect(mPipBounds);
mFromImeAdjustment = false;
mFromShelfAdjustment = false;
mDisplayRotation = 0;
@@ -122,37 +130,23 @@
}
@Test
- public void updateMovementBounds_minBounds() {
- Rect expectedMinMovementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(mMinBounds, mInsetBounds, expectedMinMovementBounds,
+ public void updateMovementBounds_minMaxBounds() {
+ final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
+ mPipBoundsState.getDisplayBounds().height());
+ Rect expectedMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(mPipBounds, mInsetBounds, expectedMovementBounds,
0);
- mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds,
+ mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds,
mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
- assertEquals(expectedMinMovementBounds, mPipBoundsState.getNormalMovementBounds());
+ assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds());
verify(mPipResizeGestureHandler, times(1))
- .updateMinSize(mMinBounds.width(), mMinBounds.height());
- }
+ .updateMinSize(mPipBounds.width(), mPipBounds.height());
- @Test
- public void updateMovementBounds_maxBounds() {
- Point displaySize = new Point();
- mContext.getDisplay().getRealSize(displaySize);
- 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();
- mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, expectedMaxMovementBounds,
- 0);
-
- mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds,
- mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
-
- assertEquals(expectedMaxMovementBounds, mPipBoundsState.getExpandedMovementBounds());
verify(mPipResizeGestureHandler, times(1))
- .updateMaxSize(maxBounds.width(), maxBounds.height());
+ .updateMaxSize(shorterLength - 2 * mInsetBounds.left,
+ shorterLength - 2 * mInsetBounds.left);
}
@Test
@@ -160,7 +154,7 @@
mFromImeAdjustment = true;
mPipTouchHandler.onImeVisibilityChanged(true /* imeVisible */, mImeHeight);
- mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds,
+ mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds,
mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
verify(mMotionHelper, times(1)).animateToOffset(any(), anyInt());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
new file mode 100644
index 0000000..d9086a6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sizecompatui;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link SizeCompatRestartButton}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:SizeCompatRestartButtonTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SizeCompatRestartButtonTest extends ShellTestCase {
+
+ @Mock private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock private IBinder mActivityToken;
+ @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock private DisplayLayout mDisplayLayout;
+
+ private SizeCompatUILayout mLayout;
+ private SizeCompatRestartButton mButton;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ final int taskId = 1;
+ mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mContext, new Configuration(),
+ taskId, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/);
+ mButton = (SizeCompatRestartButton)
+ LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null);
+ mButton.inject(mLayout);
+
+ spyOn(mLayout);
+ spyOn(mButton);
+ doNothing().when(mButton).showHint();
+ }
+
+ @Test
+ public void testOnClick() {
+ doNothing().when(mLayout).onRestartButtonClicked();
+
+ mButton.onClick(mButton);
+
+ verify(mLayout).onRestartButtonClicked();
+ }
+
+ @Test
+ public void testOnLongClick() {
+ verify(mButton, never()).showHint();
+
+ mButton.onLongClick(mButton);
+
+ verify(mButton).showHint();
+ }
+
+ @Test
+ public void testOnAttachedToWindow_showHint() {
+ mLayout.mShouldShowHint = false;
+ mButton.onAttachedToWindow();
+
+ verify(mButton, never()).showHint();
+
+ mLayout.mShouldShowHint = true;
+ mButton.onAttachedToWindow();
+
+ verify(mButton).showHint();
+ }
+
+ @Test
+ public void testRemove() {
+ mButton.remove();
+
+ verify(mButton).dismissHint();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
index 0eb64e5..806a90b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
@@ -16,23 +16,28 @@
package com.android.wm.shell.sizecompatui;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.content.Context;
-import android.graphics.Rect;
+import android.content.res.Configuration;
import android.os.IBinder;
import android.testing.AndroidTestingRunner;
-import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
import org.junit.Before;
import org.junit.Test;
@@ -50,28 +55,34 @@
@SmallTest
public class SizeCompatUIControllerTest extends ShellTestCase {
private static final int DISPLAY_ID = 0;
-
- private final TestShellExecutor mShellMainExecutor = new TestShellExecutor();
+ private static final int TASK_ID = 12;
private SizeCompatUIController mController;
private @Mock DisplayController mMockDisplayController;
+ private @Mock DisplayLayout mMockDisplayLayout;
private @Mock DisplayImeController mMockImeController;
- private @Mock SizeCompatRestartButton mMockButton;
private @Mock IBinder mMockActivityToken;
private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
+ private @Mock SyncTransactionQueue mMockSyncQueue;
+ private @Mock SizeCompatUILayout mMockLayout;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- doReturn(true).when(mMockButton).show();
+ doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt());
+ doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId();
+ doReturn(TASK_ID).when(mMockLayout).getTaskId();
mController = new SizeCompatUIController(mContext, mMockDisplayController,
- mMockImeController, mShellMainExecutor) {
+ mMockImeController, mMockSyncQueue) {
@Override
- SizeCompatRestartButton createRestartButton(Context context, int displayId) {
- return mMockButton;
+ SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
+ Configuration taskConfig, IBinder activityToken,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ return mMockLayout;
}
};
+ spyOn(mController);
}
@Test
@@ -82,42 +93,72 @@
@Test
public void testOnSizeCompatInfoChanged() {
- final int taskId = 12;
- final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+ final Configuration taskConfig = new Configuration();
- // Verify that the restart button is added with non-null size compat activity.
- mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+ // Verify that the restart button is added with non-null size compat info.
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
mMockActivityToken, mMockTaskListener);
- mShellMainExecutor.flushAll();
- verify(mMockButton).show();
- verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
+ verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig),
+ eq(mMockActivityToken), eq(mMockTaskListener));
- // Verify that the restart button is removed with null size compat activity.
- mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
+ // Verify that the restart button is updated with non-null new size compat info.
+ final Configuration newTaskConfig = new Configuration();
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig,
+ mMockActivityToken, mMockTaskListener);
- mShellMainExecutor.flushAll();
- verify(mMockButton).remove();
+ verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockActivityToken, mMockTaskListener,
+ false /* isImeShowing */);
+
+ // Verify that the restart button is removed with null size compat info.
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, null, null, mMockTaskListener);
+
+ verify(mMockLayout).release();
+ }
+
+ @Test
+ public void testOnDisplayRemoved() {
+ final Configuration taskConfig = new Configuration();
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+ mMockActivityToken, mMockTaskListener);
+
+ mController.onDisplayRemoved(DISPLAY_ID + 1);
+
+ verify(mMockLayout, never()).release();
+
+ mController.onDisplayRemoved(DISPLAY_ID);
+
+ verify(mMockLayout).release();
+ }
+
+ @Test
+ public void testOnDisplayConfigurationChanged() {
+ final Configuration taskConfig = new Configuration();
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+ mMockActivityToken, mMockTaskListener);
+
+ final Configuration newTaskConfig = new Configuration();
+ mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, newTaskConfig);
+
+ verify(mMockLayout, never()).updateDisplayLayout(any());
+
+ mController.onDisplayConfigurationChanged(DISPLAY_ID, newTaskConfig);
+
+ verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout);
}
@Test
public void testChangeButtonVisibilityOnImeShowHide() {
- final int taskId = 12;
- final Rect taskBounds = new Rect(0, 0, 1000, 2000);
- mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+ final Configuration taskConfig = new Configuration();
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
mMockActivityToken, mMockTaskListener);
- mShellMainExecutor.flushAll();
- // Verify that the restart button is hidden when IME is visible.
- doReturn(View.VISIBLE).when(mMockButton).getVisibility();
mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
- verify(mMockButton).setVisibility(eq(View.GONE));
+ verify(mMockLayout).updateImeVisibility(true);
- // Verify that the restart button is visible when IME is hidden.
- doReturn(View.GONE).when(mMockButton).getVisibility();
mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
- verify(mMockButton).setVisibility(eq(View.VISIBLE));
+ verify(mMockLayout).updateImeVisibility(false);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
new file mode 100644
index 0000000..236db44
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sizecompatui;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityClient;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link SizeCompatUILayout}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:SizeCompatUILayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SizeCompatUILayoutTest extends ShellTestCase {
+
+ private static final int TASK_ID = 1;
+
+ @Mock private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock private IBinder mActivityToken;
+ @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock private DisplayLayout mDisplayLayout;
+ @Mock private SizeCompatRestartButton mButton;
+ private Configuration mTaskConfig;
+
+ private SizeCompatUILayout mLayout;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTaskConfig = new Configuration();
+
+ mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mContext, new Configuration(),
+ TASK_ID, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/);
+
+ spyOn(mLayout);
+ spyOn(mLayout.mWindowManager);
+ doReturn(mButton).when(mLayout.mWindowManager).createSizeCompatUI();
+ }
+
+ @Test
+ public void testCreateSizeCompatButton() {
+ // Not create button if IME is showing.
+ mLayout.createSizeCompatButton(true /* isImeShowing */);
+
+ verify(mLayout.mWindowManager, never()).createSizeCompatUI();
+ assertNull(mLayout.mButton);
+
+ mLayout.createSizeCompatButton(false /* isImeShowing */);
+
+ verify(mLayout.mWindowManager).createSizeCompatUI();
+ assertNotNull(mLayout.mButton);
+ }
+
+ @Test
+ public void testRelease() {
+ mLayout.createSizeCompatButton(false /* isImeShowing */);
+
+ mLayout.release();
+
+ assertNull(mLayout.mButton);
+ verify(mButton).remove();
+ verify(mLayout.mWindowManager).release();
+ }
+
+ @Test
+ public void testUpdateSizeCompatInfo() {
+ mLayout.createSizeCompatButton(false /* isImeShowing */);
+
+ // No diff
+ clearInvocations(mLayout);
+ mLayout.updateSizeCompatInfo(mTaskConfig, mActivityToken, mTaskListener,
+ false /* isImeShowing */);
+
+ verify(mLayout, never()).updateSurfacePosition();
+ verify(mLayout, never()).release();
+ verify(mLayout, never()).createSizeCompatButton(anyBoolean());
+
+ // Change task listener, recreate button.
+ clearInvocations(mLayout);
+ final ShellTaskOrganizer.TaskListener newTaskListener = mock(
+ ShellTaskOrganizer.TaskListener.class);
+ mLayout.updateSizeCompatInfo(mTaskConfig, mActivityToken, newTaskListener,
+ false /* isImeShowing */);
+
+ verify(mLayout).release();
+ verify(mLayout).createSizeCompatButton(anyBoolean());
+
+ // Change task bounds, update position.
+ clearInvocations(mLayout);
+ final Configuration newTaskConfiguration = new Configuration();
+ newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
+ mLayout.updateSizeCompatInfo(newTaskConfiguration, mActivityToken, newTaskListener,
+ false /* isImeShowing */);
+
+ verify(mLayout).updateSurfacePosition();
+ }
+
+ @Test
+ public void testUpdateDisplayLayout() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo,
+ mContext.getResources(), false, false);
+
+ mLayout.updateDisplayLayout(displayLayout1);
+ verify(mLayout).updateSurfacePosition();
+
+ // No update if the display bounds is the same.
+ clearInvocations(mLayout);
+ final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
+ mContext.getResources(), false, false);
+ mLayout.updateDisplayLayout(displayLayout2);
+ verify(mLayout, never()).updateSurfacePosition();
+ }
+
+ @Test
+ public void testUpdateImeVisibility() {
+ // Create button if it is not created.
+ mLayout.mButton = null;
+ mLayout.updateImeVisibility(false /* isImeShowing */);
+
+ verify(mLayout).createSizeCompatButton(false /* isImeShowing */);
+
+ // Hide button if ime is shown.
+ clearInvocations(mLayout);
+ doReturn(View.VISIBLE).when(mButton).getVisibility();
+ mLayout.updateImeVisibility(true /* isImeShowing */);
+
+ verify(mLayout, never()).createSizeCompatButton(anyBoolean());
+ verify(mButton).setVisibility(View.GONE);
+
+ // Show button if ime is not shown.
+ doReturn(View.GONE).when(mButton).getVisibility();
+ mLayout.updateImeVisibility(false /* isImeShowing */);
+
+ verify(mLayout, never()).createSizeCompatButton(anyBoolean());
+ verify(mButton).setVisibility(View.VISIBLE);
+ }
+
+ @Test
+ public void testAttachToParentSurface() {
+ final SurfaceControl.Builder b = new SurfaceControl.Builder();
+ mLayout.attachToParentSurface(b);
+
+ verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b);
+ }
+
+ @Test
+ public void testOnRestartButtonClicked() {
+ spyOn(ActivityClient.getInstance());
+ doNothing().when(ActivityClient.getInstance()).restartActivityProcessIfVisible(any());
+
+ mLayout.onRestartButtonClicked();
+
+ verify(ActivityClient.getInstance()).restartActivityProcessIfVisible(mActivityToken);
+ }
+}
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 74cf1fd..20a8a8c 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -160,10 +160,6 @@
}
}
- if (!mHasStartValue) {
- doSetStartValue(getValue(mTarget));
- }
-
if (!mStagingRequests.empty()) {
// No interpolator was set, use the default
if (mPlayState == PlayState::NotStarted && !mInterpolator) {
@@ -270,9 +266,11 @@
// to call setValue even if the animation isn't yet running or is still
// being delayed as we need to override the staging value
if (playTime < 0) {
- setValue(mTarget, mFromValue);
return false;
}
+ if (!this->mHasStartValue) {
+ doSetStartValue(getValue(mTarget));
+ }
float fraction = 1.0f;
if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) {
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 4966bfa..df66981 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -189,6 +189,13 @@
}
}
+static void android_view_ThreadedRenderer_setSurfaceControl(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jlong surfaceControlPtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ ASurfaceControl* surfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControlPtr);
+ proxy->setSurfaceControl(surfaceControl);
+}
+
static jboolean android_view_ThreadedRenderer_pause(JNIEnv* env, jobject clazz,
jlong proxyPtr) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -671,6 +678,8 @@
{"nSetName", "(JLjava/lang/String;)V", (void*)android_view_ThreadedRenderer_setName},
{"nSetSurface", "(JLandroid/view/Surface;Z)V",
(void*)android_view_ThreadedRenderer_setSurface},
+ {"nSetSurfaceControl", "(JJ)V",
+ (void*)android_view_ThreadedRenderer_setSurfaceControl},
{"nPause", "(J)Z", (void*)android_view_ThreadedRenderer_pause},
{"nSetStopped", "(JZ)V", (void*)android_view_ThreadedRenderer_setStopped},
{"nSetLightAlpha", "(JFF)V", (void*)android_view_ThreadedRenderer_setLightAlpha},
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 65afcc3..9543d47 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -133,6 +133,7 @@
void CanvasContext::destroy() {
stopDrawing();
setSurface(nullptr);
+ setSurfaceControl(nullptr);
freePrefetchedLayers();
destroyHardwareResources();
mAnimationContext->destroy();
@@ -173,6 +174,19 @@
setupPipelineSurface();
}
+void CanvasContext::setSurfaceControl(ASurfaceControl* surfaceControl) {
+ if (surfaceControl == mSurfaceControl) return;
+
+ auto funcs = mRenderThread.getASurfaceControlFunctions();
+ if (mSurfaceControl != nullptr) {
+ funcs.releaseFunc(mSurfaceControl);
+ }
+ mSurfaceControl = surfaceControl;
+ if (mSurfaceControl != nullptr) {
+ funcs.acquireFunc(mSurfaceControl);
+ }
+}
+
void CanvasContext::setupPipelineSurface() {
bool hasSurface = mRenderPipeline->setSurface(
mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index b31883b..917b00c 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -112,6 +112,7 @@
void setSwapBehavior(SwapBehavior swapBehavior);
void setSurface(ANativeWindow* window, bool enableTimeout = true);
+ void setSurfaceControl(ASurfaceControl* surfaceControl);
bool pauseSurface();
void setStopped(bool stopped);
bool hasSurface() const { return mNativeSurface.get(); }
@@ -218,6 +219,9 @@
RenderThread& mRenderThread;
std::unique_ptr<ReliableSurface> mNativeSurface;
+ // The SurfaceControl reference is passed from ViewRootImpl, can be set to
+ // NULL to remove the reference
+ ASurfaceControl* mSurfaceControl = nullptr;
// stopped indicates the CanvasContext will reject actual redraw operations,
// and defer repaint until it is un-stopped
bool mStopped = false;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 0ade8dd..e14842f 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -84,6 +84,19 @@
});
}
+void RenderProxy::setSurfaceControl(ASurfaceControl* surfaceControl) {
+ auto funcs = mRenderThread.getASurfaceControlFunctions();
+ if (surfaceControl) {
+ funcs.acquireFunc(surfaceControl);
+ }
+ mRenderThread.queue().post([this, control = surfaceControl, funcs]() mutable {
+ mContext->setSurfaceControl(control);
+ if (control) {
+ funcs.releaseFunc(control);
+ }
+ });
+}
+
void RenderProxy::allocateBuffers() {
mRenderThread.queue().post([=]() { mContext->allocateBuffers(); });
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index a4adb16..366d6b5 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -20,6 +20,7 @@
#include <SkBitmap.h>
#include <android/native_window.h>
#include <cutils/compiler.h>
+#include <android/surface_control.h>
#include <utils/Functor.h>
#include "../FrameMetricsObserver.h"
@@ -72,6 +73,7 @@
void setName(const char* name);
void setSurface(ANativeWindow* window, bool enableTimeout = true);
+ void setSurfaceControl(ASurfaceControl* surfaceControl);
void allocateBuffers();
bool pause();
void setStopped(bool stopped);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 7750a31..2610186 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -33,6 +33,7 @@
#include <GrContextOptions.h>
#include <gl/GrGLInterface.h>
+#include <dlfcn.h>
#include <sys/resource.h>
#include <utils/Condition.h>
#include <utils/Log.h>
@@ -49,6 +50,17 @@
static JVMAttachHook gOnStartHook = nullptr;
+ASurfaceControlFunctions::ASurfaceControlFunctions() {
+ void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+ acquireFunc = (ASC_acquire) dlsym(handle_, "ASurfaceControl_acquire");
+ LOG_ALWAYS_FATAL_IF(acquireFunc == nullptr,
+ "Failed to find required symbol ASurfaceControl_acquire!");
+
+ releaseFunc = (ASC_release) dlsym(handle_, "ASurfaceControl_release");
+ LOG_ALWAYS_FATAL_IF(releaseFunc == nullptr,
+ "Failed to find required symbol ASurfaceControl_release!");
+}
+
void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
RenderThread* rt = reinterpret_cast<RenderThread*>(data);
int64_t vsyncId = AChoreographer_getVsyncId(rt->mChoreographer);
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 4fbb0716..bb7c518 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -78,6 +78,16 @@
virtual ~VsyncSource() {}
};
+typedef void (*ASC_acquire)(ASurfaceControl* control);
+typedef void (*ASC_release)(ASurfaceControl* control);
+
+struct ASurfaceControlFunctions {
+ ASurfaceControlFunctions();
+
+ ASC_acquire acquireFunc;
+ ASC_release releaseFunc;
+};
+
class ChoreographerSource;
class DummyVsyncSource;
@@ -121,6 +131,10 @@
void preload();
+ const ASurfaceControlFunctions& getASurfaceControlFunctions() {
+ return mASurfaceControlFunctions;
+ }
+
/**
* isCurrent provides a way to query, if the caller is running on
* the render thread.
@@ -189,6 +203,8 @@
sk_sp<GrDirectContext> mGrContext;
CacheManager* mCacheManager;
sp<VulkanManager> mVkManager;
+
+ ASurfaceControlFunctions mASurfaceControlFunctions;
};
} /* namespace renderthread */
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index a7e9a0d..5e2e559 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -569,7 +569,12 @@
/** @hide */
public long getElapsedRealtimeAgeMillis() {
- return NANOSECONDS.toMillis(getElapsedRealtimeAgeNanos());
+ return getElapsedRealtimeAgeMillis(SystemClock.elapsedRealtime());
+ }
+
+ /** @hide */
+ public long getElapsedRealtimeAgeMillis(long referenceRealtimeMs) {
+ return referenceRealtimeMs - NANOSECONDS.toMillis(mElapsedRealtimeNanos);
}
/**
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8d090f8..d896c1f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -567,6 +567,25 @@
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int FLAG_FROM_KEY = 1 << 12;
+ /** @hide */
+ @IntDef(flag = false, prefix = "FLAG", value = {
+ FLAG_SHOW_UI,
+ FLAG_ALLOW_RINGER_MODES,
+ FLAG_PLAY_SOUND,
+ FLAG_REMOVE_SOUND_AND_VIBRATE,
+ FLAG_VIBRATE,
+ FLAG_FIXED_VOLUME,
+ FLAG_BLUETOOTH_ABS_VOLUME,
+ FLAG_SHOW_SILENT_HINT,
+ FLAG_HDMI_SYSTEM_AUDIO_VOLUME,
+ FLAG_ACTIVE_MEDIA_ONLY,
+ FLAG_SHOW_UI_WARNINGS,
+ FLAG_SHOW_VIBRATE_HINT,
+ FLAG_FROM_KEY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flags {}
+
// The iterator of TreeMap#entrySet() returns the entries in ascending key order.
private static final TreeMap<Integer, String> FLAG_NAMES = new TreeMap<>();
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 27f72687..ede1dbf 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -181,6 +181,21 @@
@Retention(RetentionPolicy.SOURCE)
public @interface PlayerState {}
+ /** @hide */
+ public static String playerStateToString(@PlayerState int state) {
+ switch (state) {
+ case PLAYER_STATE_UNKNOWN: return "PLAYER_STATE_UNKNOWN";
+ case PLAYER_STATE_RELEASED: return "PLAYER_STATE_RELEASED";
+ case PLAYER_STATE_IDLE: return "PLAYER_STATE_IDLE";
+ case PLAYER_STATE_STARTED: return "PLAYER_STATE_STARTED";
+ case PLAYER_STATE_PAUSED: return "PLAYER_STATE_PAUSED";
+ case PLAYER_STATE_STOPPED: return "PLAYER_STATE_STOPPED";
+ case PLAYER_UPDATE_DEVICE_ID: return "PLAYER_UPDATE_DEVICE_ID";
+ default:
+ return "invalid state " + state;
+ }
+ }
+
// immutable data
private final int mPlayerIId;
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index e056d43..7fb83f1 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2726,8 +2726,10 @@
}
}
synchronized(mPlayStateLock) {
+ baseStart(0); // unknown device at this point
native_start();
- baseStart(native_getRoutedDeviceId());
+ // FIXME see b/179218630
+ //baseStart(native_getRoutedDeviceId());
if (mPlayState == PLAYSTATE_PAUSED_STOPPING) {
mPlayState = PLAYSTATE_STOPPING;
} else {
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 92db946..44f8385 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -23,6 +23,7 @@
import android.graphics.ImageFormat.Format;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.SurfaceUtils;
import android.hardware.HardwareBuffer;
import android.os.Handler;
@@ -202,6 +203,25 @@
if (format == ImageFormat.UNKNOWN) {
format = SurfaceUtils.getSurfaceFormat(surface);
}
+ // Several public formats use the same native HAL_PIXEL_FORMAT_BLOB. The native
+ // allocation estimation sequence depends on the public formats values. To avoid
+ // possible errors, convert where necessary.
+ if (format == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) {
+ int surfaceDataspace = SurfaceUtils.getSurfaceDataspace(surface);
+ switch (surfaceDataspace) {
+ case StreamConfigurationMap.HAL_DATASPACE_DEPTH:
+ format = ImageFormat.DEPTH_POINT_CLOUD;
+ break;
+ case StreamConfigurationMap.HAL_DATASPACE_DYNAMIC_DEPTH:
+ format = ImageFormat.DEPTH_JPEG;
+ break;
+ case StreamConfigurationMap.HAL_DATASPACE_HEIF:
+ format = ImageFormat.HEIC;
+ break;
+ default:
+ format = ImageFormat.JPEG;
+ }
+ }
// Estimate the native buffer allocation size and register it so it gets accounted for
// during GC. Note that this doesn't include the buffers required by the buffer queue
// itself and the buffers requested by the producer.
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index ca0d29f..2c45ed3 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1356,6 +1356,7 @@
}
private void startImpl() {
+ baseStart(0); // unknown device at this point
stayAwake(true);
_start();
}
@@ -1381,6 +1382,7 @@
public void stop() throws IllegalStateException {
stayAwake(false);
_stop();
+ baseStop();
}
private native void _stop() throws IllegalStateException;
@@ -1394,6 +1396,7 @@
public void pause() throws IllegalStateException {
stayAwake(false);
_pause();
+ basePause();
}
private native void _pause() throws IllegalStateException;
@@ -3479,7 +3482,8 @@
case MEDIA_STOPPED:
{
tryToDisableNativeRoutingCallback();
- baseStop();
+ // FIXME see b/179218630
+ //baseStop();
TimeProvider timeProvider = mTimeProvider;
if (timeProvider != null) {
timeProvider.onStopped();
@@ -3489,15 +3493,17 @@
case MEDIA_STARTED:
{
- baseStart(native_getRoutedDeviceId());
+ // FIXME see b/179218630
+ //baseStart(native_getRoutedDeviceId());
tryToEnableNativeRoutingCallback();
}
// fall through
case MEDIA_PAUSED:
{
- if (msg.what == MEDIA_PAUSED) {
- basePause();
- }
+ // FIXME see b/179218630
+ //if (msg.what == MEDIA_PAUSED) {
+ // basePause();
+ //}
TimeProvider timeProvider = mTimeProvider;
if (timeProvider != null) {
timeProvider.onPaused(msg.what == MEDIA_PAUSED);
diff --git a/media/java/android/media/metrics/Event.java b/media/java/android/media/metrics/Event.java
new file mode 100644
index 0000000..5646dcd
--- /dev/null
+++ b/media/java/android/media/metrics/Event.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.metrics;
+
+import android.annotation.IntRange;
+
+/**
+ * Abstract class for metrics events.
+ */
+public abstract class Event {
+ private final long mTimeSinceCreatedMillis;
+
+ // hide default constructor
+ /* package */ Event() {
+ mTimeSinceCreatedMillis = MediaMetricsManager.INVALID_TIMESTAMP;
+ }
+
+ protected Event(long timeSinceCreatedMillis) {
+ mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+ }
+
+ /**
+ * Gets time since the corresponding instance is created in millisecond.
+ * @return the timestamp since the instance is created, or -1 if unknown.
+ */
+ @IntRange(from = -1)
+ public long getTimeSinceCreatedMillis() {
+ return mTimeSinceCreatedMillis;
+ }
+}
diff --git a/media/java/android/media/metrics/MediaMetricsManager.java b/media/java/android/media/metrics/MediaMetricsManager.java
index f2eae5f..de780f6 100644
--- a/media/java/android/media/metrics/MediaMetricsManager.java
+++ b/media/java/android/media/metrics/MediaMetricsManager.java
@@ -26,7 +26,8 @@
*/
@SystemService(Context.MEDIA_METRICS_SERVICE)
public class MediaMetricsManager {
- // TODO: unhide APIs.
+ public static final long INVALID_TIMESTAMP = -1;
+
private static final String TAG = "MediaMetricsManager";
private IMediaMetricsManager mService;
diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java
index 3056e98..7f3450b 100644
--- a/media/java/android/media/metrics/PlaybackSession.java
+++ b/media/java/android/media/metrics/PlaybackSession.java
@@ -69,9 +69,8 @@
/**
* Reports playback state event.
- * @hide
*/
- public void reportPlaybackStateEvent(PlaybackStateEvent event) {
+ public void reportPlaybackStateEvent(@NonNull PlaybackStateEvent event) {
mManager.reportPlaybackStateEvent(mId, event);
}
diff --git a/media/java/android/media/metrics/PlaybackStateEvent.java b/media/java/android/media/metrics/PlaybackStateEvent.java
index 6ce5bf0..8ca5b75 100644
--- a/media/java/android/media/metrics/PlaybackStateEvent.java
+++ b/media/java/android/media/metrics/PlaybackStateEvent.java
@@ -17,6 +17,7 @@
package android.media.metrics;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
@@ -27,10 +28,8 @@
/**
* Playback state event.
- * @hide
*/
-public final class PlaybackStateEvent implements Parcelable {
- // TODO: more states
+public final class PlaybackStateEvent extends Event implements Parcelable {
/** Playback has not started (initial state) */
public static final int STATE_NOT_STARTED = 0;
/** Playback is buffering in the background for initial playback start */
@@ -41,23 +40,57 @@
public static final int STATE_PLAYING = 3;
/** Playback is paused but ready to play */
public static final int STATE_PAUSED = 4;
+ /** Playback is handling a seek. */
+ public static final int STATE_SEEKING = 5;
+ /** Playback is buffering to resume active playback. */
+ public static final int STATE_BUFFERING = 6;
+ /** Playback is buffering while paused. */
+ public static final int STATE_PAUSED_BUFFERING = 7;
+ /** Playback is suppressed (e.g. due to audio focus loss). */
+ public static final int STATE_SUPPRESSED = 9;
+ /**
+ * Playback is suppressed (e.g. due to audio focus loss) while buffering to resume a playback.
+ */
+ public static final int STATE_SUPPRESSED_BUFFERING = 10;
+ /** Playback has reached the end of the media. */
+ public static final int STATE_ENDED = 11;
+ /** Playback is stopped and can be restarted. */
+ public static final int STATE_STOPPED = 12;
+ /** Playback is stopped due a fatal error and can be retried. */
+ public static final int STATE_FAILED = 13;
+ /** Playback is interrupted by an ad. */
+ public static final int STATE_INTERRUPTED_BY_AD = 14;
+ /** Playback is abandoned before reaching the end of the media. */
+ public static final int STATE_ABANDONED = 15;
- private int mState;
- private long mTimeSincePlaybackCreatedMillis;
+ private final int mState;
+ private final long mTimeSinceCreatedMillis;
// These track ExoPlayer states. See the ExoPlayer documentation for the state transitions.
+ /** @hide */
@IntDef(prefix = "STATE_", value = {
STATE_NOT_STARTED,
STATE_JOINING_BACKGROUND,
STATE_JOINING_FOREGROUND,
STATE_PLAYING,
- STATE_PAUSED
+ STATE_PAUSED,
+ STATE_SEEKING,
+ STATE_BUFFERING,
+ STATE_PAUSED_BUFFERING,
+ STATE_SUPPRESSED,
+ STATE_SUPPRESSED_BUFFERING,
+ STATE_ENDED,
+ STATE_STOPPED,
+ STATE_FAILED,
+ STATE_INTERRUPTED_BY_AD,
+ STATE_ABANDONED,
})
@Retention(java.lang.annotation.RetentionPolicy.SOURCE)
public @interface State {}
/**
* Converts playback state to string.
+ * @hide
*/
public static String stateToString(@State int value) {
switch (value) {
@@ -71,6 +104,26 @@
return "STATE_PLAYING";
case STATE_PAUSED:
return "STATE_PAUSED";
+ case STATE_SEEKING:
+ return "STATE_SEEKING";
+ case STATE_BUFFERING:
+ return "STATE_BUFFERING";
+ case STATE_PAUSED_BUFFERING:
+ return "STATE_PAUSED_BUFFERING";
+ case STATE_SUPPRESSED:
+ return "STATE_SUPPRESSED";
+ case STATE_SUPPRESSED_BUFFERING:
+ return "STATE_SUPPRESSED_BUFFERING";
+ case STATE_ENDED:
+ return "STATE_ENDED";
+ case STATE_STOPPED:
+ return "STATE_STOPPED";
+ case STATE_FAILED:
+ return "STATE_FAILED";
+ case STATE_INTERRUPTED_BY_AD:
+ return "STATE_INTERRUPTED_BY_AD";
+ case STATE_ABANDONED:
+ return "STATE_ABANDONED";
default:
return Integer.toHexString(value);
}
@@ -83,14 +136,13 @@
*/
public PlaybackStateEvent(
int state,
- long timeSincePlaybackCreatedMillis) {
+ long timeSinceCreatedMillis) {
+ this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
this.mState = state;
- this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
}
/**
* Gets playback state.
- * @return
*/
public int getState() {
return mState;
@@ -98,9 +150,12 @@
/**
* Gets time since the corresponding playback is created in millisecond.
+ * @return the timestamp since the playback is created, or -1 if unknown.
*/
- public long getTimeSincePlaybackCreatedMillis() {
- return mTimeSincePlaybackCreatedMillis;
+ @Override
+ @IntRange(from = -1)
+ public long getTimeSinceCreatedMillis() {
+ return mTimeSinceCreatedMillis;
}
@Override
@@ -109,18 +164,18 @@
if (o == null || getClass() != o.getClass()) return false;
PlaybackStateEvent that = (PlaybackStateEvent) o;
return mState == that.mState
- && mTimeSincePlaybackCreatedMillis == that.mTimeSincePlaybackCreatedMillis;
+ && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
}
@Override
public int hashCode() {
- return Objects.hash(mState, mTimeSincePlaybackCreatedMillis);
+ return Objects.hash(mState, mTimeSinceCreatedMillis);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mState);
- dest.writeLong(mTimeSincePlaybackCreatedMillis);
+ dest.writeLong(mTimeSinceCreatedMillis);
}
@Override
@@ -131,10 +186,10 @@
/** @hide */
/* package-private */ PlaybackStateEvent(@NonNull Parcel in) {
int state = in.readInt();
- long timeSincePlaybackCreatedMillis = in.readLong();
+ long timeSinceCreatedMillis = in.readLong();
this.mState = state;
- this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+ this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
}
public static final @NonNull Parcelable.Creator<PlaybackStateEvent> CREATOR =
@@ -150,4 +205,43 @@
}
};
+ /**
+ * A builder for {@link PlaybackStateEvent}
+ */
+ public static final class Builder {
+ private int mState = STATE_NOT_STARTED;
+ private long mTimeSinceCreatedMillis = -1;
+
+ /**
+ * Creates a new Builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Sets playback state.
+ */
+ public @NonNull Builder setState(@State int value) {
+ mState = value;
+ return this;
+ }
+
+ /**
+ * Sets timestamp since the creation in milliseconds.
+ * @param value the timestamp since the creation in milliseconds.
+ * -1 indicates the value is unknown.
+ */
+ public @NonNull Builder setTimeSinceCreatedMillis(@IntRange(from = -1) long value) {
+ mTimeSinceCreatedMillis = value;
+ return this;
+ }
+
+ /** Builds the instance. */
+ public @NonNull PlaybackStateEvent build() {
+ PlaybackStateEvent o = new PlaybackStateEvent(
+ mState,
+ mTimeSinceCreatedMillis);
+ return o;
+ }
+ }
}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index f580ea5..13a3436 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -1127,9 +1127,10 @@
* toast showing the volume should be shown.
*
* @param sessionToken the remote media session token
- * @param flags extra information about how to handle the volume change
+ * @param flags flags containing extra action or information regarding the volume change
*/
- void onVolumeChanged(@NonNull MediaSession.Token sessionToken, int flags);
+ void onVolumeChanged(@NonNull MediaSession.Token sessionToken,
+ @AudioManager.Flags int flags);
/**
* Called when the default remote session is changed where the default remote session
diff --git a/media/jni/tuner/DemuxClient.cpp b/media/jni/tuner/DemuxClient.cpp
index 748d458..359ef36 100644
--- a/media/jni/tuner/DemuxClient.cpp
+++ b/media/jni/tuner/DemuxClient.cpp
@@ -216,14 +216,13 @@
Result DemuxClient::close() {
if (mTunerDemux != NULL) {
Status s = mTunerDemux->close();
+ mDemux = NULL;
return ClientHelper::getServiceSpecificErrorCode(s);
}
if (mDemux != NULL) {
Result res = mDemux->close();
- if (res == Result::SUCCESS) {
- mDemux = NULL;
- }
+ mDemux = NULL;
return res;
}
diff --git a/media/jni/tuner/DescramblerClient.cpp b/media/jni/tuner/DescramblerClient.cpp
index c9bacda..07be5cf 100644
--- a/media/jni/tuner/DescramblerClient.cpp
+++ b/media/jni/tuner/DescramblerClient.cpp
@@ -101,14 +101,18 @@
Result DescramblerClient::close() {
if (mTunerDescrambler != NULL) {
Status s = mTunerDescrambler->close();
+ mTunerDescrambler = NULL;
return ClientHelper::getServiceSpecificErrorCode(s);
}
if (mDescrambler != NULL) {
- return mDescrambler->close();
+ Result res = mDescrambler->close();
+ mDescrambler = NULL;
+ return res;
}
- return Result::INVALID_STATE;}
+ return Result::INVALID_STATE;
+}
/////////////// DescramblerClient Helper Methods ///////////////////////
diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp
index 0400485..7793180 100644
--- a/media/jni/tuner/DvrClient.cpp
+++ b/media/jni/tuner/DvrClient.cpp
@@ -316,14 +316,13 @@
Result DvrClient::close() {
if (mTunerDvr != NULL) {
Status s = mTunerDvr->close();
+ mTunerDvr = NULL;
return ClientHelper::getServiceSpecificErrorCode(s);
}
if (mDvr != NULL) {
Result res = mDvr->close();
- if (res == Result::SUCCESS) {
- mDvr = NULL;
- }
+ mDvr = NULL;
return res;
}
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index f618890..f31d465 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -262,14 +262,14 @@
if (mTunerFilter != NULL) {
Status s = mTunerFilter->close();
closeAvSharedMemory();
+ mTunerFilter = NULL;
return ClientHelper::getServiceSpecificErrorCode(s);
}
if (mFilter != NULL) {
Result res = mFilter->close();
- if (res == Result::SUCCESS) {
- mFilter = NULL;
- }
+ mFilter = NULL;
+ mFilter_1_1 = NULL;
closeAvSharedMemory();
return res;
}
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 0613223..9e36642 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -322,15 +322,14 @@
Result FrontendClient::close() {
if (mTunerFrontend != NULL) {
Status s = mTunerFrontend->close();
+ mTunerFrontend = NULL;
return ClientHelper::getServiceSpecificErrorCode(s);
}
if (mFrontend != NULL) {
Result result = mFrontend->close();
- if (result == Result::SUCCESS) {
- mFrontend = NULL;
- mFrontend_1_1 = NULL;
- }
+ mFrontend = NULL;
+ mFrontend_1_1 = NULL;
return result;
}
diff --git a/media/jni/tuner/LnbClient.cpp b/media/jni/tuner/LnbClient.cpp
index 8d08c25..5b6e46e 100644
--- a/media/jni/tuner/LnbClient.cpp
+++ b/media/jni/tuner/LnbClient.cpp
@@ -113,11 +113,14 @@
Result LnbClient::close() {
if (mTunerLnb != NULL) {
Status s = mTunerLnb->close();
+ mTunerLnb = NULL;
return ClientHelper::getServiceSpecificErrorCode(s);
}
if (mLnb != NULL) {
- return mLnb->close();
+ Result res = mLnb->close();
+ mLnb = NULL;
+ return res;
}
return Result::INVALID_STATE;
diff --git a/media/jni/tuner/TimeFilterClient.cpp b/media/jni/tuner/TimeFilterClient.cpp
index 432238d..e123c9f 100644
--- a/media/jni/tuner/TimeFilterClient.cpp
+++ b/media/jni/tuner/TimeFilterClient.cpp
@@ -126,11 +126,14 @@
Result TimeFilterClient::close() {
if (mTunerTimeFilter != NULL) {
Status s = mTunerTimeFilter->close();
+ mTunerTimeFilter = NULL;
return ClientHelper::getServiceSpecificErrorCode(s);
}
if (mTimeFilter != NULL) {
- return mTimeFilter->close();
+ Result res = mTimeFilter->close();
+ mTimeFilter = NULL;
+ return res;
}
return Result::INVALID_STATE;
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 92d7bf0..d7c8291 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -456,7 +456,7 @@
* @hide
*/
@SystemApi
- public static final int TETHERING_WIFI = TetheringManager.TETHERING_WIFI;
+ public static final int TETHERING_WIFI = 0;
/**
* USB tethering type.
@@ -464,7 +464,7 @@
* @hide
*/
@SystemApi
- public static final int TETHERING_USB = TetheringManager.TETHERING_USB;
+ public static final int TETHERING_USB = 1;
/**
* Bluetooth tethering type.
@@ -472,7 +472,7 @@
* @hide
*/
@SystemApi
- public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH;
+ public static final int TETHERING_BLUETOOTH = 2;
/**
* Wifi P2p tethering type.
@@ -2799,7 +2799,7 @@
*/
@SystemApi
@Deprecated
- public static final int TETHER_ERROR_NO_ERROR = TetheringManager.TETHER_ERROR_NO_ERROR;
+ public static final int TETHER_ERROR_NO_ERROR = 0;
/**
* @deprecated Use {@link TetheringManager#TETHER_ERROR_UNKNOWN_IFACE}.
* {@hide}
@@ -2875,8 +2875,7 @@
*/
@SystemApi
@Deprecated
- public static final int TETHER_ERROR_PROVISION_FAILED =
- TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+ public static final int TETHER_ERROR_PROVISION_FAILED = 11;
/**
* @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}.
* {@hide}
@@ -2890,8 +2889,7 @@
*/
@SystemApi
@Deprecated
- public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN =
- TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+ public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13;
/**
* Get a more detailed error code after a Tethering or Untethering
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 9d67f0b..26d14cb 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -2085,9 +2085,10 @@
/**
* Check if private dns is broken.
*
- * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken.
+ * @return {@code true} if private DNS is broken on this network.
* @hide
*/
+ @SystemApi
public boolean isPrivateDnsBroken() {
return mPrivateDnsBroken;
}
@@ -2330,6 +2331,17 @@
}
/**
+ * Completely clears the contents of this object, removing even the capabilities that are
+ * set by default when the object is constructed.
+ * @return this builder
+ */
+ @NonNull
+ public Builder clearAll() {
+ mCaps.clearAll();
+ return this;
+ }
+
+ /**
* Sets the owner UID.
*
* The default value is {@link Process#INVALID_UID}. Pass this value to reset.
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index b4a651c..4e3085f 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -16,22 +16,6 @@
package android.net;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -46,8 +30,6 @@
import android.text.TextUtils;
import android.util.proto.ProtoOutputStream;
-import java.util.Arrays;
-import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -172,30 +154,8 @@
* needed in terms of {@link NetworkCapabilities} features
*/
public static class Builder {
- /**
- * Capabilities that are currently compatible with VCN networks.
- */
- private static final List<Integer> VCN_SUPPORTED_CAPABILITIES = Arrays.asList(
- NET_CAPABILITY_CAPTIVE_PORTAL,
- NET_CAPABILITY_DUN,
- NET_CAPABILITY_FOREGROUND,
- NET_CAPABILITY_INTERNET,
- NET_CAPABILITY_NOT_CONGESTED,
- NET_CAPABILITY_NOT_METERED,
- NET_CAPABILITY_NOT_RESTRICTED,
- NET_CAPABILITY_NOT_ROAMING,
- NET_CAPABILITY_NOT_SUSPENDED,
- NET_CAPABILITY_NOT_VPN,
- NET_CAPABILITY_PARTIAL_CONNECTIVITY,
- NET_CAPABILITY_TEMPORARILY_NOT_METERED,
- NET_CAPABILITY_TRUSTED,
- NET_CAPABILITY_VALIDATED);
-
private final NetworkCapabilities mNetworkCapabilities;
- // A boolean that represents the user modified NOT_VCN_MANAGED capability.
- private boolean mModifiedNotVcnManaged = false;
-
/**
* Default constructor for Builder.
*/
@@ -217,7 +177,6 @@
// maybeMarkCapabilitiesRestricted() doesn't add back.
final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities);
nc.maybeMarkCapabilitiesRestricted();
- deduceNotVcnManagedCapability(nc);
return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE,
ConnectivityManager.REQUEST_ID_UNSET, Type.NONE);
}
@@ -234,9 +193,6 @@
*/
public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.addCapability(capability);
- if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
- mModifiedNotVcnManaged = true;
- }
return this;
}
@@ -248,9 +204,6 @@
*/
public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.removeCapability(capability);
- if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
- mModifiedNotVcnManaged = true;
- }
return this;
}
@@ -308,9 +261,6 @@
@NonNull
public Builder clearCapabilities() {
mNetworkCapabilities.clearAll();
- // If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities
- // should not be add back later.
- mModifiedNotVcnManaged = true;
return this;
}
@@ -430,25 +380,6 @@
mNetworkCapabilities.setSignalStrength(signalStrength);
return this;
}
-
- /**
- * Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities
- * and user intention, which includes:
- * 1. For the requests that don't have anything besides
- * {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to
- * allow the callers automatically utilize VCN networks if available.
- * 2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED,
- * do not alter them to allow user fire request that suits their need.
- *
- * @hide
- */
- private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) {
- if (mModifiedNotVcnManaged) return;
- for (final int cap : nc.getCapabilities()) {
- if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return;
- }
- nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
- }
}
// implement the Parcelable interface
diff --git a/packages/Connectivity/framework/src/android/net/TestNetworkManager.java b/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
index 4e89414..a174a7b 100644
--- a/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
+++ b/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
@@ -41,7 +41,6 @@
/**
* Prefix for tap interfaces created by this class.
- * @hide
*/
public static final String TEST_TAP_PREFIX = "testtap";
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index 8fc3181..ed1716f 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -25,7 +25,6 @@
],
srcs: [
"jni/com_android_server_TestNetworkService.cpp",
- "jni/com_android_server_connectivity_Vpn.cpp",
"jni/onload.cpp",
],
shared_libs: [
diff --git a/packages/Connectivity/service/jni/onload.cpp b/packages/Connectivity/service/jni/onload.cpp
index 3afcb0e..0012879 100644
--- a/packages/Connectivity/service/jni/onload.cpp
+++ b/packages/Connectivity/service/jni/onload.cpp
@@ -19,7 +19,6 @@
namespace android {
-int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_TestNetworkService(JNIEnv* env);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
@@ -29,12 +28,11 @@
return JNI_ERR;
}
- if (register_android_server_connectivity_Vpn(env) < 0
- || register_android_server_TestNetworkService(env) < 0) {
+ if (register_android_server_TestNetworkService(env) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
-};
\ No newline at end of file
+};
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 927da0a..2b357c5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -396,6 +396,28 @@
}
/**
+ * Check if USB data signaling (except from charging functions) is disabled by the admin.
+ * Only a device owner or a profile owner on an organization-owned managed profile can disable
+ * USB data signaling.
+ *
+ * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
+ * or {@code null} if USB data signaling is not disabled.
+ */
+ public static EnforcedAdmin checkIfUsbDataSignalingIsDisabled(Context context, int userId) {
+ DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ if (dpm == null || dpm.isUsbDataSignalingEnabledForUser(userId)) {
+ return null;
+ } else {
+ EnforcedAdmin admin = getProfileOrDeviceOwner(context, getUserHandleOf(userId));
+ int managedProfileId = getManagedProfileId(context, userId);
+ if (admin == null && managedProfileId != UserHandle.USER_NULL) {
+ admin = getProfileOrDeviceOwner(context, getUserHandleOf(managedProfileId));
+ }
+ return admin;
+ }
+ }
+
+ /**
* Check if {@param packageName} is restricted by the profile or device owner from using
* metered data.
*
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
index 64cb0f1..43717ab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
@@ -196,7 +196,7 @@
}
private void stopTrackingWifiRestart() {
- mWifiManager.unregisterWifiSubsystemRestartTrackingCallback(
+ mWifiManager.unregisterSubsystemRestartTrackingCallback(
mWifiSubsystemRestartTrackingCallback);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index caabf9a..1474f18 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -16,10 +16,13 @@
package com.android.settingslib.development;
+import static com.android.settingslib.RestrictedLockUtilsInternal.checkIfUsbDataSignalingIsDisabled;
+
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
@@ -28,9 +31,9 @@
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
import androidx.preference.TwoStatePreference;
+import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.ConfirmationDialogController;
public abstract class AbstractEnableAdbPreferenceController extends
@@ -44,7 +47,7 @@
public static final int ADB_SETTING_OFF = 0;
- protected SwitchPreference mPreference;
+ protected RestrictedSwitchPreference mPreference;
public AbstractEnableAdbPreferenceController(Context context) {
super(context);
@@ -54,7 +57,7 @@
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
if (isAvailable()) {
- mPreference = (SwitchPreference) screen.findPreference(KEY_ENABLE_ADB);
+ mPreference = (RestrictedSwitchPreference) screen.findPreference(KEY_ENABLE_ADB);
}
}
@@ -77,6 +80,10 @@
@Override
public void updateState(Preference preference) {
((TwoStatePreference) preference).setChecked(isAdbEnabled());
+ if (isAvailable()) {
+ ((RestrictedSwitchPreference) preference).setDisabledByAdmin(
+ checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId()));
+ }
}
public void enablePreference(boolean enabled) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
index e84a25c..5f53a92c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
@@ -19,18 +19,24 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
+
+import com.android.settingslib.RestrictedSwitchPreference;
import org.junit.Before;
import org.junit.Test;
@@ -43,26 +49,34 @@
@RunWith(RobolectricTestRunner.class)
public class EnableAdbPreferenceControllerTest {
+
+ private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("test", "test");
+
@Mock(answer = RETURNS_DEEP_STUBS)
private PreferenceScreen mScreen;
@Mock
private UserManager mUserManager;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private DevicePolicyManager mDevicePolicyManager;
private Context mContext;
- private SwitchPreference mPreference;
+ private RestrictedSwitchPreference mPreference;
private ConcreteEnableAdbPreferenceController mController;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ShadowApplication shadowContext = ShadowApplication.getInstance();
shadowContext.setSystemService(Context.USER_SERVICE, mUserManager);
mContext = spy(RuntimeEnvironment.application);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getSystemService(DevicePolicyManager.class)).thenReturn(mDevicePolicyManager);
+ doReturn(mContext).when(mContext).createPackageContextAsUser(
+ any(String.class), anyInt(), any(UserHandle.class));
mController = new ConcreteEnableAdbPreferenceController(mContext);
- mPreference = new SwitchPreference(mContext);
+ mPreference = new RestrictedSwitchPreference(mContext);
mPreference.setKey(mController.getPreferenceKey());
when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference);
}
@@ -125,6 +139,9 @@
@Test
public void updateState_settingsOn_shouldCheck() {
when(mUserManager.isAdminUser()).thenReturn(true);
+ when(mDevicePolicyManager.getProfileOwner()).thenReturn(TEST_COMPONENT_NAME);
+ when(mDevicePolicyManager.isUsbDataSignalingEnabledForUser(
+ UserHandle.myUserId())).thenReturn(true);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.ADB_ENABLED, 1);
mPreference.setChecked(false);
@@ -138,6 +155,9 @@
@Test
public void updateState_settingsOff_shouldUncheck() {
when(mUserManager.isAdminUser()).thenReturn(true);
+ when(mDevicePolicyManager.getProfileOwner()).thenReturn(TEST_COMPONENT_NAME);
+ when(mDevicePolicyManager.isUsbDataSignalingEnabledForUser(
+ UserHandle.myUserId())).thenReturn(true);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.ADB_ENABLED, 0);
mPreference.setChecked(true);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index a6e2af9..9cd7083 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -24,6 +24,7 @@
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
@@ -212,7 +213,6 @@
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
-
byte[] systemSettingsData = getSystemSettings();
byte[] secureSettingsData = getSecureSettings();
byte[] globalSettingsData = getGlobalSettings();
@@ -1204,17 +1204,25 @@
}
private byte[] getSimSpecificSettingsData() {
- SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
- byte[] simSpecificData = subManager.getAllSimSpecificSettingsForBackup();
- Log.i(TAG, "sim specific data of length + " + simSpecificData.length
+ byte[] simSpecificData = new byte[0];
+ PackageManager packageManager = getBaseContext().getPackageManager();
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
+ simSpecificData = subManager.getAllSimSpecificSettingsForBackup();
+ Log.i(TAG, "sim specific data of length + " + simSpecificData.length
+ " successfully retrieved");
+ }
return simSpecificData;
}
private void restoreSimSpecificSettings(byte[] data) {
- SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
- subManager.restoreAllSimSpecificSettingsFromBackup(data);
+ PackageManager packageManager = getBaseContext().getPackageManager();
+ boolean hasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+ if (hasTelephony) {
+ SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
+ subManager.restoreAllSimSpecificSettingsFromBackup(data);
+ }
}
private void updateWindowManagerIfNeeded(Integer previousDensity) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 438cec8..6719f17 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -323,6 +323,7 @@
Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+ Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE,
Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
Settings.Global.LOCK_SOUND,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 35a4e81..539a81b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -100,6 +100,7 @@
<uses-permission android:name="android.permission.REBOOT" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.POWER_SAVER" />
+ <uses-permission android:name="android.permission.BATTERY_PREDICTION" />
<uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" />
<uses-permission android:name="android.permission.BACKUP" />
<uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7bfb42b..bf5198e 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -194,6 +194,5 @@
dxflags: ["--multi-dex"],
required: [
"privapp_whitelist_com.android.systemui",
- "checked-wm_shell_protolog.json",
],
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5af0244..2faca8d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -600,6 +600,14 @@
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false" />
+ <!-- ContentProvider that returns a People Tile preview for a given shortcut -->
+ <provider
+ android:name="com.android.systemui.people.PeopleProvider"
+ android:authorities="com.android.systemui.people.PeopleProvider"
+ android:exported="true"
+ android:permission="android.permission.GET_PEOPLE_TILE_PREVIEW">
+ </provider>
+
<!-- a gallery of delicious treats -->
<service
android:name=".DessertCaseDream"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
index d43aaf0..beee03b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
@@ -46,6 +46,21 @@
return true;
}
+ /**
+ * @return if detail panel should animate when shown or closed
+ */
+ default boolean shouldAnimate() {
+ return true;
+ }
+
+ /**
+ * @return true if the callback handled the event and wants to keep the detail panel open, false
+ * otherwise. Returning false will close the panel.
+ */
+ default boolean onDoneButtonClicked() {
+ return false;
+ }
+
default UiEventLogger.UiEventEnum openDetailEvent() {
return INVALID;
}
diff --git a/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml
new file mode 100644
index 0000000..51c442a
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight"
+ android:radius="40dp"/>
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
new file mode 100644
index 0000000..3938b73
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<!-- This is a view that shows a user switcher in Keyguard. -->
+<com.android.systemui.statusbar.phone.UserAvatarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/keyguard_qs_user_switch_view"
+ android:layout_width="@dimen/kg_framed_avatar_size"
+ android:layout_height="@dimen/kg_framed_avatar_size"
+ android:layout_centerHorizontal="true"
+ android:layout_gravity="center_horizontal|bottom"
+ systemui:avatarPadding="0dp"
+ systemui:badgeDiameter="18dp"
+ systemui:badgeMargin="1dp"
+ systemui:frameColor="@color/kg_user_avatar_frame"
+ systemui:framePadding="0dp"
+ systemui:frameWidth="0dp">
+</com.android.systemui.statusbar.phone.UserAvatarView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 859d904..a8ef7c3 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -32,6 +32,12 @@
android:visibility="gone" />
<ViewStub
+ android:id="@+id/keyguard_qs_user_switch_stub"
+ android:layout="@layout/keyguard_qs_user_switch"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent" />
+
+ <ViewStub
android:id="@+id/keyguard_user_switcher_stub"
android:layout="@layout/keyguard_user_switcher"
android:layout_height="match_parent"
@@ -63,6 +69,13 @@
systemui:layout_constraintEnd_toEndOf="parent"
/>
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/qs_edge_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ systemui:layout_constraintGuide_percent="0.5"
+ android:orientation="vertical"/>
+
<com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"
android:layout_marginTop="@dimen/notification_panel_margin_top"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6196225..78927f8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -279,6 +279,11 @@
<!-- Whether to show the full screen user switcher. -->
<bool name="config_enableFullscreenUserSwitcher">false</bool>
+ <!-- Whether the multi-user switch on the keyguard opens QS user panel. If false, clicking the
+ user switch on the keyguard will replace the notifications and status area with the user
+ switcher. The multi-user switch is only shown if config_keyguardUserSwitcher=false. -->
+ <bool name="config_keyguard_user_switch_opens_qs_details">false</bool>
+
<!-- SystemUIFactory component -->
<string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 594fbdf..0d92aea 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -802,7 +802,7 @@
<!-- Size of user icon + frame in the qs user picker (incl. frame) -->
<dimen name="qs_framed_avatar_size">54dp</dimen>
<!-- Size of user icon + frame in the keyguard user picker (incl. frame) -->
- <dimen name="kg_framed_avatar_size">32dp</dimen>
+ <dimen name="kg_framed_avatar_size">48dp</dimen>
<!-- Margin on the left side of the carrier text on Keyguard -->
<dimen name="keyguard_carrier_text_margin">16dp</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 6d731f8..d4bb128 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -38,5 +38,8 @@
<!-- People Tile flag -->
<bool name="flag_conversations">false</bool>
+ <!-- The new animations to/from lockscreen and AOD! -->
+ <bool name="flag_lockscreen_animations">false</bool>
+
<bool name="flag_toast_style">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d997ca2..32381be 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1341,6 +1341,9 @@
<!-- Monitoring dialog: Description of Network Logging. [CHAR LIMIT=NONE]-->
<string name="monitoring_description_management_network_logging">Your admin has turned on network logging, which monitors traffic on your device.</string>
+ <!-- Monitoring dialog: Description of Network Logging in the work profile. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_managed_profile_network_logging">Your admin has turned on network logging, which monitors traffic in your work profile but not in your personal profile.</string>
+
<!-- Monitoring dialog: Description of an active VPN. [CHAR LIMIT=NONE]-->
<string name="monitoring_description_named_vpn">You\'re connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PeopleProviderUtils.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PeopleProviderUtils.java
new file mode 100644
index 0000000..15cf369
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PeopleProviderUtils.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * These strings are part of the {@link com.android.systemui.people.PeopleProvider} API
+ * contract. The API returns a People Tile preview that can be displayed by calling packages.
+ * The provider is part of the SystemUI service, and the strings live here for shared access with
+ * Launcher (caller).
+ */
+public class PeopleProviderUtils {
+ /**
+ * ContentProvider URI scheme.
+ * @hide
+ */
+ public static final String PEOPLE_PROVIDER_SCHEME = "content://";
+
+ /**
+ * ContentProvider URI authority.
+ * @hide
+ */
+ public static final String PEOPLE_PROVIDER_AUTHORITY =
+ "com.android.systemui.people.PeopleProvider";
+
+ /**
+ * Method name for getting People Tile preview.
+ * @hide
+ */
+ public static final String GET_PEOPLE_TILE_PREVIEW_METHOD = "get_people_tile_preview";
+
+ /**
+ * Extras bundle key specifying shortcut Id of the People Tile preview requested.
+ * @hide
+ */
+ public static final String EXTRAS_KEY_SHORTCUT_ID = "shortcut_id";
+
+ /**
+ * Extras bundle key specifying package name of the People Tile preview requested.
+ * @hide
+ */
+ public static final String EXTRAS_KEY_PACKAGE_NAME = "package_name";
+
+ /**
+ * Extras bundle key specifying {@code UserHandle} of the People Tile preview requested.
+ * @hide
+ */
+ public static final String EXTRAS_KEY_USER_HANDLE = "user_handle";
+
+ /**
+ * Response bundle key to access the returned People Tile preview.
+ * @hide
+ */
+ public static final String RESPONSE_KEY_REMOTE_VIEWS = "remote_views";
+
+ /**
+ * Name of the permission needed to get a People Tile preview for a given conversation shortcut.
+ * @hide
+ */
+ public static final String GET_PEOPLE_TILE_PREVIEW_PERMISSION =
+ "android.permission.GET_PEOPLE_TILE_PREVIEW";
+
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index f6b03c1..e4f6e131 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -24,41 +24,14 @@
import android.view.View;
import android.widget.TextView;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import java.util.Locale;
public class CarrierText extends TextView {
- private static final boolean DEBUG = KeyguardConstants.DEBUG;
- private static final String TAG = "CarrierText";
+ private final boolean mShowMissingSim;
- private static CharSequence mSeparator;
-
- private boolean mShowMissingSim;
-
- private boolean mShowAirplaneMode;
- private boolean mShouldMarquee;
-
- private CarrierTextController mCarrierTextController;
-
- private CarrierTextController.CarrierTextCallback mCarrierTextCallback =
- new CarrierTextController.CarrierTextCallback() {
- @Override
- public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
- setText(info.carrierText);
- }
-
- @Override
- public void startedGoingToSleep() {
- setSelected(false);
- }
-
- @Override
- public void finishedWakingUp() {
- setSelected(true);
- }
- };
+ private final boolean mShowAirplaneMode;
public CarrierText(Context context) {
this(context, null);
@@ -78,30 +51,6 @@
}
setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
}
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSeparator = getResources().getString(
- com.android.internal.R.string.kg_text_message_separator);
- mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode,
- mShowMissingSim);
- mShouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
- setSelected(mShouldMarquee); // Allow marquee to work.
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mCarrierTextController.setListening(mCarrierTextCallback);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mCarrierTextController.setListening(null);
- }
-
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
@@ -113,7 +62,15 @@
}
}
- private class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
+ public boolean getShowAirplaneMode() {
+ return mShowAirplaneMode;
+ }
+
+ public boolean getShowMissingSim() {
+ return mShowMissingSim;
+ }
+
+ private static class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
private final Locale mLocale;
private final boolean mAllCaps;
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index b1e1434..46a6d8b 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,680 +16,61 @@
package com.android.keyguard;
-import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
-import static android.telephony.PhoneStateListener.LISTEN_NONE;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settingslib.WirelessUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
+import com.android.systemui.util.ViewController;
import javax.inject.Inject;
/**
- * Controller that generates text including the carrier names and/or the status of all the SIM
- * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
- * separated by a given separator {@link CharSequence}.
+ * Controller for {@link CarrierText}.
*/
-public class CarrierTextController {
- private static final boolean DEBUG = KeyguardConstants.DEBUG;
- private static final String TAG = "CarrierTextController";
+public class CarrierTextController extends ViewController<CarrierText> {
+ private final CarrierTextManager mCarrierTextManager;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final boolean mIsEmergencyCallCapable;
- private final Handler mMainHandler;
- private final Handler mBgHandler;
- private boolean mTelephonyCapable;
- private boolean mShowMissingSim;
- private boolean mShowAirplaneMode;
- private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
- @VisibleForTesting
- protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private WifiManager mWifiManager;
- private boolean[] mSimErrorState;
- private final int mSimSlotsNumber;
- @Nullable // Check for nullability before dispatching
- private CarrierTextCallback mCarrierTextCallback;
- private Context mContext;
- private CharSequence mSeparator;
- private WakefulnessLifecycle mWakefulnessLifecycle;
- private final WakefulnessLifecycle.Observer mWakefulnessObserver =
- new WakefulnessLifecycle.Observer() {
+ private final CarrierTextManager.CarrierTextCallback mCarrierTextCallback =
+ new CarrierTextManager.CarrierTextCallback() {
@Override
- public void onFinishedWakingUp() {
- final CarrierTextCallback callback = mCarrierTextCallback;
- if (callback != null) callback.finishedWakingUp();
+ public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
+ mView.setText(info.carrierText);
}
@Override
- public void onStartedGoingToSleep() {
- final CarrierTextCallback callback = mCarrierTextCallback;
- if (callback != null) callback.startedGoingToSleep();
+ public void startedGoingToSleep() {
+ mView.setSelected(false);
+ }
+
+ @Override
+ public void finishedWakingUp() {
+ mView.setSelected(true);
}
};
- @VisibleForTesting
- protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onRefreshCarrierInfo() {
- if (DEBUG) {
- Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
- + Boolean.toString(mTelephonyCapable));
- }
- updateCarrierText();
- }
+ @Inject
+ public CarrierTextController(CarrierText view,
+ CarrierTextManager.Builder carrierTextManagerBuilder,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ super(view);
- @Override
- public void onTelephonyCapable(boolean capable) {
- if (DEBUG) {
- Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
- + Boolean.toString(capable));
- }
- mTelephonyCapable = capable;
- updateCarrierText();
- }
-
- public void onSimStateChanged(int subId, int slotId, int simState) {
- if (slotId < 0 || slotId >= mSimSlotsNumber) {
- Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
- + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
- return;
- }
-
- if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
- if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
- mSimErrorState[slotId] = true;
- updateCarrierText();
- } else if (mSimErrorState[slotId]) {
- mSimErrorState[slotId] = false;
- updateCarrierText();
- }
- }
- };
-
- private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- @Override
- public void onActiveDataSubscriptionIdChanged(int subId) {
- mActiveMobileDataSubscription = subId;
- if (mNetworkSupported.get() && mCarrierTextCallback != null) {
- updateCarrierText();
- }
- }
- };
-
- /**
- * The status of this lock screen. Primarily used for widgets on LockScreen.
- */
- private enum StatusMode {
- Normal, // Normal case (sim card present, it's not locked)
- NetworkLocked, // SIM card is 'network locked'.
- SimMissing, // SIM card is missing.
- SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
- SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
- SimLocked, // SIM card is currently locked
- SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
- SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
- SimIoError, // SIM card is faulty
- SimUnknown // SIM card is unknown
+ mCarrierTextManager = carrierTextManagerBuilder
+ .setShowAirplaneMode(mView.getShowAirplaneMode())
+ .setShowMissingSim(mView.getShowMissingSim())
+ .build();
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
}
- /**
- * Controller that provides updates on text with carriers names or SIM status.
- * Used by {@link CarrierText}.
- *
- * @param separator Separator between different parts of the text
- */
- public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
- boolean showMissingSim) {
- mContext = context;
- mIsEmergencyCallCapable = getTelephonyManager().isVoiceCapable();
-
- mShowAirplaneMode = showAirplaneMode;
- mShowMissingSim = showMissingSim;
-
- mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- mSeparator = separator;
- mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
- mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
- mSimErrorState = new boolean[mSimSlotsNumber];
- mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
- mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
- mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
- mBgHandler.post(() -> {
- boolean supported = ConnectivityManager.from(mContext).isNetworkSupported(
- ConnectivityManager.TYPE_MOBILE);
- if (supported && mNetworkSupported.compareAndSet(false, supported)) {
- // This will set/remove the listeners appropriately. Note that it will never double
- // add the listeners.
- handleSetListening(mCarrierTextCallback);
- }
- });
+ @Override
+ protected void onInit() {
+ super.onInit();
+ mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
}
- private TelephonyManager getTelephonyManager() {
- return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ @Override
+ protected void onViewAttached() {
+ mCarrierTextManager.setListening(mCarrierTextCallback);
}
- /**
- * Checks if there are faulty cards. Adds the text depending on the slot of the card
- *
- * @param text: current carrier text based on the sim state
- * @param carrierNames names order by subscription order
- * @param subOrderBySlot array containing the sub index for each slot ID
- * @param noSims: whether a valid sim card is inserted
- * @return text
- */
- private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
- CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
- final CharSequence carrier = "";
- CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
- TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier);
- // mSimErrorState has the state of each sim indexed by slotID.
- for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
- if (!mSimErrorState[index]) {
- continue;
- }
- // In the case when no sim cards are detected but a faulty card is inserted
- // overwrite the text and only show "Invalid card"
- if (noSims) {
- return concatenate(carrierTextForSimIOError,
- getContext().getText(
- com.android.internal.R.string.emergency_calls_only),
- mSeparator);
- } else if (subOrderBySlot[index] != -1) {
- int subIndex = subOrderBySlot[index];
- // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
- carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
- carrierNames[subIndex],
- mSeparator);
- } else {
- // concatenate "Invalid card" when faulty card is inserted in other slot
- text = concatenate(text, carrierTextForSimIOError, mSeparator);
- }
-
- }
- return text;
- }
-
- /**
- * This may be called internally after retrieving the correct value of {@code mNetworkSupported}
- * (assumed false to start). In that case, the following happens:
- * <ul>
- * <li> If there was a registered callback, and the network is supported, it will register
- * listeners.
- * <li> If there was not a registered callback, it will try to remove unregistered listeners
- * which is a no-op
- * </ul>
- *
- * This call will always be processed in a background thread.
- */
- private void handleSetListening(CarrierTextCallback callback) {
- TelephonyManager telephonyManager = getTelephonyManager();
- if (callback != null) {
- mCarrierTextCallback = callback;
- if (mNetworkSupported.get()) {
- // Keyguard update monitor expects callbacks from main thread
- mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
- mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
- telephonyManager.listen(mPhoneStateListener,
- LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
- } else {
- // Don't listen and clear out the text when the device isn't a phone.
- mMainHandler.post(() -> callback.updateCarrierInfo(
- new CarrierTextCallbackInfo("", null, false, null)
- ));
- }
- } else {
- mCarrierTextCallback = null;
- mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
- mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
- telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
- }
- }
-
- /**
- * Sets the listening status of this controller. If the callback is null, it is set to
- * not listening.
- *
- * @param callback Callback to provide text updates
- */
- public void setListening(CarrierTextCallback callback) {
- mBgHandler.post(() -> handleSetListening(callback));
- }
-
- protected List<SubscriptionInfo> getSubscriptionInfo() {
- return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
- }
-
- protected void updateCarrierText() {
- boolean allSimsMissing = true;
- boolean anySimReadyAndInService = false;
- CharSequence displayText = null;
- List<SubscriptionInfo> subs = getSubscriptionInfo();
-
- final int numSubs = subs.size();
- final int[] subsIds = new int[numSubs];
- // This array will contain in position i, the index of subscription in slot ID i.
- // -1 if no subscription in that slot
- final int[] subOrderBySlot = new int[mSimSlotsNumber];
- for (int i = 0; i < mSimSlotsNumber; i++) {
- subOrderBySlot[i] = -1;
- }
- final CharSequence[] carrierNames = new CharSequence[numSubs];
- if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
-
- for (int i = 0; i < numSubs; i++) {
- int subId = subs.get(i).getSubscriptionId();
- carrierNames[i] = "";
- subsIds[i] = subId;
- subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
- int simState = mKeyguardUpdateMonitor.getSimState(subId);
- CharSequence carrierName = subs.get(i).getCarrierName();
- CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
- if (DEBUG) {
- Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
- }
- if (carrierTextForSimState != null) {
- allSimsMissing = false;
- carrierNames[i] = carrierTextForSimState;
- }
- if (simState == TelephonyManager.SIM_STATE_READY) {
- ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
- if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
- // hack for WFC (IWLAN) not turning off immediately once
- // Wi-Fi is disassociated or disabled
- if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
- || (mWifiManager.isWifiEnabled()
- && mWifiManager.getConnectionInfo() != null
- && mWifiManager.getConnectionInfo().getBSSID() != null)) {
- if (DEBUG) {
- Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
- }
- anySimReadyAndInService = true;
- }
- }
- }
- }
- // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
- // This condition will also be true always when numSubs == 0
- if (allSimsMissing && !anySimReadyAndInService) {
- if (numSubs != 0) {
- // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
- // This depends on mPlmn containing the text "Emergency calls only" when the radio
- // has some connectivity. Otherwise, it should be null or empty and just show
- // "No SIM card"
- // Grab the first subscripton, because they all should contain the emergency text,
- // described above.
- displayText = makeCarrierStringOnEmergencyCapable(
- getMissingSimMessage(), subs.get(0).getCarrierName());
- } else {
- // We don't have a SubscriptionInfo to get the emergency calls only from.
- // Grab it from the old sticky broadcast if possible instead. We can use it
- // here because no subscriptions are active, so we don't have
- // to worry about MSIM clashing.
- CharSequence text =
- getContext().getText(com.android.internal.R.string.emergency_calls_only);
- Intent i = getContext().registerReceiver(null,
- new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
- if (i != null) {
- String spn = "";
- String plmn = "";
- if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
- spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
- }
- if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
- plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
- }
- if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
- if (Objects.equals(plmn, spn)) {
- text = plmn;
- } else {
- text = concatenate(plmn, spn, mSeparator);
- }
- }
- displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
- }
- }
-
- if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
-
- displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
- allSimsMissing);
-
- boolean airplaneMode = false;
- // APM (airplane mode) != no carrier state. There are carrier services
- // (e.g. WFC = Wi-Fi calling) which may operate in APM.
- if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
- displayText = getAirplaneModeMessage();
- airplaneMode = true;
- }
-
- final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
- displayText,
- carrierNames,
- !allSimsMissing,
- subsIds,
- airplaneMode);
- postToCallback(info);
- }
-
- @VisibleForTesting
- protected void postToCallback(CarrierTextCallbackInfo info) {
- final CarrierTextCallback callback = mCarrierTextCallback;
- if (callback != null) {
- mMainHandler.post(() -> callback.updateCarrierInfo(info));
- }
- }
-
- private Context getContext() {
- return mContext;
- }
-
- private String getMissingSimMessage() {
- return mShowMissingSim && mTelephonyCapable
- ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
- }
-
- private String getAirplaneModeMessage() {
- return mShowAirplaneMode
- ? getContext().getString(R.string.airplane_mode) : "";
- }
-
- /**
- * Top-level function for creating carrier text. Makes text based on simState, PLMN
- * and SPN as well as device capabilities, such as being emergency call capable.
- *
- * @return Carrier text if not in missing state, null otherwise.
- */
- private CharSequence getCarrierTextForSimState(int simState, CharSequence text) {
- CharSequence carrierText = null;
- CarrierTextController.StatusMode status = getStatusForIccState(simState);
- switch (status) {
- case Normal:
- carrierText = text;
- break;
-
- case SimNotReady:
- // Null is reserved for denoting missing, in this case we have nothing to display.
- carrierText = ""; // nothing to display yet.
- break;
-
- case NetworkLocked:
- carrierText = makeCarrierStringOnEmergencyCapable(
- mContext.getText(R.string.keyguard_network_locked_message), text);
- break;
-
- case SimMissing:
- carrierText = null;
- break;
-
- case SimPermDisabled:
- carrierText = makeCarrierStringOnEmergencyCapable(
- getContext().getText(
- R.string.keyguard_permanent_disabled_sim_message_short),
- text);
- break;
-
- case SimMissingLocked:
- carrierText = null;
- break;
-
- case SimLocked:
- carrierText = makeCarrierStringOnLocked(
- getContext().getText(R.string.keyguard_sim_locked_message),
- text);
- break;
-
- case SimPukLocked:
- carrierText = makeCarrierStringOnLocked(
- getContext().getText(R.string.keyguard_sim_puk_locked_message),
- text);
- break;
- case SimIoError:
- carrierText = makeCarrierStringOnEmergencyCapable(
- getContext().getText(R.string.keyguard_sim_error_message_short),
- text);
- break;
- case SimUnknown:
- carrierText = null;
- break;
- }
-
- return carrierText;
- }
-
- /*
- * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
- */
- private CharSequence makeCarrierStringOnEmergencyCapable(
- CharSequence simMessage, CharSequence emergencyCallMessage) {
- if (mIsEmergencyCallCapable) {
- return concatenate(simMessage, emergencyCallMessage, mSeparator);
- }
- return simMessage;
- }
-
- /*
- * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
- * DSDS
- */
- private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
- CharSequence carrierName) {
- final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
- final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
- if (simMessageValid && carrierNameValid) {
- return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
- carrierName, simMessage);
- } else if (simMessageValid) {
- return simMessage;
- } else if (carrierNameValid) {
- return carrierName;
- } else {
- return "";
- }
- }
-
- /**
- * Determine the current status of the lock screen given the SIM state and other stuff.
- */
- private CarrierTextController.StatusMode getStatusForIccState(int simState) {
- final boolean missingAndNotProvisioned =
- !mKeyguardUpdateMonitor.isDeviceProvisioned()
- && (simState == TelephonyManager.SIM_STATE_ABSENT
- || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
-
- // Assume we're NETWORK_LOCKED if not provisioned
- simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState;
- switch (simState) {
- case TelephonyManager.SIM_STATE_ABSENT:
- return CarrierTextController.StatusMode.SimMissing;
- case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
- return CarrierTextController.StatusMode.SimMissingLocked;
- case TelephonyManager.SIM_STATE_NOT_READY:
- return CarrierTextController.StatusMode.SimNotReady;
- case TelephonyManager.SIM_STATE_PIN_REQUIRED:
- return CarrierTextController.StatusMode.SimLocked;
- case TelephonyManager.SIM_STATE_PUK_REQUIRED:
- return CarrierTextController.StatusMode.SimPukLocked;
- case TelephonyManager.SIM_STATE_READY:
- return CarrierTextController.StatusMode.Normal;
- case TelephonyManager.SIM_STATE_PERM_DISABLED:
- return CarrierTextController.StatusMode.SimPermDisabled;
- case TelephonyManager.SIM_STATE_UNKNOWN:
- return CarrierTextController.StatusMode.SimUnknown;
- case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
- return CarrierTextController.StatusMode.SimIoError;
- }
- return CarrierTextController.StatusMode.SimUnknown;
- }
-
- private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
- CharSequence separator) {
- final boolean plmnValid = !TextUtils.isEmpty(plmn);
- final boolean spnValid = !TextUtils.isEmpty(spn);
- if (plmnValid && spnValid) {
- return new StringBuilder().append(plmn).append(separator).append(spn).toString();
- } else if (plmnValid) {
- return plmn;
- } else if (spnValid) {
- return spn;
- } else {
- return "";
- }
- }
-
- /**
- * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
- * separator added so there are no extra separators that are not needed.
- */
- private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
- int length = sequences.length;
- if (length == 0) return "";
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < length; i++) {
- if (!TextUtils.isEmpty(sequences[i])) {
- if (!TextUtils.isEmpty(sb)) {
- sb.append(separator);
- }
- sb.append(sequences[i]);
- }
- }
- return sb.toString();
- }
-
- private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
- if (!TextUtils.isEmpty(string)) {
- list.add(string);
- }
- return list;
- }
-
- private CharSequence getCarrierHelpTextForSimState(int simState,
- String plmn, String spn) {
- int carrierHelpTextId = 0;
- CarrierTextController.StatusMode status = getStatusForIccState(simState);
- switch (status) {
- case NetworkLocked:
- carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
- break;
-
- case SimMissing:
- carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
- break;
-
- case SimPermDisabled:
- carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
- break;
-
- case SimMissingLocked:
- carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
- break;
-
- case Normal:
- case SimLocked:
- case SimPukLocked:
- break;
- }
-
- return mContext.getText(carrierHelpTextId);
- }
-
- public static class Builder {
- private final Context mContext;
- private final String mSeparator;
- private boolean mShowAirplaneMode;
- private boolean mShowMissingSim;
-
- @Inject
- public Builder(Context context, @Main Resources resources) {
- mContext = context;
- mSeparator = resources.getString(
- com.android.internal.R.string.kg_text_message_separator);
- }
-
-
- public Builder setShowAirplaneMode(boolean showAirplaneMode) {
- mShowAirplaneMode = showAirplaneMode;
- return this;
- }
-
- public Builder setShowMissingSim(boolean showMissingSim) {
- mShowMissingSim = showMissingSim;
- return this;
- }
-
- public CarrierTextController build() {
- return new CarrierTextController(
- mContext, mSeparator, mShowAirplaneMode, mShowMissingSim);
- }
- }
- /**
- * Data structure for passing information to CarrierTextController subscribers
- */
- public static final class CarrierTextCallbackInfo {
- public final CharSequence carrierText;
- public final CharSequence[] listOfCarriers;
- public final boolean anySimReady;
- public final int[] subscriptionIds;
- public boolean airplaneMode;
-
- @VisibleForTesting
- public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
- boolean anySimReady, int[] subscriptionIds) {
- this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
- }
-
- @VisibleForTesting
- public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
- boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
- this.carrierText = carrierText;
- this.listOfCarriers = listOfCarriers;
- this.anySimReady = anySimReady;
- this.subscriptionIds = subscriptionIds;
- this.airplaneMode = airplaneMode;
- }
- }
-
- /**
- * Callback to communicate to Views
- */
- public interface CarrierTextCallback {
- /**
- * Provides updated carrier information.
- */
- default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
-
- /**
- * Notifies the View that the device is going to sleep
- */
- default void startedGoingToSleep() {};
-
- /**
- * Notifies the View that the device finished waking up
- */
- default void finishedWakingUp() {};
+ @Override
+ protected void onViewDetached() {
+ mCarrierTextManager.setListening(null);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
new file mode 100644
index 0000000..87b01e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
+import static android.telephony.PhoneStateListener.LISTEN_NONE;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.WirelessUtils;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.inject.Inject;
+
+/**
+ * Controller that generates text including the carrier names and/or the status of all the SIM
+ * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
+ * separated by a given separator {@link CharSequence}.
+ */
+public class CarrierTextManager {
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final String TAG = "CarrierTextController";
+
+ private final boolean mIsEmergencyCallCapable;
+ private final Handler mMainHandler;
+ private final Handler mBgHandler;
+ private boolean mTelephonyCapable;
+ private final boolean mShowMissingSim;
+ private final boolean mShowAirplaneMode;
+ private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
+ @VisibleForTesting
+ protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final WifiManager mWifiManager;
+ private final boolean[] mSimErrorState;
+ private final int mSimSlotsNumber;
+ @Nullable // Check for nullability before dispatching
+ private CarrierTextCallback mCarrierTextCallback;
+ private final Context mContext;
+ private final TelephonyManager mTelephonyManager;
+ private final CharSequence mSeparator;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final WakefulnessLifecycle.Observer mWakefulnessObserver =
+ new WakefulnessLifecycle.Observer() {
+ @Override
+ public void onFinishedWakingUp() {
+ final CarrierTextCallback callback = mCarrierTextCallback;
+ if (callback != null) callback.finishedWakingUp();
+ }
+
+ @Override
+ public void onStartedGoingToSleep() {
+ final CarrierTextCallback callback = mCarrierTextCallback;
+ if (callback != null) callback.startedGoingToSleep();
+ }
+ };
+
+ @VisibleForTesting
+ protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onRefreshCarrierInfo() {
+ if (DEBUG) {
+ Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
+ + Boolean.toString(mTelephonyCapable));
+ }
+ updateCarrierText();
+ }
+
+ @Override
+ public void onTelephonyCapable(boolean capable) {
+ if (DEBUG) {
+ Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
+ + Boolean.toString(capable));
+ }
+ mTelephonyCapable = capable;
+ updateCarrierText();
+ }
+
+ public void onSimStateChanged(int subId, int slotId, int simState) {
+ if (slotId < 0 || slotId >= mSimSlotsNumber) {
+ Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
+ + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
+ if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) {
+ mSimErrorState[slotId] = true;
+ updateCarrierText();
+ } else if (mSimErrorState[slotId]) {
+ mSimErrorState[slotId] = false;
+ updateCarrierText();
+ }
+ }
+ };
+
+ private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onActiveDataSubscriptionIdChanged(int subId) {
+ if (mNetworkSupported.get() && mCarrierTextCallback != null) {
+ updateCarrierText();
+ }
+ }
+ };
+
+ /**
+ * The status of this lock screen. Primarily used for widgets on LockScreen.
+ */
+ private enum StatusMode {
+ Normal, // Normal case (sim card present, it's not locked)
+ NetworkLocked, // SIM card is 'network locked'.
+ SimMissing, // SIM card is missing.
+ SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
+ SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
+ SimLocked, // SIM card is currently locked
+ SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
+ SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
+ SimIoError, // SIM card is faulty
+ SimUnknown // SIM card is unknown
+ }
+
+ /**
+ * Controller that provides updates on text with carriers names or SIM status.
+ * Used by {@link CarrierText}.
+ *
+ * @param separator Separator between different parts of the text
+ */
+ private CarrierTextManager(Context context, CharSequence separator, boolean showAirplaneMode,
+ boolean showMissingSim, @Nullable WifiManager wifiManager,
+ ConnectivityManager connectivityManager, TelephonyManager telephonyManager,
+ WakefulnessLifecycle wakefulnessLifecycle, @Main Handler mainHandler,
+ @Background Handler bgHandler, KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ mContext = context;
+ mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
+
+ mShowAirplaneMode = showAirplaneMode;
+ mShowMissingSim = showMissingSim;
+
+ mWifiManager = wifiManager;
+ mTelephonyManager = telephonyManager;
+ mSeparator = separator;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
+ mSimErrorState = new boolean[mSimSlotsNumber];
+ mMainHandler = mainHandler;
+ mBgHandler = bgHandler;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mBgHandler.post(() -> {
+ boolean supported = connectivityManager.isNetworkSupported(
+ ConnectivityManager.TYPE_MOBILE);
+ if (supported && mNetworkSupported.compareAndSet(false, supported)) {
+ // This will set/remove the listeners appropriately. Note that it will never double
+ // add the listeners.
+ handleSetListening(mCarrierTextCallback);
+ }
+ });
+ }
+
+ private TelephonyManager getTelephonyManager() {
+ return mTelephonyManager;
+ }
+
+ /**
+ * Checks if there are faulty cards. Adds the text depending on the slot of the card
+ *
+ * @param text: current carrier text based on the sim state
+ * @param carrierNames names order by subscription order
+ * @param subOrderBySlot array containing the sub index for each slot ID
+ * @param noSims: whether a valid sim card is inserted
+ * @return text
+ */
+ private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
+ CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
+ final CharSequence carrier = "";
+ CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
+ TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier);
+ // mSimErrorState has the state of each sim indexed by slotID.
+ for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
+ if (!mSimErrorState[index]) {
+ continue;
+ }
+ // In the case when no sim cards are detected but a faulty card is inserted
+ // overwrite the text and only show "Invalid card"
+ if (noSims) {
+ return concatenate(carrierTextForSimIOError,
+ getContext().getText(
+ com.android.internal.R.string.emergency_calls_only),
+ mSeparator);
+ } else if (subOrderBySlot[index] != -1) {
+ int subIndex = subOrderBySlot[index];
+ // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
+ carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
+ carrierNames[subIndex],
+ mSeparator);
+ } else {
+ // concatenate "Invalid card" when faulty card is inserted in other slot
+ text = concatenate(text, carrierTextForSimIOError, mSeparator);
+ }
+
+ }
+ return text;
+ }
+
+ /**
+ * This may be called internally after retrieving the correct value of {@code mNetworkSupported}
+ * (assumed false to start). In that case, the following happens:
+ * <ul>
+ * <li> If there was a registered callback, and the network is supported, it will register
+ * listeners.
+ * <li> If there was not a registered callback, it will try to remove unregistered listeners
+ * which is a no-op
+ * </ul>
+ *
+ * This call will always be processed in a background thread.
+ */
+ private void handleSetListening(CarrierTextCallback callback) {
+ TelephonyManager telephonyManager = getTelephonyManager();
+ if (callback != null) {
+ mCarrierTextCallback = callback;
+ if (mNetworkSupported.get()) {
+ // Keyguard update monitor expects callbacks from main thread
+ mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
+ mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+ telephonyManager.listen(mPhoneStateListener,
+ LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ } else {
+ // Don't listen and clear out the text when the device isn't a phone.
+ mMainHandler.post(() -> callback.updateCarrierInfo(
+ new CarrierTextCallbackInfo("", null, false, null)
+ ));
+ }
+ } else {
+ mCarrierTextCallback = null;
+ mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
+ mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+ telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
+ }
+ }
+
+ /**
+ * Sets the listening status of this controller. If the callback is null, it is set to
+ * not listening.
+ *
+ * @param callback Callback to provide text updates
+ */
+ public void setListening(CarrierTextCallback callback) {
+ mBgHandler.post(() -> handleSetListening(callback));
+ }
+
+ protected List<SubscriptionInfo> getSubscriptionInfo() {
+ return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+ }
+
+ protected void updateCarrierText() {
+ boolean allSimsMissing = true;
+ boolean anySimReadyAndInService = false;
+ CharSequence displayText = null;
+ List<SubscriptionInfo> subs = getSubscriptionInfo();
+
+ final int numSubs = subs.size();
+ final int[] subsIds = new int[numSubs];
+ // This array will contain in position i, the index of subscription in slot ID i.
+ // -1 if no subscription in that slot
+ final int[] subOrderBySlot = new int[mSimSlotsNumber];
+ for (int i = 0; i < mSimSlotsNumber; i++) {
+ subOrderBySlot[i] = -1;
+ }
+ final CharSequence[] carrierNames = new CharSequence[numSubs];
+ if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
+
+ for (int i = 0; i < numSubs; i++) {
+ int subId = subs.get(i).getSubscriptionId();
+ carrierNames[i] = "";
+ subsIds[i] = subId;
+ subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
+ int simState = mKeyguardUpdateMonitor.getSimState(subId);
+ CharSequence carrierName = subs.get(i).getCarrierName();
+ CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
+ if (DEBUG) {
+ Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
+ }
+ if (carrierTextForSimState != null) {
+ allSimsMissing = false;
+ carrierNames[i] = carrierTextForSimState;
+ }
+ if (simState == TelephonyManager.SIM_STATE_READY) {
+ ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
+ if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
+ // hack for WFC (IWLAN) not turning off immediately once
+ // Wi-Fi is disassociated or disabled
+ if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+ || (mWifiManager != null && mWifiManager.isWifiEnabled()
+ && mWifiManager.getConnectionInfo() != null
+ && mWifiManager.getConnectionInfo().getBSSID() != null)) {
+ if (DEBUG) {
+ Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
+ }
+ anySimReadyAndInService = true;
+ }
+ }
+ }
+ }
+ // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
+ // This condition will also be true always when numSubs == 0
+ if (allSimsMissing && !anySimReadyAndInService) {
+ if (numSubs != 0) {
+ // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
+ // This depends on mPlmn containing the text "Emergency calls only" when the radio
+ // has some connectivity. Otherwise, it should be null or empty and just show
+ // "No SIM card"
+ // Grab the first subscripton, because they all should contain the emergency text,
+ // described above.
+ displayText = makeCarrierStringOnEmergencyCapable(
+ getMissingSimMessage(), subs.get(0).getCarrierName());
+ } else {
+ // We don't have a SubscriptionInfo to get the emergency calls only from.
+ // Grab it from the old sticky broadcast if possible instead. We can use it
+ // here because no subscriptions are active, so we don't have
+ // to worry about MSIM clashing.
+ CharSequence text =
+ getContext().getText(com.android.internal.R.string.emergency_calls_only);
+ Intent i = getContext().registerReceiver(null,
+ new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
+ if (i != null) {
+ String spn = "";
+ String plmn = "";
+ if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
+ spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
+ }
+ if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
+ plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
+ }
+ if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
+ if (Objects.equals(plmn, spn)) {
+ text = plmn;
+ } else {
+ text = concatenate(plmn, spn, mSeparator);
+ }
+ }
+ displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
+ }
+ }
+
+ if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
+
+ displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
+ allSimsMissing);
+
+ boolean airplaneMode = false;
+ // APM (airplane mode) != no carrier state. There are carrier services
+ // (e.g. WFC = Wi-Fi calling) which may operate in APM.
+ if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
+ displayText = getAirplaneModeMessage();
+ airplaneMode = true;
+ }
+
+ final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
+ displayText,
+ carrierNames,
+ !allSimsMissing,
+ subsIds,
+ airplaneMode);
+ postToCallback(info);
+ }
+
+ @VisibleForTesting
+ protected void postToCallback(CarrierTextCallbackInfo info) {
+ final CarrierTextCallback callback = mCarrierTextCallback;
+ if (callback != null) {
+ mMainHandler.post(() -> callback.updateCarrierInfo(info));
+ }
+ }
+
+ private Context getContext() {
+ return mContext;
+ }
+
+ private String getMissingSimMessage() {
+ return mShowMissingSim && mTelephonyCapable
+ ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
+ }
+
+ private String getAirplaneModeMessage() {
+ return mShowAirplaneMode
+ ? getContext().getString(R.string.airplane_mode) : "";
+ }
+
+ /**
+ * Top-level function for creating carrier text. Makes text based on simState, PLMN
+ * and SPN as well as device capabilities, such as being emergency call capable.
+ *
+ * @return Carrier text if not in missing state, null otherwise.
+ */
+ private CharSequence getCarrierTextForSimState(int simState, CharSequence text) {
+ CharSequence carrierText = null;
+ CarrierTextManager.StatusMode status = getStatusForIccState(simState);
+ switch (status) {
+ case Normal:
+ carrierText = text;
+ break;
+
+ case SimNotReady:
+ // Null is reserved for denoting missing, in this case we have nothing to display.
+ carrierText = ""; // nothing to display yet.
+ break;
+
+ case NetworkLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ mContext.getText(R.string.keyguard_network_locked_message), text);
+ break;
+
+ case SimMissing:
+ carrierText = null;
+ break;
+
+ case SimPermDisabled:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(
+ R.string.keyguard_permanent_disabled_sim_message_short),
+ text);
+ break;
+
+ case SimMissingLocked:
+ carrierText = null;
+ break;
+
+ case SimLocked:
+ carrierText = makeCarrierStringOnLocked(
+ getContext().getText(R.string.keyguard_sim_locked_message),
+ text);
+ break;
+
+ case SimPukLocked:
+ carrierText = makeCarrierStringOnLocked(
+ getContext().getText(R.string.keyguard_sim_puk_locked_message),
+ text);
+ break;
+ case SimIoError:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_sim_error_message_short),
+ text);
+ break;
+ case SimUnknown:
+ carrierText = null;
+ break;
+ }
+
+ return carrierText;
+ }
+
+ /*
+ * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
+ */
+ private CharSequence makeCarrierStringOnEmergencyCapable(
+ CharSequence simMessage, CharSequence emergencyCallMessage) {
+ if (mIsEmergencyCallCapable) {
+ return concatenate(simMessage, emergencyCallMessage, mSeparator);
+ }
+ return simMessage;
+ }
+
+ /*
+ * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
+ * DSDS
+ */
+ private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
+ CharSequence carrierName) {
+ final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
+ final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
+ if (simMessageValid && carrierNameValid) {
+ return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
+ carrierName, simMessage);
+ } else if (simMessageValid) {
+ return simMessage;
+ } else if (carrierNameValid) {
+ return carrierName;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Determine the current status of the lock screen given the SIM state and other stuff.
+ */
+ private CarrierTextManager.StatusMode getStatusForIccState(int simState) {
+ final boolean missingAndNotProvisioned =
+ !mKeyguardUpdateMonitor.isDeviceProvisioned()
+ && (simState == TelephonyManager.SIM_STATE_ABSENT
+ || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
+
+ // Assume we're NETWORK_LOCKED if not provisioned
+ simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState;
+ switch (simState) {
+ case TelephonyManager.SIM_STATE_ABSENT:
+ return CarrierTextManager.StatusMode.SimMissing;
+ case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
+ return CarrierTextManager.StatusMode.SimMissingLocked;
+ case TelephonyManager.SIM_STATE_NOT_READY:
+ return CarrierTextManager.StatusMode.SimNotReady;
+ case TelephonyManager.SIM_STATE_PIN_REQUIRED:
+ return CarrierTextManager.StatusMode.SimLocked;
+ case TelephonyManager.SIM_STATE_PUK_REQUIRED:
+ return CarrierTextManager.StatusMode.SimPukLocked;
+ case TelephonyManager.SIM_STATE_READY:
+ return CarrierTextManager.StatusMode.Normal;
+ case TelephonyManager.SIM_STATE_PERM_DISABLED:
+ return CarrierTextManager.StatusMode.SimPermDisabled;
+ case TelephonyManager.SIM_STATE_UNKNOWN:
+ return CarrierTextManager.StatusMode.SimUnknown;
+ case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
+ return CarrierTextManager.StatusMode.SimIoError;
+ }
+ return CarrierTextManager.StatusMode.SimUnknown;
+ }
+
+ private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
+ CharSequence separator) {
+ final boolean plmnValid = !TextUtils.isEmpty(plmn);
+ final boolean spnValid = !TextUtils.isEmpty(spn);
+ if (plmnValid && spnValid) {
+ return new StringBuilder().append(plmn).append(separator).append(spn).toString();
+ } else if (plmnValid) {
+ return plmn;
+ } else if (spnValid) {
+ return spn;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
+ * separator added so there are no extra separators that are not needed.
+ */
+ private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
+ int length = sequences.length;
+ if (length == 0) return "";
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (!TextUtils.isEmpty(sequences[i])) {
+ if (!TextUtils.isEmpty(sb)) {
+ sb.append(separator);
+ }
+ sb.append(sequences[i]);
+ }
+ }
+ return sb.toString();
+ }
+
+ private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
+ if (!TextUtils.isEmpty(string)) {
+ list.add(string);
+ }
+ return list;
+ }
+
+ private CharSequence getCarrierHelpTextForSimState(int simState,
+ String plmn, String spn) {
+ int carrierHelpTextId = 0;
+ CarrierTextManager.StatusMode status = getStatusForIccState(simState);
+ switch (status) {
+ case NetworkLocked:
+ carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
+ break;
+
+ case SimMissing:
+ carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
+ break;
+
+ case SimPermDisabled:
+ carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
+ break;
+
+ case SimMissingLocked:
+ carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
+ break;
+
+ case Normal:
+ case SimLocked:
+ case SimPukLocked:
+ break;
+ }
+
+ return mContext.getText(carrierHelpTextId);
+ }
+
+ /** Injectable Buildeer for {@#link CarrierTextManager}. */
+ public static class Builder {
+ private final Context mContext;
+ private final String mSeparator;
+ private final WifiManager mWifiManager;
+ private final ConnectivityManager mConnectivityManager;
+ private final TelephonyManager mTelephonyManager;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final Handler mMainHandler;
+ private final Handler mBgHandler;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private boolean mShowAirplaneMode;
+ private boolean mShowMissingSim;
+
+ @Inject
+ public Builder(Context context, @Main Resources resources,
+ @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
+ TelephonyManager telephonyManager, WakefulnessLifecycle wakefulnessLifecycle,
+ @Main Handler mainHandler, @Background Handler bgHandler,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ mContext = context;
+ mSeparator = resources.getString(
+ com.android.internal.R.string.kg_text_message_separator);
+ mWifiManager = wifiManager;
+ mConnectivityManager = connectivityManager;
+ mTelephonyManager = telephonyManager;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mMainHandler = mainHandler;
+ mBgHandler = bgHandler;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ }
+
+ /** */
+ public Builder setShowAirplaneMode(boolean showAirplaneMode) {
+ mShowAirplaneMode = showAirplaneMode;
+ return this;
+ }
+
+ /** */
+ public Builder setShowMissingSim(boolean showMissingSim) {
+ mShowMissingSim = showMissingSim;
+ return this;
+ }
+
+ /** Create a CarrierTextManager. */
+ public CarrierTextManager build() {
+ return new CarrierTextManager(
+ mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager,
+ mConnectivityManager, mTelephonyManager, mWakefulnessLifecycle, mMainHandler,
+ mBgHandler, mKeyguardUpdateMonitor);
+ }
+ }
+ /**
+ * Data structure for passing information to CarrierTextController subscribers
+ */
+ public static final class CarrierTextCallbackInfo {
+ public final CharSequence carrierText;
+ public final CharSequence[] listOfCarriers;
+ public final boolean anySimReady;
+ public final int[] subscriptionIds;
+ public boolean airplaneMode;
+
+ @VisibleForTesting
+ public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+ boolean anySimReady, int[] subscriptionIds) {
+ this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
+ }
+
+ @VisibleForTesting
+ public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+ boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
+ this.carrierText = carrierText;
+ this.listOfCarriers = listOfCarriers;
+ this.anySimReady = anySimReady;
+ this.subscriptionIds = subscriptionIds;
+ this.airplaneMode = airplaneMode;
+ }
+ }
+
+ /**
+ * Callback to communicate to Views
+ */
+ public interface CarrierTextCallback {
+ /**
+ * Provides updated carrier information.
+ */
+ default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
+
+ /**
+ * Notifies the View that the device is going to sleep
+ */
+ default void startedGoingToSleep() {};
+
+ /**
+ * Notifies the View that the device finished waking up
+ */
+ default void finishedWakingUp() {};
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 707ee29..c4b02f6 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -16,34 +16,16 @@
package com.android.keyguard;
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Slog;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Button;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
-import com.android.systemui.util.EmergencyDialerConstants;
/**
* This class implements a smart emergency button that updates itself based
@@ -53,34 +35,14 @@
*/
public class EmergencyButton extends Button {
- private static final String LOG_TAG = "EmergencyButton";
private final EmergencyAffordanceManager mEmergencyAffordanceManager;
private int mDownX;
private int mDownY;
- KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
-
- @Override
- public void onSimStateChanged(int subId, int slotId, int simState) {
- updateEmergencyCallButton();
- }
-
- @Override
- public void onPhoneStateChanged(int phoneState) {
- updateEmergencyCallButton();
- }
- };
private boolean mLongPressWasDragged;
- public interface EmergencyButtonCallback {
- public void onEmergencyButtonClickedWhenInCall();
- }
-
private LockPatternUtils mLockPatternUtils;
- private PowerManager mPowerManager;
- private EmergencyButtonCallback mEmergencyButtonCallback;
- private final boolean mIsVoiceCapable;
private final boolean mEnableEmergencyCallWhileSimLocked;
public EmergencyButton(Context context) {
@@ -89,34 +51,15 @@
public EmergencyButton(Context context, AttributeSet attrs) {
super(context, attrs);
- mIsVoiceCapable = getTelephonyManager().isVoiceCapable();
mEnableEmergencyCallWhileSimLocked = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
}
- private TelephonyManager getTelephonyManager() {
- return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mLockPatternUtils = new LockPatternUtils(mContext);
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- setOnClickListener(v -> takeEmergencyCallAction());
if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
setOnLongClickListener(v -> {
if (!mLongPressWasDragged
@@ -127,7 +70,6 @@
return false;
});
}
- whitelistIpcs(this::updateEmergencyCallButton);
}
@Override
@@ -165,65 +107,13 @@
return super.performLongClick();
}
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- updateEmergencyCallButton();
- }
-
- /**
- * Shows the emergency dialer or returns the user to the existing call.
- */
- public void takeEmergencyCallAction() {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL);
- if (mPowerManager != null) {
- mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
- }
- try {
- ActivityTaskManager.getService().stopSystemLockTaskMode();
- } catch (RemoteException e) {
- Slog.w(LOG_TAG, "Failed to stop app pinning");
- }
- if (isInCall()) {
- resumeCall();
- if (mEmergencyButtonCallback != null) {
- mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
- }
- } else {
- KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
- if (updateMonitor != null) {
- updateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
- } else {
- Log.w(LOG_TAG, "KeyguardUpdateMonitor was null, launching intent anyway.");
- }
- TelecomManager telecomManager = getTelecommManager();
- if (telecomManager == null) {
- Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
- return;
- }
- Intent emergencyDialIntent =
- telecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
- | Intent.FLAG_ACTIVITY_CLEAR_TOP)
- .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
- EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
-
- getContext().startActivityAsUser(emergencyDialIntent,
- ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
- new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
- }
- }
-
- private void updateEmergencyCallButton() {
+ void updateEmergencyCallButton(boolean isInCall, boolean isVoiceCapable, boolean simLocked) {
boolean visible = false;
- if (mIsVoiceCapable) {
+ if (isVoiceCapable) {
// Emergency calling requires voice capability.
- if (isInCall()) {
+ if (isInCall) {
visible = true; // always show "return to call" if phone is off-hook
} else {
- final boolean simLocked = Dependency.get(KeyguardUpdateMonitor.class)
- .isSimPinVoiceSecure();
if (simLocked) {
// Some countries can't handle emergency calls while SIM is locked.
visible = mEnableEmergencyCallWhileSimLocked;
@@ -237,7 +127,7 @@
setVisibility(View.VISIBLE);
int textId;
- if (isInCall()) {
+ if (isInCall) {
textId = com.android.internal.R.string.lockscreen_return_to_call;
} else {
textId = com.android.internal.R.string.lockscreen_emergency_call;
@@ -247,26 +137,4 @@
setVisibility(View.GONE);
}
}
-
- public void setCallback(EmergencyButtonCallback callback) {
- mEmergencyButtonCallback = callback;
- }
-
- /**
- * Resumes a call in progress.
- */
- private void resumeCall() {
- getTelecommManager().showInCallScreen(false);
- }
-
- /**
- * @return {@code true} if there is a call currently in progress.
- */
- private boolean isInCall() {
- return getTelecommManager().isInCall();
- }
-
- private TelecomManager getTelecommManager() {
- return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
new file mode 100644
index 0000000..4275189
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static com.android.systemui.DejankUtils.whitelistIpcs;
+
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.util.EmergencyDialerConstants;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/** View Controller for {@link com.android.keyguard.EmergencyButton}. */
+@KeyguardBouncerScope
+public class EmergencyButtonController extends ViewController<EmergencyButton> {
+ static final String LOG_TAG = "EmergencyButton";
+ private final ConfigurationController mConfigurationController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final TelephonyManager mTelephonyManager;
+ private final PowerManager mPowerManager;
+ private final ActivityTaskManager mActivityTaskManager;
+ private final TelecomManager mTelecomManager;
+ private final MetricsLogger mMetricsLogger;
+
+ private EmergencyButtonCallback mEmergencyButtonCallback;
+
+ private final KeyguardUpdateMonitorCallback mInfoCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onSimStateChanged(int subId, int slotId, int simState) {
+ updateEmergencyCallButton();
+ }
+
+ @Override
+ public void onPhoneStateChanged(int phoneState) {
+ updateEmergencyCallButton();
+ }
+ };
+
+ private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ updateEmergencyCallButton();
+ }
+ };
+
+ private EmergencyButtonController(@Nullable EmergencyButton view,
+ ConfigurationController configurationController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
+ PowerManager powerManager, ActivityTaskManager activityTaskManager,
+ @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+ super(view);
+ mConfigurationController = configurationController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mTelephonyManager = telephonyManager;
+ mPowerManager = powerManager;
+ mActivityTaskManager = activityTaskManager;
+ mTelecomManager = telecomManager;
+ mMetricsLogger = metricsLogger;
+ }
+
+ @Override
+ protected void onInit() {
+ whitelistIpcs(this::updateEmergencyCallButton);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+ mConfigurationController.addCallback(mConfigurationListener);
+ mView.setOnClickListener(v -> takeEmergencyCallAction());
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+ mConfigurationController.removeCallback(mConfigurationListener);
+ }
+
+ private void updateEmergencyCallButton() {
+ if (mView != null) {
+ mView.updateEmergencyCallButton(
+ mTelecomManager != null && mTelecomManager.isInCall(),
+ mTelephonyManager.isVoiceCapable(),
+ mKeyguardUpdateMonitor.isSimPinVoiceSecure());
+ }
+ }
+
+ public void setEmergencyButtonCallback(EmergencyButtonCallback callback) {
+ mEmergencyButtonCallback = callback;
+ }
+ /**
+ * Shows the emergency dialer or returns the user to the existing call.
+ */
+ public void takeEmergencyCallAction() {
+ mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL);
+ if (mPowerManager != null) {
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+ }
+ mActivityTaskManager.stopSystemLockTaskMode();
+ if (mTelecomManager != null && mTelecomManager.isInCall()) {
+ mTelecomManager.showInCallScreen(false);
+ if (mEmergencyButtonCallback != null) {
+ mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
+ }
+ } else {
+ mKeyguardUpdateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
+ if (mTelecomManager == null) {
+ Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+ return;
+ }
+ Intent emergencyDialIntent =
+ mTelecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
+ EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
+
+ getContext().startActivityAsUser(emergencyDialIntent,
+ ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
+ new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
+ }
+ }
+
+ /** */
+ public interface EmergencyButtonCallback {
+ /** */
+ void onEmergencyButtonClickedWhenInCall();
+ }
+
+ /** Injectable Factory for creating {@link EmergencyButtonController}. */
+ public static class Factory {
+ private final ConfigurationController mConfigurationController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final TelephonyManager mTelephonyManager;
+ private final PowerManager mPowerManager;
+ private final ActivityTaskManager mActivityTaskManager;
+ @Nullable
+ private final TelecomManager mTelecomManager;
+ private final MetricsLogger mMetricsLogger;
+
+ @Inject
+ public Factory(ConfigurationController configurationController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
+ PowerManager powerManager, ActivityTaskManager activityTaskManager,
+ @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+
+ mConfigurationController = configurationController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mTelephonyManager = telephonyManager;
+ mPowerManager = powerManager;
+ mActivityTaskManager = activityTaskManager;
+ mTelecomManager = telecomManager;
+ mMetricsLogger = metricsLogger;
+ }
+
+ /** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
+ public EmergencyButtonController create(EmergencyButton view) {
+ return new EmergencyButtonController(view, mConfigurationController,
+ mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
+ mTelecomManager, mMetricsLogger);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 5760565..7a05a17 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -31,7 +31,7 @@
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
-import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
@@ -41,6 +41,7 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final LockPatternUtils mLockPatternUtils;
private final LatencyTracker mLatencyTracker;
+ private final EmergencyButtonController mEmergencyButtonController;
private CountDownTimer mCountdownTimer;
protected KeyguardMessageAreaController mMessageAreaController;
private boolean mDismissing;
@@ -70,11 +71,12 @@
LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- LatencyTracker latencyTracker) {
- super(view, securityMode, keyguardSecurityCallback);
+ LatencyTracker latencyTracker, EmergencyButtonController emergencyButtonController) {
+ super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
+ mEmergencyButtonController = emergencyButtonController;
KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
mMessageAreaController = messageAreaControllerFactory.create(kma);
}
@@ -83,6 +85,7 @@
@Override
public void onInit() {
+ super.onInit();
mMessageAreaController.init();
}
@@ -91,10 +94,7 @@
super.onViewAttached();
mView.setKeyDownListener(mKeyDownListener);
mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
- EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
- if (button != null) {
- button.setCallback(mEmergencyButtonCallback);
- }
+ mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 276036c..76a7473 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -36,7 +36,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.navigationbar.NavigationBarController;
@@ -46,12 +45,15 @@
import javax.inject.Inject;
+import dagger.Lazy;
+
public class KeyguardDisplayManager {
protected static final String TAG = "KeyguardDisplayManager";
private static boolean DEBUG = KeyguardConstants.DEBUG;
private MediaRouter mMediaRouter = null;
private final DisplayManager mDisplayService;
+ private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
private final Context mContext;
@@ -85,9 +87,11 @@
@Inject
public KeyguardDisplayManager(Context context,
+ Lazy<NavigationBarController> navigationBarControllerLazy,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
@UiBackground Executor uiBgExecutor) {
mContext = context;
+ mNavigationBarControllerLazy = navigationBarControllerLazy;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
mDisplayService = mContext.getSystemService(DisplayManager.class);
@@ -240,7 +244,7 @@
// Leave this task to {@link StatusBarKeyguardViewManager}
if (displayId == DEFAULT_DISPLAY) return;
- NavigationBarView navBarView = Dependency.get(NavigationBarController.class)
+ NavigationBarView navBarView = mNavigationBarControllerLazy.get()
.getNavigationBarView(displayId);
// We may not have nav bar on a display.
if (navBarView == null) return;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 1c691e7..a0c5958 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -28,6 +28,7 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -41,6 +42,7 @@
private final SecurityMode mSecurityMode;
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
private final EmergencyButton mEmergencyButton;
+ private final EmergencyButtonController mEmergencyButtonController;
private boolean mPaused;
@@ -68,11 +70,18 @@
};
protected KeyguardInputViewController(T view, SecurityMode securityMode,
- KeyguardSecurityCallback keyguardSecurityCallback) {
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ EmergencyButtonController emergencyButtonController) {
super(view);
mSecurityMode = securityMode;
mKeyguardSecurityCallback = keyguardSecurityCallback;
mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button);
+ mEmergencyButtonController = emergencyButtonController;
+ }
+
+ @Override
+ protected void onInit() {
+ mEmergencyButtonController.init();
}
@Override
@@ -154,9 +163,11 @@
private final InputMethodManager mInputMethodManager;
private final DelayableExecutor mMainExecutor;
private final Resources mResources;
- private LiftToActivateListener mLiftToActivateListener;
- private TelephonyManager mTelephonyManager;
+ private final LiftToActivateListener mLiftToActivateListener;
+ private final TelephonyManager mTelephonyManager;
+ private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
private final FalsingCollector mFalsingCollector;
+ private final boolean mIsNewLayoutEnabled;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -165,7 +176,10 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
@Main Resources resources, LiftToActivateListener liftToActivateListener,
- TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
+ TelephonyManager telephonyManager,
+ EmergencyButtonController.Factory emergencyButtonControllerFactory,
+ FalsingCollector falsingCollector,
+ FeatureFlags featureFlags) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -175,36 +189,49 @@
mResources = resources;
mLiftToActivateListener = liftToActivateListener;
mTelephonyManager = telephonyManager;
+ mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
mFalsingCollector = falsingCollector;
+ mIsNewLayoutEnabled = featureFlags.isKeyguardLayoutEnabled();
}
/** Create a new {@link KeyguardInputViewController}. */
public KeyguardInputViewController create(KeyguardInputView keyguardInputView,
SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) {
+ EmergencyButtonController emergencyButtonController =
+ mEmergencyButtonControllerFactory.create(
+ keyguardInputView.findViewById(R.id.emergency_call_button));
+
if (keyguardInputView instanceof KeyguardPatternView) {
return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
- keyguardSecurityCallback, mLatencyTracker, mMessageAreaControllerFactory);
+ keyguardSecurityCallback, mLatencyTracker,
+ emergencyButtonController,
+ mMessageAreaControllerFactory);
} else if (keyguardInputView instanceof KeyguardPasswordView) {
return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mInputMethodManager, mMainExecutor, mResources);
+ mInputMethodManager, emergencyButtonController, mMainExecutor, mResources);
} else if (keyguardInputView instanceof KeyguardPINView) {
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, mFalsingCollector);
+ mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
+ mIsNewLayoutEnabled);
} else if (keyguardInputView instanceof KeyguardSimPinView) {
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
+ mLiftToActivateListener, mTelephonyManager,
+ emergencyButtonController,
+ mFalsingCollector, mIsNewLayoutEnabled);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
+ mLiftToActivateListener, mTelephonyManager,
+ emergencyButtonController,
+ mFalsingCollector, mIsNewLayoutEnabled);
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 0f1c3c8..2e45545 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -111,10 +111,11 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker,
InputMethodManager inputMethodManager,
+ EmergencyButtonController emergencyButtonController,
@Main DelayableExecutor mainExecutor,
@Main Resources resources) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker);
+ messageAreaControllerFactory, latencyTracker, emergencyButtonController);
mKeyguardSecurityCallback = keyguardSecurityCallback;
mInputMethodManager = inputMethodManager;
mMainExecutor = mainExecutor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 2aaf748..55e348c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -31,7 +31,7 @@
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockPatternView.Cell;
import com.android.internal.widget.LockscreenCredential;
-import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -50,6 +50,7 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final LockPatternUtils mLockPatternUtils;
private final LatencyTracker mLatencyTracker;
+ private final EmergencyButtonController mEmergencyButtonController;
private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
private KeyguardMessageAreaController mMessageAreaController;
@@ -179,11 +180,13 @@
LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
LatencyTracker latencyTracker,
+ EmergencyButtonController emergencyButtonController,
KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
- super(view, securityMode, keyguardSecurityCallback);
+ super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
+ mEmergencyButtonController = emergencyButtonController;
mMessageAreaControllerFactory = messageAreaControllerFactory;
KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
mMessageAreaController = mMessageAreaControllerFactory.create(kma);
@@ -205,11 +208,7 @@
KeyguardUpdateMonitor.getCurrentUser()));
// vibrate mode will be the same for the life of this screen
mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
-
- EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
- if (button != null) {
- button.setCallback(mEmergencyButtonCallback);
- }
+ mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
View cancelBtn = mView.findViewById(R.id.cancel_button);
if (cancelBtn != null) {
@@ -224,10 +223,7 @@
protected void onViewDetached() {
super.onViewDetached();
mLockPatternView.setOnPatternListener(null);
- EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
- if (button != null) {
- button.setCallback(null);
- }
+ mEmergencyButtonController.setEmergencyButtonCallback(null);
View cancelBtn = mView.findViewById(R.id.cancel_button);
if (cancelBtn != null) {
cancelBtn.setOnClickListener(null);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 6a6b964..825ea25 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -167,6 +167,20 @@
}
/**
+ * By default, the new layout will be enabled. When false, revert to the old style.
+ */
+ public void setIsNewLayoutEnabled(boolean isEnabled) {
+ if (!isEnabled) {
+ for (int i = 0; i < mButtons.length; i++) {
+ mButtons[i].disableNewLayout();
+ }
+ mDeleteButton.disableNewLayout();
+ mOkButton.disableNewLayout();
+ reloadColors();
+ }
+ }
+
+ /**
* Reload colors from resources.
**/
public void reloadColors() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index f247948..1b5aa45 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -71,9 +71,10 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker,
LiftToActivateListener liftToActivateListener,
+ EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker);
+ messageAreaControllerFactory, latencyTracker, emergencyButtonController);
mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index c0aa2af..a456d42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -34,11 +34,13 @@
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
- FalsingCollector falsingCollector) {
+ EmergencyButtonController emergencyButtonController,
+ FalsingCollector falsingCollector, boolean isNewLayoutEnabled) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- falsingCollector);
+ emergencyButtonController, falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ view.setIsNewLayoutEnabled(isNewLayoutEnabled);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index c77c867..bacd29f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -23,7 +23,6 @@
import android.telephony.TelephonyManager;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -49,24 +48,27 @@
private final boolean mIsPukScreenAvailable;
private final LockPatternUtils mLockPatternUtils;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Inject
- KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils) {
+ KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mIsPukScreenAvailable = resources.getBoolean(
com.android.internal.R.bool.config_enable_puk_unlock_screen);
mLockPatternUtils = lockPatternUtils;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
}
public SecurityMode getSecurityMode(int userId) {
- KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
-
if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
- monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED))) {
+ mKeyguardUpdateMonitor.getNextSubIdForState(
+ TelephonyManager.SIM_STATE_PUK_REQUIRED))) {
return SecurityMode.SimPuk;
}
if (SubscriptionManager.isValidSubscriptionId(
- monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED))) {
+ mKeyguardUpdateMonitor.getNextSubIdForState(
+ TelephonyManager.SIM_STATE_PIN_REQUIRED))) {
return SecurityMode.SimPin;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index f1b504e..33d47fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -44,15 +44,18 @@
private final List<KeyguardInputViewController<KeyguardInputView>> mChildren =
new ArrayList<>();
private final LayoutInflater mLayoutInflater;
+ private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
private final Factory mKeyguardSecurityViewControllerFactory;
@Inject
protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
LayoutInflater layoutInflater,
- KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory) {
+ KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory,
+ EmergencyButtonController.Factory emergencyButtonControllerFactory) {
super(view);
mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
mLayoutInflater = layoutInflater;
+ mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
}
@Override
@@ -111,7 +114,8 @@
if (childController == null) {
childController = new NullKeyguardInputViewController(
- securityMode, keyguardSecurityCallback);
+ securityMode, keyguardSecurityCallback,
+ mEmergencyButtonControllerFactory.create(null));
}
return childController;
@@ -140,8 +144,9 @@
private static class NullKeyguardInputViewController
extends KeyguardInputViewController<KeyguardInputView> {
protected NullKeyguardInputViewController(SecurityMode securityMode,
- KeyguardSecurityCallback keyguardSecurityCallback) {
- super(null, securityMode, keyguardSecurityCallback);
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ EmergencyButtonController emergencyButtonController) {
+ super(null, securityMode, keyguardSecurityCallback, emergencyButtonController);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index b218141..4d2ebbb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -78,13 +78,15 @@
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
- TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
+ TelephonyManager telephonyManager, EmergencyButtonController emergencyButtonController,
+ FalsingCollector falsingCollector, boolean isNewLayoutEnabled) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- falsingCollector);
+ emergencyButtonController, falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
+ view.setIsNewLayoutEnabled(isNewLayoutEnabled);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 890a17c..0d9bb6f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -37,7 +37,6 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
@@ -85,13 +84,15 @@
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
- TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
+ TelephonyManager telephonyManager, EmergencyButtonController emergencyButtonController,
+ FalsingCollector falsingCollector, boolean isNewLayoutEnabled) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- falsingCollector);
+ emergencyButtonController, falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
+ view.setIsNewLayoutEnabled(isNewLayoutEnabled);
}
@Override
@@ -196,8 +197,7 @@
if (count < 2) {
msg = rez.getString(R.string.kg_puk_enter_puk_hint);
} else {
- SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
- .getSubscriptionInfoForSubId(mSubId);
+ SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
CharSequence displayName = info != null ? info.getDisplayName() : "";
msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
if (info != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index fb97a30..1fbf71d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -49,10 +49,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
import java.io.FileDescriptor;
@@ -317,6 +315,22 @@
R.dimen.widget_label_font_size);
mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize(
R.dimen.header_row_font_size);
+
+ for (int i = 0; i < mRow.getChildCount(); i++) {
+ View child = mRow.getChildAt(i);
+ if (child instanceof KeyguardSliceTextView) {
+ ((KeyguardSliceTextView) child).onDensityOrFontScaleChanged();
+ }
+ }
+ }
+
+ void onOverlayChanged() {
+ for (int i = 0; i < mRow.getChildCount(); i++) {
+ View child = mRow.getChildAt(i);
+ if (child instanceof KeyguardSliceTextView) {
+ ((KeyguardSliceTextView) child).onOverlayChanged();
+ }
+ }
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -479,8 +493,7 @@
* Representation of an item that appears under the clock on main keyguard message.
*/
@VisibleForTesting
- static class KeyguardSliceTextView extends TextView implements
- ConfigurationController.ConfigurationListener {
+ static class KeyguardSliceTextView extends TextView {
private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@StyleRes
@@ -492,24 +505,10 @@
setEllipsize(TruncateAt.END);
}
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- Dependency.get(ConfigurationController.class).addCallback(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- Dependency.get(ConfigurationController.class).removeCallback(this);
- }
-
- @Override
public void onDensityOrFontScaleChanged() {
updatePadding();
}
- @Override
public void onOverlayChanged() {
setTextAppearance(sStyleId);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 1b0a7fa..8038ce4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -83,6 +83,10 @@
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
}
+ @Override
+ public void onOverlayChanged() {
+ mView.onOverlayChanged();
+ }
};
Observer<Slice> mObserver = new Observer<Slice>() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index fea152a..5db4f9e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -34,7 +34,6 @@
import androidx.core.graphics.ColorUtils;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import java.io.FileDescriptor;
@@ -56,7 +55,6 @@
private final IActivityManager mIActivityManager;
private TextView mLogoutView;
- private boolean mCanShowLogout = true; // by default, try to show the logout button here
private KeyguardClockSwitch mClockView;
private TextView mOwnerInfo;
private boolean mCanShowOwnerInfo = true; // by default, try to show the owner information here
@@ -130,11 +128,6 @@
}
}
- void setCanShowLogout(boolean canShowLogout) {
- mCanShowLogout = canShowLogout;
- updateLogoutView();
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -159,10 +152,7 @@
mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
onSliceContentChanged();
- boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
- setEnableMarquee(shouldMarquee);
updateOwnerInfo();
- updateLogoutView();
updateDark();
}
@@ -209,11 +199,11 @@
return mOwnerInfo.getVisibility() == VISIBLE ? mOwnerInfo.getHeight() : 0;
}
- void updateLogoutView() {
+ void updateLogoutView(boolean shouldShowLogout) {
if (mLogoutView == null) {
return;
}
- mLogoutView.setVisibility(mCanShowLogout && shouldShowLogout() ? VISIBLE : GONE);
+ mLogoutView.setVisibility(shouldShowLogout ? VISIBLE : GONE);
// Logout button will stay in language of user 0 if we don't set that manually.
mLogoutView.setText(mContext.getResources().getString(
com.android.internal.R.string.global_action_logout));
@@ -313,11 +303,6 @@
}
}
- private boolean shouldShowLogout() {
- return Dependency.get(KeyguardUpdateMonitor.class).isLogoutEnabled()
- && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
- }
-
private void onLogoutClicked(View view) {
int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
try {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 6fb6760..bfe7f8c7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import android.os.UserHandle;
import android.util.Slog;
import android.view.View;
@@ -78,6 +79,8 @@
@Override
public void onInit() {
mKeyguardClockSwitchController.init();
+ mView.setEnableMarquee(mKeyguardUpdateMonitor.isDeviceInteractive());
+ mView.updateLogoutView(shouldShowLogout());
}
@Override
@@ -245,6 +248,11 @@
}
}
+ private boolean shouldShowLogout() {
+ return mKeyguardUpdateMonitor.isLogoutEnabled()
+ && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
+ }
+
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
@@ -271,12 +279,12 @@
mKeyguardSliceViewController.updateTopMargin(
mKeyguardClockSwitchController.getClockTextTopPadding());
mView.setCanShowOwnerInfo(false);
- mView.setCanShowLogout(false);
+ mView.updateLogoutView(false);
} else {
// reset margin
mKeyguardSliceViewController.updateTopMargin(0);
mView.setCanShowOwnerInfo(true);
- mView.setCanShowLogout(false);
+ mView.updateLogoutView(false);
}
updateAodIcons();
}
@@ -302,7 +310,7 @@
if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
refreshTime();
mView.updateOwnerInfo();
- mView.updateLogoutView();
+ mView.updateLogoutView(shouldShowLogout());
}
}
@@ -320,12 +328,12 @@
public void onUserSwitchComplete(int userId) {
mKeyguardClockSwitchController.refreshFormat();
mView.updateOwnerInfo();
- mView.updateLogoutView();
+ mView.updateLogoutView(shouldShowLogout());
}
@Override
public void onLogoutEnabledChanged() {
- mView.updateLogoutView();
+ mView.updateLogoutView(shouldShowLogout());
}
};
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 3728106..886c372 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -16,17 +16,23 @@
package com.android.keyguard;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.VectorDrawable;
import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
import android.view.MotionEvent;
import android.view.ViewGroup;
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
/**
* Similar to the {@link NumPadKey}, but displays an image.
*/
public class NumPadButton extends AlphaOptimizedImageButton {
- private final NumPadAnimator mAnimator;
+ private NumPadAnimator mAnimator;
public NumPadButton(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -36,25 +42,34 @@
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- mAnimator.updateMargin((ViewGroup.MarginLayoutParams) getLayoutParams());
+ public void setLayoutParams(ViewGroup.LayoutParams params) {
+ if (mAnimator != null) mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
+ super.setLayoutParams(params);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- // Set width/height to the same value to ensure a smooth circle for the bg
- setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
+ // Set width/height to the same value to ensure a smooth circle for the bg, but shrink
+ // the height to match the old pin bouncer
+ int width = getMeasuredWidth();
+ int height = mAnimator == null ? (int) (width * .75f) : width;
+
+ setMeasuredDimension(getMeasuredWidth(), height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- mAnimator.onLayout(b - t);
+ if (mAnimator != null) mAnimator.onLayout(b - t);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
- mAnimator.start();
+ if (mAnimator != null) mAnimator.start();
return super.onTouchEvent(event);
}
@@ -62,6 +77,23 @@
* Reload colors from resources.
**/
public void reloadColors() {
- mAnimator.reloadColors(getContext());
+ if (mAnimator != null) {
+ mAnimator.reloadColors(getContext());
+ } else {
+ // Needed for old style pin
+ int textColor = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary)
+ .getDefaultColor();
+ ((VectorDrawable) getDrawable()).setTintList(ColorStateList.valueOf(textColor));
+ }
+ }
+
+ /**
+ * By default, the new layout will be enabled. Invoking will revert to the old style
+ */
+ public void disableNewLayout() {
+ mAnimator = null;
+ ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+ setBackground(getContext().getResources().getDrawable(
+ R.drawable.ripple_drawable_pin, ctw.getTheme()));
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 756d610..01e1c63 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -22,6 +22,7 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -47,7 +48,7 @@
private int mTextViewResId;
private PasswordTextView mTextView;
- private final NumPadAnimator mAnimator;
+ private NumPadAnimator mAnimator;
private View.OnClickListener mListener = new View.OnClickListener() {
@Override
@@ -131,6 +132,17 @@
}
/**
+ * By default, the new layout will be enabled. Invoking will revert to the old style
+ */
+ public void disableNewLayout() {
+ findViewById(R.id.klondike_text).setVisibility(View.VISIBLE);
+ mAnimator = null;
+ ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+ setBackground(getContext().getResources().getDrawable(
+ R.drawable.ripple_drawable_pin, ctw.getTheme()));
+ }
+
+ /**
* Reload colors from resources.
**/
public void reloadColors() {
@@ -141,7 +153,7 @@
mDigitText.setTextColor(textColor);
mKlondikeText.setTextColor(klondikeColor);
- mAnimator.reloadColors(getContext());
+ if (mAnimator != null) mAnimator.reloadColors(getContext());
}
@Override
@@ -150,14 +162,14 @@
doHapticKeyClick();
}
- mAnimator.start();
+ if (mAnimator != null) mAnimator.start();
return super.onTouchEvent(event);
}
@Override
public void setLayoutParams(ViewGroup.LayoutParams params) {
- mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
+ if (mAnimator != null) mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
super.setLayoutParams(params);
}
@@ -167,8 +179,12 @@
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
- // Set width/height to the same value to ensure a smooth circle for the bg
- setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
+ // Set width/height to the same value to ensure a smooth circle for the bg, but shrink
+ // the height to match the old pin bouncer
+ int width = getMeasuredWidth();
+ int height = mAnimator == null ? (int) (width * .75f) : width;
+
+ setMeasuredDimension(getMeasuredWidth(), height);
}
@Override
@@ -187,7 +203,7 @@
left = centerX - mKlondikeText.getMeasuredWidth() / 2;
mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom);
- mAnimator.onLayout(b - t);
+ if (mAnimator != null) mAnimator.onLayout(b - t);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
new file mode 100644
index 0000000..c4be1ba535
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.clock;
+
+import java.util.List;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger Module for clock package. */
+@Module
+public abstract class ClockModule {
+
+ /** */
+ @Provides
+ public static List<ClockInfo> provideClockInfoList(ClockManager clockManager) {
+ return clockManager.getClockInfos();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
index 5ef35be..b6413cb 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
@@ -28,11 +28,12 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
import java.io.FileNotFoundException;
import java.util.List;
-import java.util.function.Supplier;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
/**
* Exposes custom clock face options and provides realistic preview images.
@@ -65,15 +66,12 @@
private static final String CONTENT_SCHEME = "content";
private static final String AUTHORITY = "com.android.keyguard.clock";
- private final Supplier<List<ClockInfo>> mClocksSupplier;
-
- public ClockOptionsProvider() {
- this(() -> Dependency.get(ClockManager.class).getClockInfos());
- }
+ @Inject
+ public Provider<List<ClockInfo>> mClockInfosProvider;
@VisibleForTesting
- ClockOptionsProvider(Supplier<List<ClockInfo>> clocksSupplier) {
- mClocksSupplier = clocksSupplier;
+ ClockOptionsProvider(Provider<List<ClockInfo>> clockInfosProvider) {
+ mClockInfosProvider = clockInfosProvider;
}
@Override
@@ -99,7 +97,7 @@
}
MatrixCursor cursor = new MatrixCursor(new String[] {
COLUMN_NAME, COLUMN_TITLE, COLUMN_ID, COLUMN_THUMBNAIL, COLUMN_PREVIEW});
- List<ClockInfo> clocks = mClocksSupplier.get();
+ List<ClockInfo> clocks = mClockInfosProvider.get();
for (int i = 0; i < clocks.size(); i++) {
ClockInfo clock = clocks.get(i);
cursor.newRow()
@@ -139,7 +137,7 @@
throw new FileNotFoundException("Invalid preview url, missing id");
}
ClockInfo clock = null;
- List<ClockInfo> clocks = mClocksSupplier.get();
+ List<ClockInfo> clocks = mClockInfosProvider.get();
for (int i = 0; i < clocks.size(); i++) {
if (id.equals(clocks.get(i).getId())) {
clock = clocks.get(i);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
new file mode 100644
index 0000000..3a0357d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.dagger;
+
+import com.android.systemui.statusbar.phone.UserAvatarView;
+import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardQsUserSwitch and its children.
+ */
+@Subcomponent(modules = {KeyguardUserSwitcherModule.class})
+@KeyguardUserSwitcherScope
+public interface KeyguardQsUserSwitchComponent {
+ /** Simple factory for {@link KeyguardUserSwitcherComponent}. */
+ @Subcomponent.Factory
+ interface Factory {
+ KeyguardQsUserSwitchComponent build(
+ @BindsInstance UserAvatarView userAvatarView);
+ }
+
+ /** Builds a {@link KeyguardQsUserSwitchController}. */
+ KeyguardQsUserSwitchController getKeyguardQsUserSwitchController();
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
new file mode 100644
index 0000000..49a617e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
@@ -0,0 +1,42 @@
+/*
+ * 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.keyguard.dagger;
+
+import com.android.keyguard.KeyguardStatusViewController;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardStatusView and its children.
+ *
+ * TODO: unify this with {@link KeyguardStatusViewComponent}
+ */
+@Subcomponent(modules = {KeyguardStatusBarViewModule.class})
+@KeyguardStatusBarViewScope
+public interface KeyguardStatusBarViewComponent {
+ /** Simple factory for {@link KeyguardStatusBarViewComponent}. */
+ @Subcomponent.Factory
+ interface Factory {
+ KeyguardStatusBarViewComponent build(@BindsInstance KeyguardStatusBarView view);
+ }
+
+ /** Builds a {@link KeyguardStatusViewController}. */
+ KeyguardStatusBarViewController getKeyguardStatusBarViewController();
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
new file mode 100644
index 0000000..a672523
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.dagger;
+
+import com.android.keyguard.CarrierText;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger module for {@link KeyguardStatusBarViewComponent}. */
+@Module
+public abstract class KeyguardStatusBarViewModule {
+ @Provides
+ @KeyguardStatusBarViewScope
+ static CarrierText getCarrierText(KeyguardStatusBarView view) {
+ return view.findViewById(R.id.keyguard_carrier_text);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
new file mode 100644
index 0000000..ba0642f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the StatusBarComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface KeyguardStatusBarViewScope {}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
index 1b6476c..d342377 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
@@ -25,6 +25,8 @@
/**
* Subcomponent for helping work with KeyguardStatusView and its children.
+ *
+ * TODO: unify this with {@link KeyguardStatusBarViewComponent}
*/
@Subcomponent(modules = {KeyguardStatusViewModule.class})
@KeyguardStatusViewScope
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 71ec33e..b6a232d 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,7 +16,10 @@
package com.android.systemui;
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -26,13 +29,16 @@
import android.util.Size;
import android.view.SurfaceHolder;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.glwallpaper.EglHelper;
-import com.android.systemui.glwallpaper.GLWallpaperRenderer;
import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import javax.inject.Inject;
@@ -45,8 +51,13 @@
// We delayed destroy render context that subsequent render requests have chance to cancel it.
// This is to avoid destroying then recreating render context in a very short time.
private static final int DELAY_FINISH_RENDERING = 1000;
+ private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS =
+ new RectF(0, 0, 1, 1);
private static final boolean DEBUG = false;
+ private ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
private HandlerThread mWorker;
+ // scaled down version
+ private Bitmap mMiniBitmap;
@Inject
public ImageWallpaper() {
@@ -70,6 +81,7 @@
super.onDestroy();
mWorker.quitSafely();
mWorker = null;
+ mMiniBitmap = null;
}
class GLEngine extends Engine {
@@ -80,7 +92,7 @@
@VisibleForTesting
static final int MIN_SURFACE_HEIGHT = 64;
- private GLWallpaperRenderer mRenderer;
+ private ImageWallpaperRenderer mRenderer;
private EglHelper mEglHelper;
private final Runnable mFinishRenderingTask = this::finishRendering;
private boolean mNeedRedraw;
@@ -101,6 +113,12 @@
setFixedSizeAllowed(true);
setOffsetNotificationsEnabled(false);
updateSurfaceSize();
+ mMiniBitmap = null;
+ if (mWorker == null || mWorker.getThreadHandler() == null) {
+ updateMiniBitmap();
+ } else {
+ mWorker.getThreadHandler().post(this::updateMiniBitmap);
+ }
}
EglHelper getEglHelperInstance() {
@@ -111,6 +129,20 @@
return new ImageWallpaperRenderer(getDisplayContext());
}
+ private void updateMiniBitmap() {
+ mRenderer.useBitmap(b -> {
+ int size = Math.min(b.getWidth(), b.getHeight());
+ float scale = 1.0f;
+ if (size > MIN_SURFACE_WIDTH) {
+ scale = (float) MIN_SURFACE_WIDTH / (float) size;
+ }
+ mMiniBitmap = Bitmap.createScaledBitmap(b, Math.round(scale * b.getWidth()),
+ Math.round(scale * b.getHeight()), false);
+ computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap);
+ mLocalColorsToAdd.clear();
+ });
+ }
+
private void updateSurfaceSize() {
SurfaceHolder holder = getSurfaceHolder();
Size frameSize = mRenderer.reportSurfaceSize();
@@ -126,6 +158,7 @@
@Override
public void onDestroy() {
+ mMiniBitmap = null;
mWorker.getThreadHandler().post(() -> {
mRenderer.finish();
mRenderer = null;
@@ -134,6 +167,61 @@
});
}
+
+
+ @Override
+ public boolean supportsLocalColorExtraction() {
+ return true;
+ }
+
+ @Override
+ public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+ mWorker.getThreadHandler().post(() -> {
+ Bitmap bitmap = mMiniBitmap;
+ if (bitmap == null) {
+ mLocalColorsToAdd.addAll(regions);
+ } else {
+ computeAndNotifyLocalColors(regions, bitmap);
+ }
+ });
+ }
+
+ private void computeAndNotifyLocalColors(@NonNull List<RectF> regions, Bitmap b) {
+ List<WallpaperColors> colors = getLocalWallpaperColors(regions, b);
+ try {
+ notifyLocalColorsChanged(regions, colors);
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
+ // No-OP
+ }
+
+ private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
+ Bitmap b) {
+ List<WallpaperColors> colors = new ArrayList<>(areas.size());
+ for (int i = 0; i < areas.size(); i++) {
+ RectF area = areas.get(i);
+ if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
+ colors.add(null);
+ continue;
+ }
+ Rect subImage = new Rect(
+ Math.round(area.left * b.getWidth()),
+ Math.round(area.top * b.getHeight()),
+ Math.round(area.right * b.getWidth()),
+ Math.round(area.bottom * b.getHeight()));
+ Bitmap colorImg = Bitmap.createBitmap(b,
+ subImage.left, subImage.top, subImage.width(), subImage.height());
+ WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
+ colors.add(color);
+ }
+ return colors;
+ }
+
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
if (mWorker == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 38a82f8..36937d6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -16,10 +16,19 @@
package com.android.systemui.controls.dagger
+import android.content.ContentResolver
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.SecureSettings
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
@@ -28,15 +37,43 @@
* Pseudo-component to inject into classes outside `com.android.systemui.controls`.
*
* If `featureEnabled` is false, all the optionals should be empty. The controllers will only be
- * instantiated if `featureEnabled` is true.
+ * instantiated if `featureEnabled` is true. Can also be queried for the availability of controls.
*/
@SysUISingleton
class ControlsComponent @Inject constructor(
@ControlsFeatureEnabled private val featureEnabled: Boolean,
+ private val context: Context,
private val lazyControlsController: Lazy<ControlsController>,
private val lazyControlsUiController: Lazy<ControlsUiController>,
- private val lazyControlsListingController: Lazy<ControlsListingController>
+ private val lazyControlsListingController: Lazy<ControlsListingController>,
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardStateController: KeyguardStateController,
+ private val userTracker: UserTracker,
+ private val secureSettings: SecureSettings
) {
+
+ private val contentResolver: ContentResolver
+ get() = context.contentResolver
+
+ private var canShowWhileLockedSetting = false
+
+ val showWhileLockedObserver = object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ updateShowWhileLocked()
+ }
+ }
+
+ init {
+ if (featureEnabled) {
+ secureSettings.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT),
+ false, /* notifyForDescendants */
+ showWhileLockedObserver
+ )
+ updateShowWhileLocked()
+ }
+ }
+
fun getControlsController(): Optional<ControlsController> {
return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
}
@@ -52,4 +89,37 @@
Optional.empty()
}
}
-}
\ No newline at end of file
+
+ /**
+ * @return true if controls are feature-enabled and have available services to serve controls
+ */
+ fun isEnabled() = featureEnabled && lazyControlsController.get().available
+
+ /**
+ * Returns one of 3 states:
+ * * AVAILABLE - Controls can be made visible
+ * * AVAILABLE_AFTER_UNLOCK - Controls can be made visible only after device unlock
+ * * UNAVAILABLE - Controls are not enabled
+ */
+ fun getVisibility(): Visibility {
+ if (!isEnabled()) return Visibility.UNAVAILABLE
+ if (lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
+ == STRONG_AUTH_REQUIRED_AFTER_BOOT) {
+ return Visibility.AVAILABLE_AFTER_UNLOCK
+ }
+ if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) {
+ return Visibility.AVAILABLE_AFTER_UNLOCK
+ }
+
+ return Visibility.AVAILABLE
+ }
+
+ private fun updateShowWhileLocked() {
+ canShowWhileLockedSetting = secureSettings.getInt(
+ Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0
+ }
+
+ enum class Visibility {
+ AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index ffb8446..91c2dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -16,6 +16,7 @@
package com.android.systemui.dagger;
+import com.android.keyguard.clock.ClockOptionsProvider;
import com.android.systemui.BootCompleteCacheImpl;
import com.android.systemui.Dependency;
import com.android.systemui.InitController;
@@ -146,4 +147,9 @@
* Member injection into the supplied argument.
*/
void inject(KeyguardSliceProvider keyguardSliceProvider);
+
+ /**
+ * Member injection into the supplied argument.
+ */
+ void inject(ClockOptionsProvider clockOptionsProvider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index b0067cd..b67db03 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -22,6 +22,7 @@
import androidx.annotation.Nullable;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.clock.ClockModule;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
@@ -90,6 +91,7 @@
@Module(includes = {
AppOpsModule.class,
AssistModule.class,
+ ClockModule.class,
ControlsModule.class,
DemoModeModule.class,
FalsingModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 8af45a5..d85b101 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -25,6 +25,8 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
import android.animation.Animator;
@@ -250,6 +252,7 @@
private final IWindowManager mIWindowManager;
private final Executor mBackgroundExecutor;
private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
+ private ControlsComponent mControlsComponent;
private Optional<ControlsController> mControlsControllerOptional;
private final RingerModeTracker mRingerModeTracker;
private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
@@ -338,6 +341,7 @@
mSysuiColorExtractor = colorExtractor;
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
+ mControlsComponent = controlsComponent;
mControlsUiControllerOptional = controlsComponent.getControlsUiController();
mIWindowManager = iWindowManager;
mBackgroundExecutor = backgroundExecutor;
@@ -387,7 +391,8 @@
if (mDialog.mWalletViewController != null) {
mDialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
}
- if (!mDialog.isShowingControls() && shouldShowControls()) {
+ if (!mDialog.isShowingControls()
+ && mControlsComponent.getVisibility() == AVAILABLE) {
mDialog.showControls(mControlsUiControllerOptional.get());
}
if (unlocked) {
@@ -397,14 +402,15 @@
}
});
- if (controlsComponent.getControlsListingController().isPresent()) {
- controlsComponent.getControlsListingController().get()
+ if (mControlsComponent.getControlsListingController().isPresent()) {
+ mControlsComponent.getControlsListingController().get()
.addCallback(list -> {
mControlsServiceInfos = list;
// This callback may occur after the dialog has been shown. If so, add
// controls into the already visible space or show the lock msg if needed.
if (mDialog != null) {
- if (!mDialog.isShowingControls() && shouldShowControls()) {
+ if (!mDialog.isShowingControls()
+ && mControlsComponent.getVisibility() == AVAILABLE) {
mDialog.showControls(mControlsUiControllerOptional.get());
} else if (shouldShowLockMessage(mDialog)) {
mDialog.showLockMessage();
@@ -704,7 +710,7 @@
mDepthController.setShowingHomeControls(true);
ControlsUiController uiController = null;
- if (mControlsUiControllerOptional.isPresent() && shouldShowControls()) {
+ if (mControlsComponent.getVisibility() == AVAILABLE) {
uiController = mControlsUiControllerOptional.get();
}
ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
@@ -2687,26 +2693,24 @@
return isPanelDebugModeEnabled(context);
}
- private boolean shouldShowControls() {
- boolean showOnLockScreen = mShowLockScreenCardsAndControls && mLockPatternUtils
- .getStrongAuthForUser(getCurrentUser().id) != STRONG_AUTH_REQUIRED_AFTER_BOOT;
- return controlsAvailable()
- && (mKeyguardStateController.isUnlocked() || showOnLockScreen);
- }
-
private boolean controlsAvailable() {
return mDeviceProvisioned
- && mControlsUiControllerOptional.isPresent()
- && mControlsUiControllerOptional.get().getAvailable()
+ && mControlsComponent.isEnabled()
&& !mControlsServiceInfos.isEmpty();
}
private boolean shouldShowLockMessage(ActionsDialog dialog) {
+ return mControlsComponent.getVisibility() == AVAILABLE_AFTER_UNLOCK
+ || isWalletAvailableAfterUnlock(dialog);
+ }
+
+ // Temporary while we move items out of the power menu
+ private boolean isWalletAvailableAfterUnlock(ActionsDialog dialog) {
boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
== STRONG_AUTH_REQUIRED_AFTER_BOOT;
return !mKeyguardStateController.isUnlocked()
&& (!mShowLockScreenCardsAndControls || isLockedAfterBoot)
- && (controlsAvailable() || dialog.isWalletViewAvailable());
+ && dialog.isWalletViewAvailable();
}
private void onPowerMenuLockScreenSettingsChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 1a0356c..01a353c 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -58,6 +58,14 @@
mWallpaper = new ImageGLWallpaper(mProgram);
}
+ /**
+ * @hide
+ * @return
+ */
+ public void useBitmap(Consumer<Bitmap> c) {
+ mTexture.use(c);
+ }
+
@Override
public boolean isWcgContent() {
return mTexture.isWcgContent();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 9e5b225..a747edd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -29,6 +29,8 @@
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -62,7 +64,11 @@
/**
* Dagger Module providing {@link StatusBar}.
*/
-@Module(subcomponents = {KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.class},
+@Module(subcomponents = {
+ KeyguardQsUserSwitchComponent.class,
+ KeyguardStatusBarViewComponent.class,
+ KeyguardStatusViewComponent.class,
+ KeyguardUserSwitcherComponent.class},
includes = {FalsingModule.class})
public class KeyguardModule {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
new file mode 100644
index 0000000..e7458a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.people;
+
+import android.app.people.ConversationChannel;
+import android.app.people.IPeopleManager;
+import android.app.people.PeopleSpaceTile;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import com.android.systemui.shared.system.PeopleProviderUtils;
+
+/** API that returns a People Tile preview. */
+public class PeopleProvider extends ContentProvider {
+
+ LauncherApps mLauncherApps;
+ IPeopleManager mPeopleManager;
+
+ private static final String TAG = "PeopleProvider";
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+ private static final String EMPTY_STRING = "";
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ if (!doesCallerHavePermission()) {
+ String callingPackage = getCallingPackage();
+ Log.w(TAG, "API not accessible to calling package: " + callingPackage);
+ throw new SecurityException("API not accessible to calling package: " + callingPackage);
+ }
+ if (!PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD.equals(method)) {
+ Log.w(TAG, "Invalid method");
+ throw new IllegalArgumentException("Invalid method");
+ }
+
+ // If services are not set as mocks in tests, fetch them now.
+ mPeopleManager = mPeopleManager != null ? mPeopleManager
+ : IPeopleManager.Stub.asInterface(
+ ServiceManager.getService(Context.PEOPLE_SERVICE));
+ mLauncherApps = mLauncherApps != null ? mLauncherApps
+ : getContext().getSystemService(LauncherApps.class);
+
+ if (mPeopleManager == null || mLauncherApps == null) {
+ Log.w(TAG, "Null system managers");
+ return null;
+ }
+
+ if (extras == null) {
+ Log.w(TAG, "Extras can't be null");
+ throw new IllegalArgumentException("Extras can't be null");
+ }
+
+ String shortcutId = extras.getString(
+ PeopleProviderUtils.EXTRAS_KEY_SHORTCUT_ID, EMPTY_STRING);
+ String packageName = extras.getString(
+ PeopleProviderUtils.EXTRAS_KEY_PACKAGE_NAME, EMPTY_STRING);
+ UserHandle userHandle = extras.getParcelable(
+ PeopleProviderUtils.EXTRAS_KEY_USER_HANDLE);
+ if (shortcutId.isEmpty()) {
+ Log.w(TAG, "Invalid shortcut id");
+ throw new IllegalArgumentException("Invalid shortcut id");
+ }
+
+ if (packageName.isEmpty()) {
+ Log.w(TAG, "Invalid package name");
+ throw new IllegalArgumentException("Invalid package name");
+ }
+ if (userHandle == null) {
+ Log.w(TAG, "Null user handle");
+ throw new IllegalArgumentException("Null user handle");
+ }
+
+ ConversationChannel channel;
+ try {
+ channel = mPeopleManager.getConversation(
+ packageName, userHandle.getIdentifier(), shortcutId);
+ } catch (Exception e) {
+ Log.w(TAG, "Exception getting tiles: " + e);
+ return null;
+ }
+ PeopleSpaceTile tile = PeopleSpaceUtils.getTile(channel, mLauncherApps);
+
+ if (tile == null) {
+ if (DEBUG) Log.i(TAG, "No tile was returned");
+ return null;
+ }
+
+ if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
+ RemoteViews view = PeopleSpaceUtils.createRemoteViews(getContext(), tile, 0);
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(PeopleProviderUtils.RESPONSE_KEY_REMOTE_VIEWS, view);
+ return bundle;
+ }
+
+ private boolean doesCallerHavePermission() {
+ return getContext().checkPermission(
+ PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ throw new IllegalArgumentException("Invalid method");
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new IllegalArgumentException("Invalid method");
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues initialValues) {
+ throw new IllegalArgumentException("Invalid method");
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new IllegalArgumentException("Invalid method");
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new IllegalArgumentException("Invalid method");
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index c67aef6..2f9b17a 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -19,6 +19,8 @@
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.getUserHandle;
+
import android.app.Activity;
import android.app.INotificationManager;
import android.app.people.IPeopleManager;
@@ -39,6 +41,7 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
@@ -49,6 +52,7 @@
public class PeopleSpaceActivity extends Activity {
private static final String TAG = "PeopleSpaceActivity";
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
private ViewGroup mPeopleSpaceLayout;
private IPeopleManager mPeopleManager;
@@ -134,9 +138,11 @@
/** Stores the user selected configuration for {@code mAppWidgetId}. */
private void storeWidgetConfiguration(PeopleSpaceTile tile) {
if (PeopleSpaceUtils.DEBUG) {
- Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
- + tile.getId() + " for widget ID: "
- + mAppWidgetId);
+ if (DEBUG) {
+ Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
+ + tile.getId() + " for widget ID: "
+ + mAppWidgetId);
+ }
}
PeopleSpaceUtils.setStorageForTile(mContext, tile, mAppWidgetId);
@@ -144,12 +150,22 @@
// TODO: Populate new widget with existing conversation notification, if there is any.
PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager,
mPeopleManager);
+ if (mLauncherApps != null) {
+ try {
+ if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId());
+ mLauncherApps.cacheShortcuts(tile.getPackageName(),
+ Collections.singletonList(tile.getId()),
+ getUserHandle(tile), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
+ } catch (Exception e) {
+ Log.w(TAG, "Exception caching shortcut:" + e);
+ }
+ }
finishActivity();
}
/** Finish activity with a successful widget configuration result. */
private void finishActivity() {
- if (PeopleSpaceUtils.DEBUG) Log.d(TAG, "Widget added!");
+ if (DEBUG) Log.d(TAG, "Widget added!");
mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_ADDED);
setActivityResult(RESULT_OK);
finish();
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index dd05484..cbcbd3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -49,6 +49,7 @@
import android.provider.Settings;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
@@ -286,7 +287,7 @@
SharedPreferences.Editor widgetEditor = widgetSp.edit();
widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, tile.getPackageName());
widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, tile.getId());
- int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+ int userId = getUserId(tile);
widgetEditor.putInt(PeopleSpaceUtils.USER_ID, userId);
widgetEditor.apply();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
@@ -438,7 +439,7 @@
}
/** Creates a {@link RemoteViews} for {@code tile}. */
- private static RemoteViews createRemoteViews(Context context,
+ public static RemoteViews createRemoteViews(Context context,
PeopleSpaceTile tile, int appWidgetId) {
RemoteViews views;
if (tile.getNotificationKey() != null) {
@@ -455,6 +456,9 @@
PeopleSpaceTile tile, int appWidgetId) {
try {
views.setTextViewText(R.id.name, tile.getUserName().toString());
+ views.setImageViewIcon(R.id.person_icon, tile.getUserIcon());
+ views.setBoolean(R.id.content_background, "setClipToOutline", true);
+
views.setImageViewBitmap(
R.id.package_icon,
PeopleSpaceUtils.convertDrawableToBitmap(
@@ -462,8 +466,6 @@
tile.getPackageName())
)
);
- views.setImageViewIcon(R.id.person_icon, tile.getUserIcon());
- views.setBoolean(R.id.content_background, "setClipToOutline", true);
Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
activityIntent.addFlags(
@@ -612,11 +614,27 @@
.collect(Collectors.toList());
}
+ /** Returns {@code PeopleSpaceTile} based on provided {@ConversationChannel}. */
+ public static PeopleSpaceTile getTile(ConversationChannel channel, LauncherApps launcherApps) {
+ if (channel == null) {
+ Log.i(TAG, "ConversationChannel is null");
+ return null;
+ }
+
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(channel, launcherApps).build();
+ if (!PeopleSpaceUtils.shouldKeepConversation(tile)) {
+ Log.i(TAG, "PeopleSpaceTile is not valid");
+ return null;
+ }
+
+ return tile;
+ }
+
/** Returns the last interaction time with the user specified by {@code PeopleSpaceTile}. */
private static Long getLastInteraction(IPeopleManager peopleManager,
PeopleSpaceTile tile) {
try {
- int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+ int userId = getUserId(tile);
String pkg = tile.getPackageName();
return peopleManager.getLastInteraction(pkg, userId, tile.getId());
} catch (Exception e) {
@@ -701,7 +719,7 @@
* </li>
*/
public static boolean shouldKeepConversation(PeopleSpaceTile tile) {
- return tile != null && tile.getUserName().length() != 0;
+ return tile != null && !TextUtils.isEmpty(tile.getUserName());
}
private static boolean hasBirthdayStatus(PeopleSpaceTile tile, Context context) {
@@ -792,8 +810,7 @@
private static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
Context context, int appWidgetId, PeopleSpaceTile tile) {
updateAppWidgetOptions(appWidgetManager, appWidgetId, tile);
- RemoteViews views = createRemoteViews(context,
- tile, appWidgetId);
+ RemoteViews views = createRemoteViews(context, tile, appWidgetId);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@@ -866,4 +883,14 @@
public static String getKey(String shortcutId, String packageName, int userId) {
return shortcutId + "/" + userId + "/" + packageName;
}
+
+ /** Returns the userId associated with a {@link PeopleSpaceTile} */
+ public static int getUserId(PeopleSpaceTile tile) {
+ return getUserHandle(tile).getIdentifier();
+ }
+
+ /** Returns the {@link UserHandle} associated with a {@link PeopleSpaceTile} */
+ public static UserHandle getUserHandle(PeopleSpaceTile tile) {
+ return UserHandle.getUserHandleForUid(tile.getUid());
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index f5577d3..3d1055f 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -16,13 +16,20 @@
package com.android.systemui.people.widget;
+import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
+import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
+
import android.app.PendingIntent;
import android.app.people.IPeopleManager;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.LauncherApps;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import android.widget.RemoteViews;
@@ -32,6 +39,8 @@
import com.android.systemui.R;
import com.android.systemui.people.PeopleSpaceUtils;
+import java.util.Collections;
+
/** People Space Widget Provider class. */
public class PeopleSpaceWidgetProvider extends AppWidgetProvider {
private static final String TAG = "PeopleSpaceWidgetPvd";
@@ -88,11 +97,31 @@
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
+ LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+
for (int widgetId : appWidgetIds) {
if (DEBUG) Log.d(TAG, "Widget removed");
mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED);
+ if (launcherApps != null) {
+ SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId),
+ Context.MODE_PRIVATE);
+ String packageName = widgetSp.getString(PACKAGE_NAME, null);
+ String shortcutId = widgetSp.getString(SHORTCUT_ID, null);
+ int userId = widgetSp.getInt(USER_ID, -1);
+
+ if (packageName != null && shortcutId != null && userId != -1) {
+ try {
+ if (DEBUG) Log.d(TAG, "Uncaching shortcut for PeopleTile: " + shortcutId);
+ launcherApps.uncacheShortcuts(packageName,
+ Collections.singletonList(shortcutId),
+ UserHandle.of(userId),
+ LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
+ } catch (Exception e) {
+ Log.d(TAG, "Exception uncaching shortcut:" + e);
+ }
+ }
+ }
PeopleSpaceUtils.removeStorageForTile(context, widgetId);
}
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 619729e..9967936 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -72,6 +72,7 @@
private boolean mFullyExpanded;
private QuickStatusBarHeader mHeader;
private boolean mTriggeredExpand;
+ private boolean mShouldAnimate;
private int mOpenX;
private int mOpenY;
private boolean mAnimatingOpen;
@@ -108,16 +109,6 @@
updateDetailText();
mClipper = new QSDetailClipper(this);
-
- final OnClickListener doneListener = new OnClickListener() {
- @Override
- public void onClick(View v) {
- announceForAccessibility(
- mContext.getString(R.string.accessibility_desc_quick_settings));
- mQsPanelController.closeDetail();
- }
- };
- mDetailDoneButton.setOnClickListener(doneListener);
}
/** */
@@ -169,6 +160,7 @@
public void handleShowingDetail(final DetailAdapter adapter, int x, int y,
boolean toggleQs) {
final boolean showingDetail = adapter != null;
+ final boolean wasShowingDetail = mDetailAdapter != null;
setClickable(showingDetail);
if (showingDetail) {
setupDetailHeader(adapter);
@@ -178,6 +170,7 @@
} else {
mTriggeredExpand = false;
}
+ mShouldAnimate = adapter.shouldAnimate();
mOpenX = x;
mOpenY = y;
} else {
@@ -190,10 +183,10 @@
}
}
- boolean visibleDiff = (mDetailAdapter != null) != (adapter != null);
- if (!visibleDiff && mDetailAdapter == adapter) return; // already in right state
- AnimatorListener listener = null;
- if (adapter != null) {
+ boolean visibleDiff = wasShowingDetail != showingDetail;
+ if (!visibleDiff && !wasShowingDetail) return; // already in right state
+ AnimatorListener listener;
+ if (showingDetail) {
int viewCacheIndex = adapter.getMetricsCategory();
View detailView = adapter.createDetailView(mContext, mDetailViews.get(viewCacheIndex),
mDetailContent);
@@ -213,7 +206,7 @@
listener = mHideGridContentWhenDone;
setVisibility(View.VISIBLE);
} else {
- if (mDetailAdapter != null) {
+ if (wasShowingDetail) {
Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
mUiEventLogger.log(mDetailAdapter.closeDetailEvent());
}
@@ -227,7 +220,15 @@
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- animateDetailVisibleDiff(x, y, visibleDiff, listener);
+ if (mShouldAnimate) {
+ animateDetailVisibleDiff(x, y, visibleDiff, listener);
+ } else {
+ if (showingDetail) {
+ showImmediately();
+ } else {
+ hideImmediately();
+ }
+ }
}
protected void animateDetailVisibleDiff(int x, int y, boolean visibleDiff, AnimatorListener listener) {
@@ -245,6 +246,17 @@
}
}
+ void showImmediately() {
+ setVisibility(VISIBLE);
+ mClipper.cancelAnimator();
+ mClipper.showBackground();
+ }
+
+ public void hideImmediately() {
+ mClipper.cancelAnimator();
+ setVisibility(View.GONE);
+ }
+
protected void setupDetailFooter(DetailAdapter adapter) {
final Intent settingsIntent = adapter.getSettingsIntent();
mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
@@ -255,6 +267,13 @@
Dependency.get(ActivityStarter.class)
.postStartActivityDismissingKeyguard(settingsIntent, 0);
});
+ mDetailDoneButton.setOnClickListener(v -> {
+ announceForAccessibility(
+ mContext.getString(R.string.accessibility_desc_quick_settings));
+ if (!adapter.onDoneButtonClicked()) {
+ mQsPanelController.closeDetail();
+ }
+ });
}
protected void setupDetailHeader(final DetailAdapter adapter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
index 7d87e174..b50af00 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
@@ -39,7 +39,7 @@
/** Show the supplied DetailAdapter in the Quick Settings. */
public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
if (mQsPanelController != null) {
- mQsPanelController.showDetailDapater(detailAdapter, x, y);
+ mQsPanelController.showDetailAdapter(detailAdapter, x, y);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 7820921..fcb35e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -311,7 +311,7 @@
}
/** */
- public void showDetailDapater(DetailAdapter detailAdapter, int x, int y) {
+ public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
mView.showDetailAdapter(true, detailAdapter, new int[]{x, y});
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 3866382..1411fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -170,7 +170,8 @@
// Update visibility of footer
mIsVisible = (isDeviceManaged && !isDemoDevice) || hasCACerts || hasCACertsInWorkProfile
|| vpnName != null || vpnNameWorkProfile != null
- || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled;
+ || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled
+ || (hasWorkProfile && isNetworkLoggingEnabled);
// Update the string
mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
@@ -275,12 +276,30 @@
vpnName);
}
if (isProfileOwnerOfOrganizationOwnedDevice) {
+ if (isNetworkLoggingEnabled) {
+ if (organizationName == null) {
+ return mContext.getString(
+ R.string.quick_settings_disclosure_management_monitoring);
+ }
+ return mContext.getString(
+ R.string.quick_settings_disclosure_named_management_monitoring,
+ organizationName);
+ }
if (workProfileOrganizationName == null) {
return mContext.getString(R.string.quick_settings_disclosure_management);
}
return mContext.getString(R.string.quick_settings_disclosure_named_management,
workProfileOrganizationName);
}
+ if (hasWorkProfile && isNetworkLoggingEnabled) {
+ if (workProfileOrganizationName == null) {
+ return mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_monitoring);
+ }
+ return mContext.getString(
+ R.string.quick_settings_disclosure_named_managed_profile_monitoring,
+ workProfileOrganizationName);
+ }
return null;
}
@@ -367,7 +386,8 @@
}
// network logging section
- CharSequence networkLoggingMessage = getNetworkLoggingMessage(isNetworkLoggingEnabled);
+ CharSequence networkLoggingMessage = getNetworkLoggingMessage(isDeviceManaged,
+ isNetworkLoggingEnabled);
if (networkLoggingMessage == null) {
dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.GONE);
} else {
@@ -492,9 +512,15 @@
return mContext.getString(R.string.monitoring_description_ca_certificate);
}
- protected CharSequence getNetworkLoggingMessage(boolean isNetworkLoggingEnabled) {
+ protected CharSequence getNetworkLoggingMessage(boolean isDeviceManaged,
+ boolean isNetworkLoggingEnabled) {
if (!isNetworkLoggingEnabled) return null;
- return mContext.getString(R.string.monitoring_description_management_network_logging);
+ if (isDeviceManaged) {
+ return mContext.getString(R.string.monitoring_description_management_network_logging);
+ } else {
+ return mContext.getString(
+ R.string.monitoring_description_managed_profile_network_logging);
+ }
}
protected CharSequence getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index a567f51..aa6bbbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -34,7 +34,7 @@
import androidx.annotation.VisibleForTesting;
-import com.android.keyguard.CarrierTextController;
+import com.android.keyguard.CarrierTextManager;
import com.android.settingslib.AccessibilityContentDescriptions;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.R;
@@ -58,7 +58,7 @@
private final ActivityStarter mActivityStarter;
private final Handler mBgHandler;
private final NetworkController mNetworkController;
- private final CarrierTextController mCarrierTextController;
+ private final CarrierTextManager mCarrierTextManager;
private final TextView mNoSimTextView;
private final H mMainHandler;
private final Callback mCallback;
@@ -153,7 +153,7 @@
}
};
- private static class Callback implements CarrierTextController.CarrierTextCallback {
+ private static class Callback implements CarrierTextManager.CarrierTextCallback {
private H mHandler;
Callback(H handler) {
@@ -161,7 +161,7 @@
}
@Override
- public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+ public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
}
}
@@ -169,7 +169,7 @@
private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
@Background Handler bgHandler, @Main Looper mainLooper,
NetworkController networkController,
- CarrierTextController.Builder carrierTextControllerBuilder, Context context) {
+ CarrierTextManager.Builder carrierTextManagerBuilder, Context context) {
if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
mProviderModel = true;
} else {
@@ -178,7 +178,7 @@
mActivityStarter = activityStarter;
mBgHandler = bgHandler;
mNetworkController = networkController;
- mCarrierTextController = carrierTextControllerBuilder
+ mCarrierTextManager = carrierTextManagerBuilder
.setShowAirplaneMode(false)
.setShowMissingSim(false)
.build();
@@ -196,7 +196,6 @@
mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState);
mCallback = new Callback(mMainHandler);
-
mCarrierGroups[0] = view.getCarrier1View();
mCarrierGroups[1] = view.getCarrier2View();
mCarrierGroups[2] = view.getCarrier3View();
@@ -247,10 +246,10 @@
if (mNetworkController.hasVoiceCallingFeature()) {
mNetworkController.addCallback(mSignalCallback);
}
- mCarrierTextController.setListening(mCallback);
+ mCarrierTextManager.setListening(mCallback);
} else {
mNetworkController.removeCallback(mSignalCallback);
- mCarrierTextController.setListening(null);
+ mCarrierTextManager.setListening(null);
}
}
@@ -277,7 +276,7 @@
}
@MainThread
- private void handleUpdateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+ private void handleUpdateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
if (!mMainHandler.getLooper().isCurrentThread()) {
mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
return;
@@ -331,13 +330,13 @@
}
private static class H extends Handler {
- private Consumer<CarrierTextController.CarrierTextCallbackInfo> mUpdateCarrierInfo;
+ private Consumer<CarrierTextManager.CarrierTextCallbackInfo> mUpdateCarrierInfo;
private Runnable mUpdateState;
static final int MSG_UPDATE_CARRIER_INFO = 0;
static final int MSG_UPDATE_STATE = 1;
H(Looper looper,
- Consumer<CarrierTextController.CarrierTextCallbackInfo> updateCarrierInfo,
+ Consumer<CarrierTextManager.CarrierTextCallbackInfo> updateCarrierInfo,
Runnable updateState) {
super(looper);
mUpdateCarrierInfo = updateCarrierInfo;
@@ -349,7 +348,7 @@
switch (msg.what) {
case MSG_UPDATE_CARRIER_INFO:
mUpdateCarrierInfo.accept(
- (CarrierTextController.CarrierTextCallbackInfo) msg.obj);
+ (CarrierTextManager.CarrierTextCallbackInfo) msg.obj);
break;
case MSG_UPDATE_STATE:
mUpdateState.run();
@@ -366,13 +365,13 @@
private final Handler mHandler;
private final Looper mLooper;
private final NetworkController mNetworkController;
- private final CarrierTextController.Builder mCarrierTextControllerBuilder;
+ private final CarrierTextManager.Builder mCarrierTextControllerBuilder;
private final Context mContext;
@Inject
public Builder(ActivityStarter activityStarter, @Background Handler handler,
@Main Looper looper, NetworkController networkController,
- CarrierTextController.Builder carrierTextControllerBuilder, Context context) {
+ CarrierTextManager.Builder carrierTextControllerBuilder, Context context) {
mActivityStarter = activityStarter;
mHandler = handler;
mLooper = looper;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 6176a57..4144591 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -25,6 +25,7 @@
import com.android.systemui.R
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsDialog
import com.android.systemui.dagger.qualifiers.Background
@@ -91,7 +92,7 @@
override fun isAvailable(): Boolean {
return featureFlags.isKeyguardLayoutEnabled &&
controlsLockscreen &&
- controlsComponent.getControlsUiController().isPresent
+ controlsComponent.getVisibility() != UNAVAILABLE
}
override fun newTileState(): QSTile.State {
@@ -154,4 +155,4 @@
override fun getTileLabel(): CharSequence {
return mContext.getText(R.string.quick_controls_title)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index 26adfdc..a6cddd3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -82,7 +82,7 @@
@Override
public DetailAdapter getDetailAdapter() {
- return mUserSwitcherController.userDetailAdapter;
+ return mUserSwitcherController.mUserDetailAdapter;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index d56c806..dc639dc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -95,6 +95,17 @@
this.requested = request;
this.captured = captured;
}
+
+ @Override
+ public String toString() {
+ return "CaptureResult{"
+ + "requested=" + requested
+ + " (" + requested.width() + "x" + requested.height() + ")"
+ + ", captured=" + captured
+ + " (" + captured.width() + "x" + captured.height() + ")"
+ + ", image=" + image
+ + '}';
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index d97f644..ad5e637 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -253,7 +253,7 @@
&& result.captured.height() < result.requested.height();
boolean finish = false;
- if (partialResult) {
+ if (partialResult || emptyResult) {
// Potentially reached a vertical boundary. Extend in the other direction.
switch (mDirection) {
case DOWN:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 7aa41e4..862c279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -67,6 +67,10 @@
return mFlagReader.isEnabled(R.bool.flag_brightness_slider);
}
+ public boolean useNewLockscreenAnimations() {
+ return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations);
+ }
+
public boolean isPeopleTileEnabled() {
return mFlagReader.isEnabled(R.bool.flag_conversations);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 2f0f90d..c1feaca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -11,14 +11,10 @@
import android.graphics.PorterDuffXfermode
import android.graphics.RadialGradient
import android.graphics.Shader
-import android.os.SystemProperties
import android.util.AttributeSet
import android.view.View
import com.android.systemui.Interpolators
-val enableLightReveal =
- SystemProperties.getBoolean("persist.sysui.show_new_screen_on_transitions", false)
-
/**
* Provides methods to modify the various properties of a [LightRevealScrim] to reveal between 0% to
* 100% of the view(s) underneath the scrim.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 8c2fa33..85d8df8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -29,6 +29,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.tuner.TunerService;
@@ -54,6 +55,7 @@
private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
private final Resources mResources;
private final BatteryController mBatteryController;
+ private final FeatureFlags mFeatureFlags;
private boolean mDozeAlwaysOn;
private boolean mControlScreenOffAnimation;
@@ -65,7 +67,8 @@
AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
PowerManager powerManager,
BatteryController batteryController,
- TunerService tunerService) {
+ TunerService tunerService,
+ FeatureFlags featureFlags) {
mResources = resources;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -74,6 +77,7 @@
mControlScreenOffAnimation = !getDisplayNeedsBlanking();
mPowerManager = powerManager;
mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
+ mFeatureFlags = featureFlags;
tunerService.addTunable(
this,
@@ -200,8 +204,7 @@
* then abruptly showing AOD.
*/
public boolean shouldControlUnlockedScreenOff() {
- return getAlwaysOn() && SystemProperties.getBoolean(
- "persist.sysui.show_new_screen_on_transitions", false);
+ return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations();
}
private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 2ce0a87..986333c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -19,6 +19,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
@@ -183,6 +184,7 @@
private ControlsComponent mControlsComponent;
private int mLockScreenMode;
private BroadcastDispatcher mBroadcastDispatcher;
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
public KeyguardBottomAreaView(Context context) {
this(context, null);
@@ -295,7 +297,8 @@
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
getContext().registerReceiverAsUser(mDevicePolicyReceiver,
UserHandle.ALL, filter, null, null);
- Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
+ mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+ mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
mKeyguardStateController.addCallback(this);
}
@@ -307,7 +310,7 @@
mRightExtension.destroy();
mLeftExtension.destroy();
getContext().unregisterReceiver(mDevicePolicyReceiver);
- Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
+ mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
}
private void initAccessibility() {
@@ -410,12 +413,6 @@
}
private void updateLeftAffordanceIcon() {
- if (mDozing) {
- mAltLeftButton.setVisibility(GONE);
- } else if (mAltLeftButton.getDrawable() != null) {
- mAltLeftButton.setVisibility(VISIBLE);
- }
-
if (!mShowLeftAffordance || mDozing) {
mLeftAffordanceView.setVisibility(GONE);
return;
@@ -430,6 +427,14 @@
mLeftAffordanceView.setContentDescription(state.contentDescription);
}
+ private void updateControlsVisibility() {
+ if (mDozing || mControlsComponent.getVisibility() != AVAILABLE) {
+ mAltLeftButton.setVisibility(GONE);
+ } else {
+ mAltLeftButton.setVisibility(VISIBLE);
+ }
+ }
+
public boolean isLeftVoiceAssist() {
return mLeftIsVoiceAssist;
}
@@ -769,6 +774,7 @@
updateCameraVisibility();
updateLeftAffordanceIcon();
+ updateControlsVisibility();
if (dozing) {
mOverlayContainer.setVisibility(INVISIBLE);
@@ -889,35 +895,27 @@
}
private void setupControls() {
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+ boolean inNewLayout = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
+ boolean settingEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+ "controls_lockscreen", 0) == 1;
+ if (!inNewLayout || !settingEnabled || !mControlsComponent.isEnabled()) {
mAltLeftButton.setVisibility(View.GONE);
- mAltLeftButton.setOnClickListener(null);
return;
}
- if (Settings.Global.getInt(mContext.getContentResolver(), "controls_lockscreen", 0) == 0) {
- return;
- }
-
- if (mControlsComponent.getControlsListingController().isPresent()) {
- mControlsComponent.getControlsListingController().get()
- .addCallback(list -> {
- if (!list.isEmpty()) {
- mAltLeftButton.setImageDrawable(list.get(0).loadIcon());
- mAltLeftButton.setVisibility(View.VISIBLE);
- mAltLeftButton.setOnClickListener((v) -> {
- ControlsUiController ui = mControlsComponent
- .getControlsUiController().get();
- mControlsDialog = new ControlsDialog(mContext, mBroadcastDispatcher)
- .show(ui);
- });
-
- } else {
- mAltLeftButton.setVisibility(View.GONE);
- mAltLeftButton.setOnClickListener(null);
- }
- });
- }
+ mControlsComponent.getControlsListingController().get()
+ .addCallback(list -> {
+ if (!list.isEmpty()) {
+ mAltLeftButton.setImageDrawable(list.get(0).loadIcon());
+ mAltLeftButton.setOnClickListener((v) -> {
+ ControlsUiController ui = mControlsComponent
+ .getControlsUiController().get();
+ mControlsDialog = new ControlsDialog(mContext, mBroadcastDispatcher)
+ .show(ui);
+ });
+ }
+ updateControlsVisibility();
+ });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index e0df4f8..a6daed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -56,10 +56,12 @@
private int mKeyguardStatusHeight;
/**
- * Height of {@link KeyguardUserSwitcherListView} when it
- * is closed and only the current user's icon is visible.
+ * Height of user avatar used by the multi-user switcher. This could either be the
+ * {@link KeyguardUserSwitcherListView} when it is closed and only the current user's icon is
+ * visible, or it could be height of the avatar used by the
+ * {@link com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController}.
*/
- private int mKeyguardUserSwitcherHeight;
+ private int mUserSwitchHeight;
/**
* Preferred Y position of clock.
@@ -67,6 +69,11 @@
private int mClockPreferredY;
/**
+ * Preferred Y position of user avatar used by the multi-user switcher.
+ */
+ private int mUserSwitchPreferredY;
+
+ /**
* Whether or not there is a custom clock face on keyguard.
*/
private boolean mHasCustomClock;
@@ -181,20 +188,21 @@
*/
public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight,
float panelExpansion, int parentHeight, int keyguardStatusHeight,
- int keyguardUserSwitcherHeight, int clockPreferredY, boolean hasCustomClock,
- boolean hasVisibleNotifs, float dark, float emptyDragAmount, boolean bypassEnabled,
- int unlockedStackScrollerPadding, boolean showLockIcon, float qsExpansion,
- int cutoutTopInset) {
+ int userSwitchHeight, int clockPreferredY, int userSwitchPreferredY,
+ boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount,
+ boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon,
+ float qsExpansion, int cutoutTopInset) {
mMinTopMargin = statusBarMinHeight + (showLockIcon
? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon)
- + keyguardUserSwitcherHeight;
+ + userSwitchHeight;
mMaxShadeBottom = maxShadeBottom;
mNotificationStackHeight = notificationStackHeight;
mPanelExpansion = panelExpansion;
mHeight = parentHeight;
mKeyguardStatusHeight = keyguardStatusHeight;
- mKeyguardUserSwitcherHeight = keyguardUserSwitcherHeight;
+ mUserSwitchHeight = userSwitchHeight;
mClockPreferredY = clockPreferredY;
+ mUserSwitchPreferredY = userSwitchPreferredY;
mHasCustomClock = hasCustomClock;
mHasVisibleNotifs = hasVisibleNotifs;
mDarkAmount = dark;
@@ -208,6 +216,7 @@
public void run(Result result) {
final int y = getClockY(mPanelExpansion, mDarkAmount);
result.clockY = y;
+ result.userSwitchY = getUserSwitcherY(mPanelExpansion);
result.clockYFullyDozing = getClockY(
1.0f /* panelExpansion */, 1.0f /* darkAmount */);
result.clockAlpha = getClockAlpha(y);
@@ -241,7 +250,7 @@
private int getExpandedPreferredClockY() {
if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
- return mMinTopMargin;
+ return mMinTopMargin + mUserSwitchHeight;
}
return (mHasCustomClock && (!mHasVisibleNotifs || mBypassEnabled)) ? getPreferredClockY()
: getExpandedClockPosition();
@@ -257,7 +266,7 @@
final int containerCenter = mMinTopMargin + availableHeight / 2;
float y = containerCenter
- - (mKeyguardStatusHeight + mKeyguardUserSwitcherHeight) * CLOCK_HEIGHT_WEIGHT
+ - (mKeyguardStatusHeight + mUserSwitchHeight) * CLOCK_HEIGHT_WEIGHT
- mClockNotificationsMargin - mNotificationStackHeight / 2;
if (y < mMinTopMargin) {
y = mMinTopMargin;
@@ -299,6 +308,17 @@
return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mEmptyDragAmount);
}
+ private int getUserSwitcherY(float panelExpansion) {
+ float userSwitchYRegular = mUserSwitchPreferredY;
+ float userSwitchYBouncer = -mKeyguardStatusHeight - mUserSwitchHeight;
+
+ // Move user-switch up while collapsing the shade
+ float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion);
+ float userSwitchY = MathUtils.lerp(userSwitchYBouncer, userSwitchYRegular, shadeExpansion);
+
+ return (int) (userSwitchY + mEmptyDragAmount);
+ }
+
/**
* We might want to fade out the clock when the user is swiping up.
* One exception is when the bouncer will become visible, in this cause the clock
@@ -341,6 +361,11 @@
public int clockY;
/**
+ * The y translation of the multi-user switch.
+ */
+ public int userSwitchY;
+
+ /**
* The y translation of the clock when we're fully dozing.
*/
public int clockYFullyDozing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
new file mode 100644
index 0000000..377fb92
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.keyguard.CarrierTextController;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
+public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
+ private final CarrierTextController mCarrierTextController;
+
+ @Inject
+ public KeyguardStatusBarViewController(
+ KeyguardStatusBarView view, CarrierTextController carrierTextController) {
+ super(view);
+ mCarrierTextController = carrierTextController;
+ }
+
+ @Override
+ protected void onInit() {
+ super.onInit();
+ mCarrierTextController.init();
+ }
+
+ @Override
+ protected void onViewAttached() {
+ }
+
+ @Override
+ protected void onViewDetached() {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index d9cb9ce..16f36b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -168,6 +168,6 @@
}
protected DetailAdapter getUserDetailAdapter() {
- return mUserSwitcherController.userDetailAdapter;
+ return mUserSwitcherController.mUserDetailAdapter;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 3b09eda..8aadef8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -18,6 +18,10 @@
import static android.view.View.GONE;
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.START;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -66,6 +70,8 @@
import android.widget.FrameLayout;
import android.widget.TextView;
+import androidx.constraintlayout.widget.ConstraintSet;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
@@ -77,6 +83,8 @@
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.DejankUtils;
@@ -95,9 +103,11 @@
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.GestureRecorder;
@@ -133,6 +143,7 @@
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
@@ -292,14 +303,13 @@
new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() {
@Override
public void onKeyguardUserSwitcherChanged(boolean open) {
- if (mKeyguardUserSwitcherController != null
- && mKeyguardUserSwitcherController.isSimpleUserSwitcher()) {
- return;
+ if (mKeyguardUserSwitcherController == null) {
+ updateUserSwitcherVisibility(false);
+ } else if (!mKeyguardUserSwitcherController.isSimpleUserSwitcher()) {
+ updateUserSwitcherVisibility(open
+ && mKeyguardStateController.isShowing()
+ && !mKeyguardStateController.isKeyguardFadingAway());
}
-
- updateUserSwitcherVisibility(open
- && mKeyguardStateController.isShowing()
- && !mKeyguardStateController.isKeyguardFadingAway());
}
};
@@ -315,7 +325,10 @@
private final MediaHierarchyManager mMediaHierarchyManager;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+ private final KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
+ private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
+ private final QSDetailDisplayer mQSDetailDisplayer;
private final FeatureFlags mFeatureFlags;
private final ScrimController mScrimController;
private final ControlsComponent mControlsComponent;
@@ -327,8 +340,11 @@
private int mMaxAllowedKeyguardNotifications;
private KeyguardAffordanceHelper mAffordanceHelper;
+ private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
+ private boolean mKeyguardUserSwitcherIsShowing;
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
+ private KeyguardStatusBarViewController mKeyguarStatusBarViewController;
private ViewGroup mBigClockContainer;
private QS mQs;
private FrameLayout mQsFrame;
@@ -352,6 +368,7 @@
private boolean mQsExpandedWhenExpandingStarted;
private boolean mQsFullyExpanded;
private boolean mKeyguardShowing;
+ private boolean mKeyguardQsUserSwitchEnabled;
private boolean mKeyguardUserSwitcherEnabled;
private boolean mDozing;
private boolean mDozingOnDown;
@@ -578,7 +595,10 @@
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
+ KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
+ KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
+ QSDetailDisplayer qsDetailDisplayer,
NotificationGroupManagerLegacy groupManager,
NotificationIconAreaController notificationIconAreaController,
AuthController authController,
@@ -604,10 +624,16 @@
mGroupManager = groupManager;
mNotificationIconAreaController = notificationIconAreaController;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
+ mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
mFeatureFlags = featureFlags;
+ mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
+ mQSDetailDisplayer = qsDetailDisplayer;
mKeyguardUserSwitcherEnabled = mResources.getBoolean(
com.android.internal.R.bool.config_keyguardUserSwitcher);
+ mKeyguardQsUserSwitchEnabled =
+ mKeyguardUserSwitcherEnabled && mResources.getBoolean(
+ R.bool.config_keyguard_user_switch_opens_qs_details);
mView.setWillNotDraw(!DEBUG);
mLayoutInflater = layoutInflater;
mFalsingManager = falsingManager;
@@ -688,14 +714,22 @@
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
mBigClockContainer = mView.findViewById(R.id.big_clock_container);
+ UserAvatarView userAvatarView = null;
KeyguardUserSwitcherView keyguardUserSwitcherView = null;
if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
- ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
- keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
+ if (mKeyguardQsUserSwitchEnabled) {
+ ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub);
+ userAvatarView = (UserAvatarView) stub.inflate();
+ } else {
+ ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub);
+ keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate();
+ }
}
updateViewControllers(mView.findViewById(R.id.keyguard_status_view),
+ userAvatarView,
+ mKeyguardStatusBar,
keyguardUserSwitcherView);
mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
@@ -741,6 +775,10 @@
});
mView.setAccessibilityDelegate(mAccessibilityDelegate);
+ // dynamically apply the split shade value overrides.
+ if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+ updateResources();
+ }
}
@Override
@@ -770,6 +808,8 @@
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
+ UserAvatarView userAvatarView,
+ KeyguardStatusBarView keyguardStatusBarView,
KeyguardUserSwitcherView keyguardUserSwitcherView) {
// Re-associate the KeyguardStatusViewController
KeyguardStatusViewComponent statusViewComponent =
@@ -777,6 +817,12 @@
mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
mKeyguardStatusViewController.init();
+ KeyguardStatusBarViewComponent statusBarViewComponent =
+ mKeyguardStatusBarViewComponentFactory.build(keyguardStatusBarView);
+ mKeyguarStatusBarViewController =
+ statusBarViewComponent.getKeyguardStatusBarViewController();
+ mKeyguarStatusBarViewController.init();
+
// Re-associate the clock container with the keyguard clock switch.
KeyguardClockSwitchController keyguardClockSwitchController =
statusViewComponent.getKeyguardClockSwitchController();
@@ -789,18 +835,27 @@
mKeyguardUserSwitcherController.removeCallback();
}
+ mKeyguardQsUserSwitchController = null;
+ mKeyguardUserSwitcherController = null;
+
// Re-associate the KeyguardUserSwitcherController
- if (keyguardUserSwitcherView != null) {
+ if (userAvatarView != null) {
+ KeyguardQsUserSwitchComponent userSwitcherComponent =
+ mKeyguardQsUserSwitchComponentFactory.build(userAvatarView);
+ mKeyguardQsUserSwitchController =
+ userSwitcherComponent.getKeyguardQsUserSwitchController();
+ mKeyguardQsUserSwitchController.setNotificationPanelViewController(this);
+ mKeyguardQsUserSwitchController.init();
+ mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true);
+ } else if (keyguardUserSwitcherView != null) {
KeyguardUserSwitcherComponent userSwitcherComponent =
mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView);
-
mKeyguardUserSwitcherController =
userSwitcherComponent.getKeyguardUserSwitcherController();
mKeyguardUserSwitcherController.setCallback(mKeyguardUserSwitcherListener);
mKeyguardUserSwitcherController.init();
mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true);
} else {
- mKeyguardUserSwitcherController = null;
mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false);
}
}
@@ -840,11 +895,21 @@
mNotificationStackScrollLayoutController.setLayoutParams(lp);
}
+ // In order to change the constraints at runtime, all children of the Constraint Layout
+ // must have ids.
+ ensureAllViewsHaveIds(mNotificationContainerParent);
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mNotificationContainerParent);
if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
- // In order to change the constraints at runtime, all children of the Constraint Layout
- // must have ids.
- ensureAllViewsHaveIds(mNotificationContainerParent);
+ constraintSet.connect(R.id.qs_frame, END, R.id.qs_edge_guideline, END);
+ constraintSet.connect(
+ R.id.notification_stack_scroller, START,
+ R.id.qs_edge_guideline, START);
+ } else {
+ constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END);
+ constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START);
}
+ constraintSet.applyTo(mNotificationContainerParent);
}
private static void ensureAllViewsHaveIds(ViewGroup parentView) {
@@ -856,6 +921,25 @@
}
}
+ private View reInflateStub(int viewId, int stubId, int layoutId, boolean enabled) {
+ View view = mView.findViewById(viewId);
+ if (view != null) {
+ int index = mView.indexOfChild(view);
+ mView.removeView(view);
+ if (enabled) {
+ view = mLayoutInflater.inflate(layoutId, mView, false);
+ mView.addView(view, index);
+ } else {
+ view = null;
+ }
+ } else if (enabled) {
+ // It's possible the stub was never inflated if the configuration changed
+ ViewStub stub = mView.findViewById(stubId);
+ view = stub.inflate();
+ }
+ return view;
+ }
+
private void reInflateViews() {
if (DEBUG) Log.d(TAG, "reInflateViews");
// Re-inflate the status view group.
@@ -867,26 +951,27 @@
mView.addView(keyguardStatusView, index);
// Re-inflate the keyguard user switcher group.
- boolean showUserSwitcher =
- mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled();
- KeyguardUserSwitcherView keyguardUserSwitcherView = mView.findViewById(
- R.id.keyguard_user_switcher_view);
- if (keyguardUserSwitcherView != null) {
- index = mView.indexOfChild(keyguardUserSwitcherView);
- mView.removeView(keyguardUserSwitcherView);
- if (showUserSwitcher) {
- keyguardUserSwitcherView = (KeyguardUserSwitcherView) mLayoutInflater.inflate(
- R.layout.keyguard_user_switcher, mView, false);
- mView.addView(keyguardUserSwitcherView, index);
- }
- } else if (showUserSwitcher) {
- // It's possible the user switcher was never inflated if the configuration changed
- ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
- keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
- }
+ boolean isUserSwitcherEnabled = mUserManager.isUserSwitcherEnabled();
+ boolean showQsUserSwitch = mKeyguardQsUserSwitchEnabled && isUserSwitcherEnabled;
+ boolean showKeyguardUserSwitcher =
+ !mKeyguardQsUserSwitchEnabled
+ && mKeyguardUserSwitcherEnabled
+ && isUserSwitcherEnabled;
+ UserAvatarView userAvatarView = (UserAvatarView) reInflateStub(
+ R.id.keyguard_qs_user_switch_view /* viewId */,
+ R.id.keyguard_qs_user_switch_stub /* stubId */,
+ R.layout.keyguard_qs_user_switch /* layoutId */,
+ showQsUserSwitch /* enabled */);
+ KeyguardUserSwitcherView keyguardUserSwitcherView =
+ (KeyguardUserSwitcherView) reInflateStub(
+ R.id.keyguard_user_switcher_view /* viewId */,
+ R.id.keyguard_user_switcher_stub /* stubId */,
+ R.layout.keyguard_user_switcher /* layoutId */,
+ showKeyguardUserSwitcher /* enabled */);
mBigClockContainer.removeAllViews();
- updateViewControllers(keyguardStatusView, keyguardUserSwitcherView);
+ updateViewControllers(
+ keyguardStatusView, userAvatarView, mKeyguardStatusBar, keyguardUserSwitcherView);
// Update keyguard bottom area
index = mView.indexOfChild(mKeyguardBottomArea);
@@ -910,6 +995,13 @@
false,
false,
mBarState);
+ if (mKeyguardQsUserSwitchController != null) {
+ mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
+ mBarState,
+ false,
+ false,
+ mBarState);
+ }
if (mKeyguardUserSwitcherController != null) {
mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
mBarState,
@@ -1009,10 +1101,15 @@
int totalHeight = mView.getHeight();
int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
int clockPreferredY = mKeyguardStatusViewController.getClockPreferredY(totalHeight);
+ int userSwitcherPreferredY = mStatusBarMinHeight;
boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
.getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
mKeyguardStatusViewController.setHasVisibleNotifications(hasVisibleNotifications);
+ int userIconHeight = mKeyguardQsUserSwitchController != null
+ ? mKeyguardQsUserSwitchController.getUserIconHeight()
+ : (mKeyguardUserSwitcherController != null
+ ? mKeyguardUserSwitcherController.getUserIconHeight() : 0);
mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding,
mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
getExpandedFraction(),
@@ -1021,9 +1118,8 @@
? mKeyguardStatusViewController.getHeight()
: (int) (mKeyguardStatusViewController.getHeight()
- mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
- mKeyguardUserSwitcherController == null
- ? 0 : mKeyguardUserSwitcherController.getUserIconHeight(),
- clockPreferredY, hasCustomClock(),
+ userIconHeight,
+ clockPreferredY, userSwitcherPreferredY, hasCustomClock(),
hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
bypassEnabled, getUnlockedStackScrollerPadding(),
mUpdateMonitor.canShowLockIcon(),
@@ -1033,11 +1129,16 @@
mKeyguardStatusViewController.updatePosition(
mClockPositionResult.clockX, mClockPositionResult.clockY,
mClockPositionResult.clockScale, animateClock);
+ if (mKeyguardQsUserSwitchController != null) {
+ mKeyguardQsUserSwitchController.updatePosition(
+ mClockPositionResult.clockX,
+ mClockPositionResult.userSwitchY,
+ animateClock);
+ }
if (mKeyguardUserSwitcherController != null) {
mKeyguardUserSwitcherController.updatePosition(
mClockPositionResult.clockX,
- mClockPositionResult.clockY
- - mKeyguardUserSwitcherController.getUserIconHeight(),
+ mClockPositionResult.userSwitchY,
animateClock);
}
updateNotificationTranslucency();
@@ -1180,6 +1281,9 @@
private void updateClock() {
mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha);
+ if (mKeyguardQsUserSwitchController != null) {
+ mKeyguardQsUserSwitchController.setAlpha(mClockPositionResult.clockAlpha);
+ }
if (mKeyguardUserSwitcherController != null) {
mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha);
}
@@ -1271,6 +1375,12 @@
}
}
+ public void expandWithQsDetail(DetailAdapter qsDetailAdapter) {
+ traceQsJank(true /* startTracing */, false /* wasCancelled */);
+ flingSettings(0 /* velocity */, FLING_EXPAND);
+ mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, 0, 0);
+ }
+
public void expandWithoutQs() {
if (isQsExpanded()) {
flingSettings(0 /* velocity */, FLING_COLLAPSE);
@@ -1933,6 +2043,10 @@
}
private float calculateQsTopPadding() {
+ // in split shade mode we want notifications to be directly below status bar
+ if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources) && !mKeyguardShowing) {
+ return 0f;
+ }
if (mKeyguardShowing && (mQsExpandImmediate
|| mIsExpanding && mQsExpandedWhenExpandingStarted)) {
@@ -2572,7 +2686,9 @@
super.onTrackingStarted();
if (mQsFullyExpanded) {
mQsExpandImmediate = true;
- mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
+ if (!Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+ mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
+ }
}
if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
mAffordanceHelper.animateHideLeftRightIcon();
@@ -2814,12 +2930,12 @@
}
/**
- * Updates the vertical position of the panel so it is positioned closer to the touch
+ * Updates the horizontal position of the panel so it is positioned closer to the touch
* responsible for opening the panel.
*
* @param x the x-coordinate the touch event
*/
- protected void updateVerticalPanelPosition(float x) {
+ protected void updateHorizontalPanelPosition(float x) {
if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()) {
resetHorizontalPanelPosition();
return;
@@ -3166,6 +3282,13 @@
true /* keyguardFadingAway */,
false /* goingToFullShade */,
mBarState);
+ if (mKeyguardQsUserSwitchController != null) {
+ mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
+ mBarState,
+ true /* keyguardFadingAway */,
+ false /* goingToFullShade */,
+ mBarState);
+ }
if (mKeyguardUserSwitcherController != null) {
mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
mBarState,
@@ -3396,7 +3519,7 @@
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
- updateVerticalPanelPosition(event.getX());
+ updateHorizontalPanelPosition(event.getX());
handled = true;
}
handled |= super.onTouch(v, event);
@@ -3432,6 +3555,12 @@
}
private void updateUserSwitcherVisibility(boolean open) {
+ // Do not update if previously called with the same state.
+ if (mKeyguardUserSwitcherIsShowing == open) {
+ return;
+ }
+ mKeyguardUserSwitcherIsShowing = open;
+
if (open) {
animateKeyguardStatusBarOut();
mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
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 e63902f..041a97e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -35,7 +35,6 @@
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
-import static com.android.systemui.statusbar.LightRevealScrimKt.getEnableLightReveal;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -180,6 +179,7 @@
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -440,6 +440,7 @@
private final KeyguardViewMediator mKeyguardViewMediator;
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final BrightnessSlider.Factory mBrightnessSliderFactory;
+ private final FeatureFlags mFeatureFlags;
private final List<ExpansionChangedListener> mExpansionChangedListeners;
@@ -762,7 +763,8 @@
Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
NotificationIconAreaController notificationIconAreaController,
- BrightnessSlider.Factory brightnessSliderFactory) {
+ BrightnessSlider.Factory brightnessSliderFactory,
+ FeatureFlags featureFlags) {
super(context);
mNotificationsController = notificationsController;
mLightBarController = lightBarController;
@@ -840,6 +842,7 @@
mDemoModeController = demoModeController;
mNotificationIconAreaController = notificationIconAreaController;
mBrightnessSliderFactory = brightnessSliderFactory;
+ mFeatureFlags = featureFlags;
mExpansionChangedListeners = new ArrayList<>();
@@ -1181,9 +1184,11 @@
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
- if (getEnableLightReveal()) {
+ if (mFeatureFlags.useNewLockscreenAnimations() && mDozeParameters.getAlwaysOn()) {
mLightRevealScrim.setVisibility(View.VISIBLE);
mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE);
+ } else {
+ mLightRevealScrim.setVisibility(View.GONE);
}
mNotificationPanelViewController.initDependencies(
@@ -3614,7 +3619,7 @@
@Override
public void onDozeAmountChanged(float linear, float eased) {
- if (getEnableLightReveal()) {
+ if (mFeatureFlags.useNewLockscreenAnimations()) {
mLightRevealScrim.setRevealAmount(1f - linear);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 9e9533d..b572c57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -47,6 +47,7 @@
import com.android.systemui.settings.brightness.BrightnessSlider;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -200,7 +201,8 @@
DismissCallbackRegistry dismissCallbackRegistry,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
NotificationIconAreaController notificationIconAreaController,
- BrightnessSlider.Factory brightnessSliderFactory) {
+ BrightnessSlider.Factory brightnessSliderFactory,
+ FeatureFlags featureFlags) {
return new StatusBar(
context,
notificationsController,
@@ -279,6 +281,7 @@
notificationShadeDepthController,
statusBarTouchableRegionManager,
notificationIconAreaController,
- brightnessSliderFactory);
+ brightnessSliderFactory,
+ featureFlags);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
new file mode 100644
index 0000000..38f3bc8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.policy;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardConstants;
+import com.android.keyguard.KeyguardVisibilityHelper;
+import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.UserAvatarView;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/**
+ * Manages the user switch on the Keyguard that is used for opening the QS user panel.
+ */
+@KeyguardUserSwitcherScope
+public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> {
+
+ private static final String TAG = "KeyguardQsUserSwitchController";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+ private static final AnimationProperties ANIMATION_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+ private final Context mContext;
+ private Resources mResources;
+ private final UserSwitcherController mUserSwitcherController;
+ private final ScreenLifecycle mScreenLifecycle;
+ private UserSwitcherController.BaseUserAdapter mAdapter;
+ private final KeyguardStateController mKeyguardStateController;
+ protected final SysuiStatusBarStateController mStatusBarStateController;
+ private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+ private final KeyguardUserDetailAdapter mUserDetailAdapter;
+ private NotificationPanelViewController mNotificationPanelViewController;
+ private UserManager mUserManager;
+ UserSwitcherController.UserRecord mCurrentUser;
+
+ // State info for the user switch and keyguard
+ private int mBarState;
+ private float mDarkAmount;
+
+ private final StatusBarStateController.StateListener mStatusBarStateListener =
+ new StatusBarStateController.StateListener() {
+ @Override
+ public void onStateChanged(int newState) {
+ if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState));
+
+ boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
+ boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
+ int oldState = mBarState;
+ mBarState = newState;
+
+ setKeyguardQsUserSwitchVisibility(
+ newState,
+ keyguardFadingAway,
+ goingToFullShade,
+ oldState);
+ }
+
+ @Override
+ public void onDozeAmountChanged(float linearAmount, float amount) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
+ linearAmount, amount));
+ }
+ setDarkAmount(amount);
+ }
+ };
+
+ @Inject
+ public KeyguardQsUserSwitchController(
+ UserAvatarView view,
+ Context context,
+ @Main Resources resources,
+ UserManager userManager,
+ ScreenLifecycle screenLifecycle,
+ UserSwitcherController userSwitcherController,
+ KeyguardStateController keyguardStateController,
+ SysuiStatusBarStateController statusBarStateController,
+ DozeParameters dozeParameters,
+ UiEventLogger uiEventLogger) {
+ super(view);
+ if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
+ mContext = context;
+ mResources = resources;
+ mUserManager = userManager;
+ mScreenLifecycle = screenLifecycle;
+ mUserSwitcherController = userSwitcherController;
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = statusBarStateController;
+ mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
+ keyguardStateController, dozeParameters);
+ mUserDetailAdapter = new KeyguardUserDetailAdapter(mUserSwitcherController, mContext,
+ uiEventLogger);
+ }
+
+ @Override
+ protected void onInit() {
+ super.onInit();
+ if (DEBUG) Log.d(TAG, "onInit");
+ mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return null;
+ }
+ };
+
+ mView.setOnClickListener(v -> {
+ if (isListAnimating()) {
+ return;
+ }
+
+ // Tapping anywhere in the view will open QS user panel
+ openQsUserPanel();
+ });
+
+ updateView(true /* forceUpdate */);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ if (DEBUG) Log.d(TAG, "onViewAttached");
+ mAdapter.registerDataSetObserver(mDataSetObserver);
+ mDataSetObserver.onChanged();
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ if (DEBUG) Log.d(TAG, "onViewDetached");
+
+ mAdapter.unregisterDataSetObserver(mDataSetObserver);
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ }
+
+ public final DataSetObserver mDataSetObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ updateView(false /* forceUpdate */);
+ }
+ };
+
+ /**
+ * @return true if the current user has changed
+ */
+ private boolean updateCurrentUser() {
+ UserSwitcherController.UserRecord previousUser = mCurrentUser;
+ mCurrentUser = null;
+ for (int i = 0; i < mAdapter.getCount(); i++) {
+ UserSwitcherController.UserRecord r = mAdapter.getItem(i);
+ if (r.isCurrent) {
+ mCurrentUser = r;
+ return !mCurrentUser.equals(previousUser);
+ }
+ }
+ return mCurrentUser == null && previousUser != null;
+ }
+
+ /**
+ * @param forceUpdate whether to update view even if current user did not change
+ */
+ private void updateView(boolean forceUpdate) {
+ if (!updateCurrentUser() && !forceUpdate) {
+ return;
+ }
+
+ if (mCurrentUser == null) {
+ mView.setVisibility(View.GONE);
+ return;
+ }
+
+ mView.setVisibility(View.VISIBLE);
+
+ String currentUserName = mCurrentUser.info.name;
+ String contentDescription = null;
+
+ if (!TextUtils.isEmpty(currentUserName)) {
+ contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_user,
+ currentUserName);
+ }
+
+ if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) {
+ mView.setContentDescription(contentDescription);
+ }
+
+ if (mCurrentUser.picture == null) {
+ mView.setDrawableWithBadge(getDrawable(mCurrentUser).mutate(),
+ mCurrentUser.resolveId());
+ } else {
+ int avatarSize =
+ (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
+ Drawable drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
+ drawable.setColorFilter(
+ mCurrentUser.isSwitchToEnabled ? null
+ : mAdapter.getDisabledUserAvatarColorFilter());
+ mView.setDrawableWithBadge(drawable, mCurrentUser.info.id);
+ }
+ }
+
+ Drawable getDrawable(UserSwitcherController.UserRecord item) {
+ Drawable drawable;
+ if (item.isCurrent && item.isGuest) {
+ drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
+ } else {
+ drawable = mAdapter.getIconDrawable(mContext, item);
+ }
+
+ int iconColorRes;
+ if (item.isSwitchToEnabled) {
+ iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
+ } else {
+ iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
+ }
+ drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
+
+ Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
+ drawable = new LayerDrawable(new Drawable[]{bg, drawable});
+ return drawable;
+ }
+
+ /**
+ * Get the height of the keyguard user switcher view when closed.
+ */
+ public int getUserIconHeight() {
+ return mView.getHeight();
+ }
+
+ /**
+ * Set the visibility of the user avatar view based on some new state.
+ */
+ public void setKeyguardQsUserSwitchVisibility(
+ int statusBarState,
+ boolean keyguardFadingAway,
+ boolean goingToFullShade,
+ int oldStatusBarState) {
+ mKeyguardVisibilityHelper.setViewVisibility(
+ statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
+ }
+
+ /**
+ * Update position of the view with an optional animation
+ */
+ public void updatePosition(int x, int y, boolean animate) {
+ PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate);
+ PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
+ ANIMATION_PROPERTIES, animate);
+ }
+
+ /**
+ * Set keyguard user avatar view alpha.
+ */
+ public void setAlpha(float alpha) {
+ if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+ mView.setAlpha(alpha);
+ }
+ }
+
+ /**
+ * Set the amount (ratio) that the device has transitioned to doze.
+ *
+ * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+ */
+ private void setDarkAmount(float darkAmount) {
+ boolean isAwake = darkAmount != 0;
+ if (darkAmount == mDarkAmount) {
+ return;
+ }
+ mDarkAmount = darkAmount;
+ mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
+ }
+
+ private boolean isListAnimating() {
+ return mKeyguardVisibilityHelper.isVisibilityAnimating();
+ }
+
+ private void openQsUserPanel() {
+ mNotificationPanelViewController.expandWithQsDetail(mUserDetailAdapter);
+ }
+
+ public void setNotificationPanelViewController(
+ NotificationPanelViewController notificationPanelViewController) {
+ mNotificationPanelViewController = notificationPanelViewController;
+ }
+
+ class KeyguardUserDetailAdapter extends UserSwitcherController.UserDetailAdapter {
+ KeyguardUserDetailAdapter(UserSwitcherController userSwitcherController, Context context,
+ UiEventLogger uiEventLogger) {
+ super(userSwitcherController, context, uiEventLogger);
+ }
+
+ @Override
+ public boolean shouldAnimate() {
+ return false;
+ }
+
+ @Override
+ public boolean onDoneButtonClicked() {
+ if (mNotificationPanelViewController != null) {
+ mNotificationPanelViewController.animateCloseQs(true /* animateAway */);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 019ab47..d4029e64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -121,6 +121,7 @@
private Intent mSecondaryUserServiceIntent;
private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
private final UiEventLogger mUiEventLogger;
+ public final DetailAdapter mUserDetailAdapter;
@Inject
public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
@@ -131,6 +132,7 @@
mBroadcastDispatcher = broadcastDispatcher;
mActivityTaskManager = activityTaskManager;
mUiEventLogger = uiEventLogger;
+ mUserDetailAdapter = new UserDetailAdapter(this, mContext, mUiEventLogger);
if (!UserManager.isGuestUserEphemeral()) {
mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
}
@@ -781,9 +783,20 @@
}
}
- public final DetailAdapter userDetailAdapter = new DetailAdapter() {
+ public static class UserDetailAdapter implements DetailAdapter {
private final Intent USER_SETTINGS_INTENT = new Intent(Settings.ACTION_USER_SETTINGS);
+ private final UserSwitcherController mUserSwitcherController;
+ private final Context mContext;
+ private final UiEventLogger mUiEventLogger;
+
+ UserDetailAdapter(UserSwitcherController userSwitcherController, Context context,
+ UiEventLogger uiEventLogger) {
+ mUserSwitcherController = userSwitcherController;
+ mContext = context;
+ mUiEventLogger = uiEventLogger;
+ }
+
@Override
public CharSequence getTitle() {
return mContext.getString(R.string.quick_settings_user_title);
@@ -794,7 +807,7 @@
UserDetailView v;
if (!(convertView instanceof UserDetailView)) {
v = UserDetailView.inflate(context, parent, false);
- v.createAndSetAdapter(UserSwitcherController.this, mUiEventLogger);
+ v.createAndSetAdapter(mUserSwitcherController, mUiEventLogger);
} else {
v = (UserDetailView) convertView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index ec61db5..8505703 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -366,7 +366,7 @@
switch (args[i]) {
case "enable-text": {
String[] groups = Arrays.copyOfRange(args, i + 1, args.length);
- int result = protoLogImpl.startTextLogging(mContext, groups, pw);
+ int result = protoLogImpl.startTextLogging(groups, pw);
if (result == 0) {
pw.println("Starting logging on groups: " + Arrays.toString(groups));
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 449db61..fba0b00 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -20,7 +20,6 @@
import android.animation.AnimationHandler;
import android.app.ActivityTaskManager;
-import android.app.IActivityManager;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
@@ -73,7 +72,6 @@
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
-import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -211,8 +209,8 @@
@Provides
static SizeCompatUIController provideSizeCompatUIController(Context context,
DisplayController displayController, DisplayImeController imeController,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new SizeCompatUIController(context, displayController, imeController, mainExecutor);
+ SyncTransactionQueue syncQueue) {
+ return new SizeCompatUIController(context, displayController, imeController, syncQueue);
}
@WMSingleton
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
similarity index 79%
rename from packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index aa4122f..d3f9d641 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -54,7 +54,6 @@
import android.testing.TestableLooper;
import android.text.TextUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -74,7 +73,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class CarrierTextControllerTest extends SysuiTestCase {
+public class CarrierTextManagerTest extends SysuiTestCase {
private static final CharSequence SEPARATOR = " \u2014 ";
private static final CharSequence INVALID_CARD_TEXT = "Invalid card";
@@ -95,7 +94,9 @@
@Mock
private WifiManager mWifiManager;
@Mock
- private CarrierTextController.CarrierTextCallback mCarrierTextCallback;
+ private WakefulnessLifecycle mWakefulnessLifecycle;
+ @Mock
+ private CarrierTextManager.CarrierTextCallback mCarrierTextCallback;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
@@ -104,13 +105,14 @@
private TelephonyManager mTelephonyManager;
@Mock
private SubscriptionManager mSubscriptionManager;
- private CarrierTextController.CarrierTextCallbackInfo mCarrierTextCallbackInfo;
+ private CarrierTextManager.CarrierTextCallbackInfo mCarrierTextCallbackInfo;
- private CarrierTextController mCarrierTextController;
+ private CarrierTextManager mCarrierTextManager;
private TestableLooper mTestableLooper;
+ private Handler mMainHandler;
private Void checkMainThread(InvocationOnMock inv) {
- Looper mainLooper = Dependency.get(Dependency.MAIN_HANDLER).getLooper();
+ Looper mainLooper = mMainHandler.getLooper();
if (!mainLooper.isCurrentThread()) {
fail("This call should be done from the main thread");
}
@@ -122,35 +124,33 @@
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
- mContext.addMockSystemService(WifiManager.class, mWifiManager);
- mContext.addMockSystemService(ConnectivityManager.class, mConnectivityManager);
+ mMainHandler = new Handler(mTestableLooper.getLooper());
when(mConnectivityManager.isNetworkSupported(anyInt())).thenReturn(true);
- mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
- mContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
mContext.getOrCreateTestableResources().addOverride(
R.string.keyguard_sim_error_message_short, INVALID_CARD_TEXT);
mContext.getOrCreateTestableResources().addOverride(
R.string.airplane_mode, AIRPLANE_MODE_TEXT);
- mDependency.injectMockDependency(WakefulnessLifecycle.class);
- mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
- new Handler(mTestableLooper.getLooper()));
- mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
- mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
.registerCallback(any(KeyguardUpdateMonitorCallback.class));
doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
.removeCallback(any(KeyguardUpdateMonitorCallback.class));
- mCarrierTextCallbackInfo = new CarrierTextController.CarrierTextCallbackInfo("",
+ mCarrierTextCallbackInfo = new CarrierTextManager.CarrierTextCallbackInfo("",
new CharSequence[]{}, false, new int[]{});
when(mTelephonyManager.getSupportedModemCount()).thenReturn(3);
when(mTelephonyManager.getActiveModemCount()).thenReturn(3);
- mCarrierTextController = new CarrierTextController(mContext, SEPARATOR, true, true);
+ mCarrierTextManager = new CarrierTextManager.Builder(
+ mContext, mContext.getResources(), mWifiManager, mConnectivityManager,
+ mTelephonyManager, mWakefulnessLifecycle, new Handler(mTestableLooper.getLooper()),
+ mMainHandler, mKeyguardUpdateMonitor)
+ .setShowAirplaneMode(true)
+ .setShowMissingSim(true)
+ .build();
// This should not start listening on any of the real dependencies but will test that
// callbacks in mKeyguardUpdateMonitor are done in the mTestableLooper thread
- mCarrierTextController.setListening(mCarrierTextCallback);
+ mCarrierTextManager.setListening(mCarrierTextCallback);
mTestableLooper.processAllMessages();
}
@@ -165,8 +165,8 @@
TestableLooper testableLooper = new TestableLooper(thread.getLooper());
Handler h = new Handler(testableLooper.getLooper());
h.post(() -> {
- mCarrierTextController.setListening(null);
- mCarrierTextController.setListening(mCarrierTextCallback);
+ mCarrierTextManager.setListening(null);
+ mCarrierTextManager.setListening(mCarrierTextCallback);
});
testableLooper.processAllMessages();
mTestableLooper.processAllMessages();
@@ -183,11 +183,11 @@
when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(TelephonyManager.SIM_STATE_READY);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
@@ -205,12 +205,12 @@
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- mCarrierTextController.mCallback.onSimStateChanged(3, 1,
+ mCarrierTextManager.mCallback.onSimStateChanged(3, 1,
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
@@ -223,7 +223,7 @@
reset(mCarrierTextCallback);
when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
// Update carrier text. It should ignore error state of subId 3 in inactive slotId.
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertEquals("TEST_CARRIER", captor.getValue().carrierText);
@@ -237,9 +237,9 @@
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
// This should not produce an out of bounds error, even though there are no subscriptions
- mCarrierTextController.mCallback.onSimStateChanged(0, -3,
+ mCarrierTextManager.mCallback.onSimStateChanged(0, -3,
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
- mCarrierTextController.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY);
+ mCarrierTextManager.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY);
verify(mCarrierTextCallback, never()).updateCarrierInfo(any());
}
@@ -257,23 +257,23 @@
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
// This should not produce an out of bounds error, even though there are no subscriptions
- mCarrierTextController.mCallback.onSimStateChanged(0, 1,
+ mCarrierTextManager.mCallback.onSimStateChanged(0, 1,
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(
- any(CarrierTextController.CarrierTextCallbackInfo.class));
+ any(CarrierTextManager.CarrierTextCallbackInfo.class));
}
@Test
public void testCallback() {
reset(mCarrierTextCallback);
- mCarrierTextController.postToCallback(mCarrierTextCallbackInfo);
+ mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
mTestableLooper.processAllMessages();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertEquals(mCarrierTextCallbackInfo, captor.getValue());
}
@@ -282,8 +282,8 @@
public void testNullingCallback() {
reset(mCarrierTextCallback);
- mCarrierTextController.postToCallback(mCarrierTextCallbackInfo);
- mCarrierTextController.setListening(null);
+ mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
+ mCarrierTextManager.setListening(null);
// This shouldn't produce NPE
mTestableLooper.processAllMessages();
@@ -301,15 +301,15 @@
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
- CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+ CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
assertEquals(1, info.listOfCarriers.length);
assertEquals(TEST_CARRIER, info.listOfCarriers[0]);
assertEquals(1, info.subscriptionIds.length);
@@ -326,15 +326,15 @@
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
- CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+ CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
assertEquals(1, info.listOfCarriers.length);
assertTrue(info.listOfCarriers[0].toString().contains(TEST_CARRIER));
assertEquals(1, info.subscriptionIds.length);
@@ -346,16 +346,16 @@
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION_NULL);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
- TelephonyManager.SIM_STATE_READY);
+ TelephonyManager.SIM_STATE_READY);
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
@@ -380,11 +380,11 @@
when(ss.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
mKeyguardUpdateMonitor.mServiceStates.put(TEST_SUBSCRIPTION_NULL.getSubscriptionId(), ss);
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
@@ -407,15 +407,15 @@
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
new ArrayList<>());
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
- CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+ CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
assertEquals(0, info.listOfCarriers.length);
assertEquals(0, info.subscriptionIds.length);
@@ -428,16 +428,16 @@
list.add(TEST_SUBSCRIPTION);
list.add(TEST_SUBSCRIPTION);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
- TelephonyManager.SIM_STATE_READY);
+ TelephonyManager.SIM_STATE_READY);
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
@@ -458,11 +458,11 @@
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
@@ -483,11 +483,11 @@
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
@@ -509,11 +509,11 @@
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
mTestableLooper.processAllMessages();
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index c2ade81..d67fe6d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -69,6 +69,8 @@
private KeyguardMessageAreaController mKeyguardMessageAreaController;
@Mock
private LatencyTracker mLatencyTracker;
+ @Mock
+ private EmergencyButtonController mEmergencyButtonController;
private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
@@ -84,7 +86,8 @@
.thenReturn(mKeyguardMessageArea);
mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
- mKeyguardMessageAreaControllerFactory, mLatencyTracker) {
+ mKeyguardMessageAreaControllerFactory, mLatencyTracker,
+ mEmergencyButtonController) {
@Override
void resetState() {
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index 826be2b..4beec57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -54,11 +54,11 @@
public class KeyguardDisplayManagerTest extends SysuiTestCase {
@Mock
+ private NavigationBarController mNavigationBarController;
+ @Mock
private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-
@Mock
private DisplayManager mDisplayManager;
-
@Mock
private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
@@ -76,9 +76,8 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
- mDependency.injectMockDependency(NavigationBarController.class);
- mManager = spy(new KeyguardDisplayManager(mContext, mKeyguardStatusViewComponentFactory,
- mBackgroundExecutor));
+ mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
+ mKeyguardStatusViewComponentFactory, mBackgroundExecutor));
doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index c69ec1a..6d0c640 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -49,6 +49,8 @@
@Mock
private lateinit var mLatencyTracker: LatencyTracker
@Mock
+ private lateinit var mEmergencyButtonController: EmergencyButtonController
+ @Mock
private lateinit
var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
@Mock
@@ -72,7 +74,8 @@
.thenReturn(mKeyguardMessageAreaController)
mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
- mLatencyTracker, mKeyguardMessageAreaControllerFactory)
+ mLatencyTracker, mEmergencyButtonController,
+ mKeyguardMessageAreaControllerFactory)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 31cc7bb..8d1e1a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -67,6 +67,8 @@
private LatencyTracker mLatencyTracker;
@Mock
private LiftToActivateListener mLiftToactivateListener;
+ @Mock
+ private EmergencyButtonController mEmergencyButtonController;
private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
@Mock
private View mDeleteButton;
@@ -92,7 +94,7 @@
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
- mFalsingCollector) {
+ mEmergencyButtonController, mFalsingCollector) {
@Override
public void onResume(int reason) {
super.onResume(reason);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 3b7f4b8..9296d3d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -59,6 +59,10 @@
@Mock
private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory;
@Mock
+ private EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
+ @Mock
+ private EmergencyButtonController mEmergencyButtonController;
+ @Mock
private KeyguardInputViewController mKeyguardInputViewController;
@Mock
private KeyguardInputView mInputView;
@@ -76,9 +80,12 @@
any(KeyguardSecurityCallback.class)))
.thenReturn(mKeyguardInputViewController);
when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+ when(mEmergencyButtonControllerFactory.create(any(EmergencyButton.class)))
+ .thenReturn(mEmergencyButtonController);
mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
- mLayoutInflater, mKeyguardSecurityViewControllerFactory);
+ mLayoutInflater, mKeyguardSecurityViewControllerFactory,
+ mEmergencyButtonControllerFactory);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 0c69ffd..10332bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -100,6 +100,7 @@
return new ImageWallpaper() {
@Override
public Engine onCreateEngine() {
+ onCreate();
return new GLEngine(mHandler) {
@Override
public Context getDisplayContext() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 7fe6827..b8f91b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -17,11 +17,18 @@
package com.android.systemui.controls.dagger
import android.testing.AndroidTestingRunner
+import android.provider.Settings
import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.SecureSettings
import dagger.Lazy
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -29,7 +36,11 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Answers
import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -42,20 +53,29 @@
private lateinit var uiController: ControlsUiController
@Mock
private lateinit var listingController: ControlsListingController
+ @Mock
+ private lateinit var keyguardStateController: KeyguardStateController
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock
+ private lateinit var secureSettings: SecureSettings
+
+ companion object {
+ fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+ }
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ `when`(userTracker.userHandle.identifier).thenReturn(0)
}
@Test
fun testFeatureEnabled() {
- val component = ControlsComponent(
- true,
- Lazy { controller },
- Lazy { uiController },
- Lazy { listingController }
- )
+ val component = setupComponent(true)
assertTrue(component.getControlsController().isPresent)
assertEquals(controller, component.getControlsController().get())
@@ -67,15 +87,80 @@
@Test
fun testFeatureDisabled() {
- val component = ControlsComponent(
- false,
- Lazy { controller },
- Lazy { uiController },
- Lazy { listingController }
- )
+ val component = setupComponent(false)
assertFalse(component.getControlsController().isPresent)
assertFalse(component.getControlsUiController().isPresent)
assertFalse(component.getControlsListingController().isPresent)
}
-}
\ No newline at end of file
+
+ @Test
+ fun testFeatureDisabledVisibility() {
+ val component = setupComponent(false)
+
+ assertEquals(ControlsComponent.Visibility.UNAVAILABLE, component.getVisibility())
+ }
+
+ @Test
+ fun testFeatureEnabledAfterBootVisibility() {
+ `when`(controller.available).thenReturn(true)
+ `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
+ .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT)
+ val component = setupComponent(true)
+
+ assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility())
+ }
+
+ @Test
+ fun testFeatureEnabledAndCannotShowOnLockScreenVisibility() {
+ `when`(controller.available).thenReturn(true)
+ `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
+ .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+ `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+ `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt()))
+ .thenReturn(0)
+ val component = setupComponent(true)
+
+ assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility())
+ }
+
+ @Test
+ fun testFeatureEnabledAndCanShowOnLockScreenVisibility() {
+ `when`(controller.available).thenReturn(true)
+ `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
+ .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+ `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+ `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt()))
+ .thenReturn(1)
+ val component = setupComponent(true)
+
+ assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility())
+ }
+
+ @Test
+ fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() {
+ `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt()))
+ .thenReturn(0)
+ `when`(controller.available).thenReturn(true)
+ `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
+ .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+ `when`(keyguardStateController.isUnlocked()).thenReturn(true)
+ val component = setupComponent(true)
+
+ assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility())
+ }
+
+ private fun setupComponent(enabled: Boolean): ControlsComponent {
+ return ControlsComponent(
+ enabled,
+ mContext,
+ Lazy { controller },
+ Lazy { uiController },
+ Lazy { listingController },
+ lockPatternUtils,
+ keyguardStateController,
+ userTracker,
+ secureSettings
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 069699c..6d8c372 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -173,4 +173,30 @@
mDozeUi.transitionTo(UNINITIALIZED, DOZE);
verify(mHost).setAnimateWakeup(eq(false));
}
+
+ @Test
+ public void controlScreenOffTrueWhenKeyguardNotShowingAndControlUnlockedScreenOff() {
+ when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+ when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+
+ // Tell doze that keyguard is not visible.
+ mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */);
+
+ // Since we're controlling the unlocked screen off animation, verify that we've asked to
+ // control the screen off animation despite being unlocked.
+ verify(mDozeParameters).setControlScreenOffAnimation(true);
+ }
+
+ @Test
+ public void controlScreenOffFalseWhenKeyguardNotShowingAndControlUnlockedScreenOffFalse() {
+ when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+ when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
+
+ // Tell doze that keyguard is not visible.
+ mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */);
+
+ // Since we're not controlling the unlocked screen off animation, verify that we haven't
+ // asked to control the screen off animation since we're unlocked.
+ verify(mDozeParameters).setControlScreenOffAnimation(false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 78ee593..1062fae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -74,12 +74,14 @@
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -135,6 +137,8 @@
@Mock GlobalActionsPanelPlugin.PanelViewController mWalletController;
@Mock private Handler mHandler;
@Mock private UserContextProvider mUserContextProvider;
+ @Mock private UserTracker mUserTracker;
+ @Mock private SecureSettings mSecureSettings;
private ControlsComponent mControlsComponent;
private TestableLooper mTestableLooper;
@@ -149,9 +153,14 @@
when(mUserContextProvider.getUserContext()).thenReturn(mContext);
mControlsComponent = new ControlsComponent(
true,
+ mContext,
() -> mControlsController,
() -> mControlsUiController,
- () -> mControlsListingController
+ () -> mControlsListingController,
+ mLockPatternUtils,
+ mKeyguardStateController,
+ mUserTracker,
+ mSecureSettings
);
mGlobalActionsDialog = new GlobalActionsDialog(mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 00943bc..b8c37fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -18,6 +18,8 @@
import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -27,13 +29,17 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
+import com.android.systemui.R;
import androidx.test.filters.SmallTest;
import com.android.internal.widget.LockPatternUtils;
@@ -45,6 +51,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.DeviceConfigProxy;
@@ -117,4 +124,20 @@
mViewMediator.mViewMediatorCallback.keyguardGone();
verify(mStatusBarKeyguardViewManager).setKeyguardGoingAwayState(eq(false));
}
+
+ @Test
+ public void testIsAnimatingScreenOff() {
+ when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+
+ mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false);
+ mViewMediator.setDozing(true);
+
+ // Mid-doze, we should be animating the screen off animation.
+ mViewMediator.onDozeAmountChanged(0.5f, 0.5f);
+ assertTrue(mViewMediator.isAnimatingScreenOff());
+
+ // Once we're 100% dozed, the screen off animation should be completed.
+ mViewMediator.onDozeAmountChanged(1f, 1f);
+ assertFalse(mViewMediator.isAnimatingScreenOff());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java
new file mode 100644
index 0000000..b3ad6ef
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.people;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.people.ConversationChannel;
+import android.app.people.IPeopleManager;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.testing.AndroidTestingRunner;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.PeopleProviderUtils;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class PeopleProviderTest extends SysuiTestCase {
+ private static final String TAG = "PeopleProviderTest";
+
+ private static final Uri URI = Uri.parse(PeopleProviderUtils.PEOPLE_PROVIDER_SCHEME
+ + PeopleProviderUtils.PEOPLE_PROVIDER_AUTHORITY);
+
+ private static final String SHORTCUT_ID_A = "shortcut_id_a";
+ private static final String PACKAGE_NAME_A = "package_name_a";
+ private static final UserHandle USER_HANDLE_A = UserHandle.of(1);
+ private static final String USERNAME = "username";
+
+ private final ShortcutInfo mShortcutInfo =
+ new ShortcutInfo.Builder(mContext, SHORTCUT_ID_A).setLongLabel(USERNAME).build();
+ private final ConversationChannel mConversationChannel =
+ new ConversationChannel(mShortcutInfo, USER_HANDLE_A.getIdentifier(),
+ null, null, 0L, false);
+
+ private Bundle mExtras = new Bundle();
+
+ @Mock
+ private LauncherApps mLauncherApps;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private IPeopleManager mPeopleManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext.setMockPackageManager(mPackageManager);
+
+ PeopleProviderTestable provider = new PeopleProviderTestable();
+ provider.initializeForTesting(
+ mContext, PeopleProviderUtils.PEOPLE_PROVIDER_AUTHORITY);
+ provider.setLauncherApps(mLauncherApps);
+ provider.setPeopleManager(mPeopleManager);
+ mContext.getContentResolver().addProvider(
+ PeopleProviderUtils.PEOPLE_PROVIDER_AUTHORITY, provider);
+
+ mContext.getTestablePermissions().setPermission(
+ PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION,
+ PackageManager.PERMISSION_GRANTED);
+
+ when(mPeopleManager.getConversation(
+ eq(PACKAGE_NAME_A), eq(USER_HANDLE_A.getIdentifier()), eq(SHORTCUT_ID_A)))
+ .thenReturn(mConversationChannel);
+
+ mExtras.putString(PeopleProviderUtils.EXTRAS_KEY_SHORTCUT_ID, SHORTCUT_ID_A);
+ mExtras.putString(PeopleProviderUtils.EXTRAS_KEY_PACKAGE_NAME, PACKAGE_NAME_A);
+ mExtras.putParcelable(PeopleProviderUtils.EXTRAS_KEY_USER_HANDLE, USER_HANDLE_A);
+ }
+
+ @Test
+ public void testPermissionDeniedThrowsSecurityException() throws RemoteException {
+ mContext.getTestablePermissions().setPermission(
+ PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION,
+ PackageManager.PERMISSION_DENIED);
+ try {
+ Bundle result = mContext.getContentResolver()
+ .call(URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, null);
+ Assert.fail("Call should have failed with SecurityException");
+ } catch (SecurityException e) {
+ } catch (Exception e) {
+ Assert.fail("Call should have failed with SecurityException");
+ }
+ }
+
+ @Test
+ public void testPermissionGrantedNoExtraReturnsNull() throws RemoteException {
+ try {
+ Bundle result = mContext.getContentResolver()
+ .call(URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, null);
+ Assert.fail("Call should have failed with IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ } catch (Exception e) {
+ Assert.fail("Call should have failed with IllegalArgumentException");
+ }
+ }
+
+ @Test
+ public void testPermissionGrantedExtrasReturnsRemoteViews() throws RemoteException {
+ try {
+ Bundle result = mContext.getContentResolver().call(
+ URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, mExtras);
+ RemoteViews views = result.getParcelable(
+ PeopleProviderUtils.RESPONSE_KEY_REMOTE_VIEWS);
+ assertThat(views).isNotNull();
+ } catch (Exception e) {
+ Assert.fail("Fail " + e);
+ }
+ }
+
+ @Test
+ public void testPermissionGrantedNoConversationForShortcutReturnsNull() throws RemoteException {
+ when(mPeopleManager.getConversation(
+ eq(PACKAGE_NAME_A), eq(USER_HANDLE_A.getIdentifier()), eq(SHORTCUT_ID_A)))
+ .thenReturn(null);
+ try {
+ Bundle result = mContext.getContentResolver().call(
+ URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, mExtras);
+ assertThat(result).isNull();
+ } catch (Exception e) {
+ Assert.fail("Fail " + e);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java
new file mode 100644
index 0000000..ac18934
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.people;
+
+import android.app.people.IPeopleManager;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.ProviderInfo;
+
+public class PeopleProviderTestable extends PeopleProvider {
+
+ public void initializeForTesting(Context context, String authority) {
+ ProviderInfo info = new ProviderInfo();
+ info.authority = authority;
+
+ attachInfoForTesting(context, info);
+ }
+
+ void setLauncherApps(LauncherApps launcherApps) {
+ mLauncherApps = launcherApps;
+ }
+
+ void setPeopleManager(IPeopleManager peopleManager) {
+ mPeopleManager = peopleManager;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index fd0715b..862e374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -443,9 +443,18 @@
@Test
public void testGetNetworkLoggingMessage() {
- assertEquals(null, mFooter.getNetworkLoggingMessage(false));
+ // Test network logging message on a device with a device owner.
+ // Network traffic may be monitored on the device.
+ assertEquals(null, mFooter.getNetworkLoggingMessage(true, false));
assertEquals(mContext.getString(R.string.monitoring_description_management_network_logging),
- mFooter.getNetworkLoggingMessage(true));
+ mFooter.getNetworkLoggingMessage(true, true));
+
+ // Test network logging message on a device with a managed profile owner
+ // Network traffic may be monitored on the work profile.
+ assertEquals(null, mFooter.getNetworkLoggingMessage(false, false));
+ assertEquals(
+ mContext.getString(R.string.monitoring_description_managed_profile_network_logging),
+ mFooter.getNetworkLoggingMessage(false, true));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index 1ec1da4..e855834 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -33,7 +33,7 @@
import androidx.test.filters.SmallTest;
-import com.android.keyguard.CarrierTextController;
+import com.android.keyguard.CarrierTextManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -54,7 +54,7 @@
private QSCarrierGroupController mQSCarrierGroupController;
private NetworkController.SignalCallback mSignalCallback;
- private CarrierTextController.CarrierTextCallback mCallback;
+ private CarrierTextManager.CarrierTextCallback mCallback;
@Mock
private QSCarrierGroup mQSCarrierGroup;
@Mock
@@ -62,9 +62,9 @@
@Mock
private NetworkController mNetworkController;
@Mock
- private CarrierTextController.Builder mCarrierTextControllerBuilder;
+ private CarrierTextManager.Builder mCarrierTextControllerBuilder;
@Mock
- private CarrierTextController mCarrierTextController;
+ private CarrierTextManager mCarrierTextManager;
private TestableLooper mTestableLooper;
@Before
@@ -83,11 +83,11 @@
.thenReturn(mCarrierTextControllerBuilder);
when(mCarrierTextControllerBuilder.setShowMissingSim(anyBoolean()))
.thenReturn(mCarrierTextControllerBuilder);
- when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextController);
+ when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextManager);
doAnswer(invocation -> mCallback = invocation.getArgument(0))
- .when(mCarrierTextController)
- .setListening(any(CarrierTextController.CarrierTextCallback.class));
+ .when(mCarrierTextManager)
+ .setListening(any(CarrierTextManager.CarrierTextCallback.class));
when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext));
when(mQSCarrierGroup.getCarrier1View()).thenReturn(mock(QSCarrier.class));
@@ -113,8 +113,8 @@
(Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0));
// listOfCarriers length 1, subscriptionIds length 1, anySims false
- CarrierTextController.CarrierTextCallbackInfo
- c1 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c1 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
false,
@@ -122,8 +122,8 @@
mCallback.updateCarrierInfo(c1);
// listOfCarriers length 1, subscriptionIds length 1, anySims true
- CarrierTextController.CarrierTextCallbackInfo
- c2 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c2 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
true,
@@ -131,8 +131,8 @@
mCallback.updateCarrierInfo(c2);
// listOfCarriers length 2, subscriptionIds length 2, anySims false
- CarrierTextController.CarrierTextCallbackInfo
- c3 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c3 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
false,
@@ -140,8 +140,8 @@
mCallback.updateCarrierInfo(c3);
// listOfCarriers length 2, subscriptionIds length 2, anySims true
- CarrierTextController.CarrierTextCallbackInfo
- c4 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c4 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
@@ -159,8 +159,8 @@
(Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0));
// listOfCarriers length 2, subscriptionIds length 1, anySims false
- CarrierTextController.CarrierTextCallbackInfo
- c1 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c1 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
false,
@@ -168,8 +168,8 @@
mCallback.updateCarrierInfo(c1);
// listOfCarriers length 2, subscriptionIds length 1, anySims true
- CarrierTextController.CarrierTextCallbackInfo
- c2 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c2 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
@@ -177,8 +177,8 @@
mCallback.updateCarrierInfo(c2);
// listOfCarriers length 1, subscriptionIds length 2, anySims false
- CarrierTextController.CarrierTextCallbackInfo
- c3 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c3 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
false,
@@ -186,8 +186,8 @@
mCallback.updateCarrierInfo(c3);
// listOfCarriers length 1, subscriptionIds length 2, anySims true
- CarrierTextController.CarrierTextCallbackInfo
- c4 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c4 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
true,
@@ -203,8 +203,8 @@
when(spiedCarrierGroupController.getSlotIndex(anyInt())).thenReturn(
SubscriptionManager.INVALID_SIM_SLOT_INDEX);
- CarrierTextController.CarrierTextCallbackInfo
- c4 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c4 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
@@ -223,8 +223,8 @@
@Test
public void testNoEmptyVisibleView_airplaneMode() {
- CarrierTextController.CarrierTextCallbackInfo
- info = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ info = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index b7b9678..d3dbe2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -25,6 +25,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
@@ -36,15 +37,19 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Answers
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
@@ -91,6 +96,15 @@
private lateinit var testableLooper: TestableLooper
private lateinit var tile: DeviceControlsTile
+ @Mock
+ private lateinit var keyguardStateController: KeyguardStateController
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock
+ private lateinit var secureSettings: SecureSettings
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -101,9 +115,14 @@
controlsComponent = ControlsComponent(
true,
+ mContext,
{ controlsController },
{ controlsUiController },
- { controlsListingController }
+ { controlsListingController },
+ lockPatternUtils,
+ keyguardStateController,
+ userTracker,
+ secureSettings
)
globalSettings = FakeSettings()
@@ -111,16 +130,20 @@
globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 1)
`when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(true)
+ `when`(userTracker.userHandle.identifier).thenReturn(0)
+
tile = createTile()
}
@Test
fun testAvailable() {
+ `when`(controlsController.available).thenReturn(true)
assertThat(tile.isAvailable).isTrue()
}
@Test
fun testNotAvailableFeature() {
+ `when`(controlsController.available).thenReturn(true)
`when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(false)
assertThat(tile.isAvailable).isFalse()
@@ -130,9 +153,14 @@
fun testNotAvailableControls() {
controlsComponent = ControlsComponent(
false,
+ mContext,
{ controlsController },
{ controlsUiController },
- { controlsListingController }
+ { controlsListingController },
+ lockPatternUtils,
+ keyguardStateController,
+ userTracker,
+ secureSettings
)
tile = createTile()
@@ -264,4 +292,4 @@
globalSettings
)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index fa253e6..421c6f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
@@ -35,6 +37,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.tuner.TunerService;
@@ -57,6 +60,7 @@
@Mock private PowerManager mPowerManager;
@Mock private TunerService mTunerService;
@Mock private BatteryController mBatteryController;
+ @Mock private FeatureFlags mFeatureFlags;
@Before
public void setup() {
@@ -67,7 +71,8 @@
mAlwaysOnDisplayPolicy,
mPowerManager,
mBatteryController,
- mTunerService
+ mTunerService,
+ mFeatureFlags
);
}
@Test
@@ -111,4 +116,37 @@
assertThat(mDozeParameters.getAlwaysOn()).isFalse();
}
+
+ @Test
+ public void testControlUnlockedScreenOffAnimation_dozeAfterScreenOff_false() {
+ when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+ mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
+ when(mFeatureFlags.useNewLockscreenAnimations()).thenReturn(true);
+
+ assertTrue(mDozeParameters.shouldControlUnlockedScreenOff());
+
+ // Trigger the setter for the current value.
+ mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff());
+
+ // We should have asked power manager not to doze after screen off no matter what, since
+ // we're animating and controlling screen off.
+ verify(mPowerManager).setDozeAfterScreenOff(eq(false));
+ }
+
+ @Test
+ public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() {
+ when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+ mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
+ when(mFeatureFlags.useNewLockscreenAnimations()).thenReturn(false);
+
+ assertFalse(mDozeParameters.shouldControlUnlockedScreenOff());
+
+ // Trigger the setter for the current value.
+ mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff());
+
+ // We should have asked power manager to doze only if we're not controlling screen off
+ // normally.
+ verify(mPowerManager).setDozeAfterScreenOff(
+ eq(!mDozeParameters.shouldControlScreenOff()));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 4162884..9b623f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -394,8 +394,8 @@
private void positionClock() {
mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
- 0 /* keyguardUserSwitcherHeight */, mPreferredClockY, mHasCustomClock,
- mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
+ 0 /* userSwitchHeight */, mPreferredClockY, 0 /* userSwitchPreferredY */,
+ mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, mQsExpansion,
mCutoutTopInset);
mClockPositionAlgorithm.run(mClockPosition);
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 d0e7031..d69d2db 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
@@ -48,6 +48,8 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
@@ -58,6 +60,8 @@
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.R;
@@ -70,6 +74,7 @@
import com.android.systemui.doze.DozeLog;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -194,14 +199,24 @@
@Mock
private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
@Mock
- private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponent;
+ private KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
+ @Mock
+ private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
+ @Mock
+ private QSDetailDisplayer mQSDetailDisplayer;
@Mock
private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
@Mock
+ private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
+ @Mock
+ private KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
+ @Mock
private KeyguardClockSwitchController mKeyguardClockSwitchController;
@Mock
private KeyguardStatusViewController mKeyguardStatusViewController;
@Mock
+ private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
+ @Mock
private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@Mock
private AuthController mAuthController;
@@ -216,14 +231,13 @@
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
- private NotificationsQuickSettingsContainer mNotificationContainerParent;
- @Mock
private AmbientState mAmbientState;
@Mock
private UserManager mUserManager;
private NotificationPanelViewController mNotificationPanelViewController;
private View.AccessibilityDelegate mAccessibiltyDelegate;
+ private NotificationsQuickSettingsContainer mNotificationContainerParent;
@Before
public void setup() {
@@ -256,6 +270,7 @@
when(mView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
+ mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
when(mView.findViewById(R.id.notification_container_parent))
.thenReturn(mNotificationContainerParent);
FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
@@ -289,6 +304,10 @@
new ViewGroup.LayoutParams(600, 400));
when(mNotificationStackScrollLayoutController.getLayoutParams()).thenReturn(
new ViewGroup.LayoutParams(600, 400));
+ when(mKeyguardStatusBarViewComponentFactory.build(any()))
+ .thenReturn(mKeyguardStatusBarViewComponent);
+ when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController())
+ .thenReturn(mKeyguardStatusBarViewController);
mNotificationPanelViewController = new NotificationPanelViewController(mView,
mResources,
@@ -305,7 +324,10 @@
mBiometricUnlockController, mStatusBarKeyguardViewManager,
mNotificationStackScrollLayoutController,
mKeyguardStatusViewComponentFactory,
- mKeyguardUserSwitcherComponent,
+ mKeyguardQsUserSwitchComponentFactory,
+ mKeyguardUserSwitcherComponentFactory,
+ mKeyguardStatusBarViewComponentFactory,
+ mQSDetailDisplayer,
mGroupManager,
mNotificationAreaController,
mAuthController,
@@ -432,16 +454,11 @@
@Test
public void testAllChildrenOfNotificationContainer_haveIds() {
- when(mNotificationContainerParent.getChildCount()).thenReturn(2);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
- View view1 = new View(mContext);
- view1.setId(1);
- when(mNotificationContainerParent.getChildAt(0)).thenReturn(view1);
-
- View view2 = mock(View.class);
- when(mNotificationContainerParent.getChildAt(1)).thenReturn(view2);
+ mNotificationContainerParent.addView(newViewWithId(1));
+ mNotificationContainerParent.addView(newViewWithId(View.NO_ID));
mNotificationPanelViewController.updateResources();
@@ -449,6 +466,51 @@
assertThat(mNotificationContainerParent.getChildAt(1).getId()).isNotEqualTo(View.NO_ID);
}
+ @Test
+ public void testSinglePaneShadeLayout_isAlignedToParent() {
+ when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+ mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
+ mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
+
+ mNotificationPanelViewController.updateResources();
+
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mNotificationContainerParent);
+ ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout;
+ ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint(
+ R.id.notification_stack_scroller).layout;
+ assertThat(qsFrameLayout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID);
+ assertThat(stackScrollerLayout.startToStart).isEqualTo(ConstraintSet.PARENT_ID);
+ }
+
+ @Test
+ public void testSplitShadeLayout_isAlignedToGuideline() {
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+ when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+ mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
+ mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
+
+ mNotificationPanelViewController.updateResources();
+
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mNotificationContainerParent);
+ ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout;
+ ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint(
+ R.id.notification_stack_scroller).layout;
+ assertThat(qsFrameLayout.endToEnd).isEqualTo(R.id.qs_edge_guideline);
+ assertThat(stackScrollerLayout.startToStart).isEqualTo(R.id.qs_edge_guideline);
+ }
+
+ private View newViewWithId(int id) {
+ View view = new View(mContext);
+ view.setId(id);
+ ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ // required as cloning ConstraintSet fails if view doesn't have layout params
+ view.setLayoutParams(layoutParams);
+ return view;
+ }
+
private void onTouchEvent(MotionEvent ev) {
mTouchHandler.onTouch(mView, ev);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index cae488a..253460d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -97,6 +97,7 @@
import com.android.systemui.settings.brightness.BrightnessSlider;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -258,6 +259,7 @@
@Mock private DemoModeController mDemoModeController;
@Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
@Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
+ @Mock private FeatureFlags mFeatureFlags;
private ShadeController mShadeController;
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private InitController mInitController = new InitController();
@@ -418,7 +420,8 @@
mNotificationShadeDepthControllerLazy,
mStatusBarTouchableRegionManager,
mNotificationIconAreaController,
- mBrightnessSliderFactory);
+ mBrightnessSliderFactory,
+ mFeatureFlags);
when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
mLockIconContainer);
diff --git a/services/Android.bp b/services/Android.bp
index 61591c2..3154628 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -33,6 +33,7 @@
":services.startop.iorap-sources",
":services.systemcaptions-sources",
":services.translation-sources",
+ ":services.texttospeech-sources",
":services.usage-sources",
":services.usb-sources",
":services.voiceinteraction-sources",
@@ -83,6 +84,7 @@
"services.startop",
"services.systemcaptions",
"services.translation",
+ "services.texttospeech",
"services.usage",
"services.usb",
"services.voiceinteraction",
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index b36626f..0d323fb 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -23,6 +23,9 @@
import android.os.ShellCommand;
import android.os.UserHandle;
+import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
+
import java.io.PrintWriter;
/**
@@ -31,11 +34,13 @@
final class AccessibilityShellCommand extends ShellCommand {
final @NonNull AccessibilityManagerService mService;
final @NonNull SystemActionPerformer mSystemActionPerformer;
+ final @NonNull WindowManagerInternal mWindowManagerService;
AccessibilityShellCommand(@NonNull AccessibilityManagerService service,
@NonNull SystemActionPerformer systemActionPerformer) {
mService = service;
mSystemActionPerformer = systemActionPerformer;
+ mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
}
@Override
@@ -53,6 +58,10 @@
case "call-system-action": {
return runCallSystemAction();
}
+ case "start-trace":
+ return startTrace();
+ case "stop-trace":
+ return stopTrace();
}
return -1;
}
@@ -98,6 +107,20 @@
return -1;
}
+ private int startTrace() {
+ WindowManagerInternal.AccessibilityControllerInternal ac =
+ mWindowManagerService.getAccessibilityController();
+ ac.startTrace();
+ return 0;
+ }
+
+ private int stopTrace() {
+ WindowManagerInternal.AccessibilityControllerInternal ac =
+ mWindowManagerService.getAccessibilityController();
+ ac.stopTrace();
+ return 0;
+ }
+
private Integer parseUserId() {
final String option = getNextOption();
if (option != null) {
@@ -123,5 +146,9 @@
pw.println(" Get whether binding to services provided by instant apps is allowed.");
pw.println(" call-system-action <ACTION_ID>");
pw.println(" Calls the system action with the given action id.");
+ pw.println(" start-trace");
+ pw.println(" Start the debug tracing.");
+ pw.println(" stop-trace");
+ pw.println(" Stop the debug tracing.");
}
-}
\ No newline at end of file
+}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index eff410c..809304b 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2624,12 +2624,26 @@
info.minWidth = value != null ? value.data : 0;
value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight);
info.minHeight = value != null ? value.data : 0;
+
value = sa.peekValue(
com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeWidth);
info.minResizeWidth = value != null ? value.data : info.minWidth;
value = sa.peekValue(
com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeHeight);
info.minResizeHeight = value != null ? value.data : info.minHeight;
+
+ value = sa.peekValue(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_maxResizeWidth);
+ info.maxResizeWidth = value != null ? value.data : 0;
+ value = sa.peekValue(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_maxResizeHeight);
+ info.maxResizeHeight = value != null ? value.data : 0;
+
+ info.targetCellWidth = sa.getInt(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_targetCellWidth, 0);
+ info.targetCellHeight = sa.getInt(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_targetCellHeight, 0);
+
info.updatePeriodMillis = sa.getInt(
com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0);
info.initialLayout = sa.getResourceId(
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 542a260..0725bb2 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -127,10 +127,10 @@
"android.hardware.health-V2.0-java",
"android.hardware.health-V2.1-java",
"android.hardware.light-V1-java",
- "android.hardware.tv.cec-V1.0-java",
+ "android.hardware.tv.cec-V1.1-java",
"android.hardware.weaver-V1.0-java",
- "android.hardware.biometrics.face-V1.1-java",
"android.hardware.biometrics.face-V1-java",
+ "android.hardware.biometrics.face-V1.0-java",
"android.hardware.biometrics.fingerprint-V2.3-java",
"android.hardware.biometrics.fingerprint-V1-java",
"android.hardware.oemlock-V1.0-java",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4b9fed2..154e183 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -44,7 +44,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
@@ -1232,7 +1231,6 @@
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
- netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
netCap.setSingleUid(uid);
return netCap;
@@ -1247,7 +1245,6 @@
int transportType, NetworkRequest.Type type) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
- netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
if (transportType > TYPE_NONE) {
netCap.addTransportType(transportType);
@@ -3587,10 +3584,10 @@
}
private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) {
- handleRegisterNetworkRequest(Collections.singletonList(nri));
+ handleRegisterNetworkRequests(Collections.singleton(nri));
}
- private void handleRegisterNetworkRequest(@NonNull final List<NetworkRequestInfo> nris) {
+ private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
ensureRunningOnConnectivityServiceThread();
for (final NetworkRequestInfo nri : nris) {
mNetworkRequestInfoLogs.log("REGISTER " + nri);
@@ -3721,7 +3718,10 @@
private NetworkRequestInfo getNriForAppRequest(
NetworkRequest request, int callingUid, String requestedOperation) {
- final NetworkRequestInfo nri = mNetworkRequests.get(request);
+ // Looking up the app passed param request in mRequests isn't possible since it may return
+ // null for a request managed by a per-app default. Therefore use getNriForAppRequest() to
+ // do the lookup since that will also find per-app default managed requests.
+ final NetworkRequestInfo nri = getNriForAppRequest(request);
if (nri != null) {
if (Process.SYSTEM_UID != callingUid && Process.NETWORK_STACK_UID != callingUid
@@ -3770,8 +3770,6 @@
if (nri == null) {
return;
}
- // handleReleaseNetworkRequest() paths don't apply to multilayer requests.
- ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequest");
if (VDBG || (DBG && request.isRequest())) {
log("releasing " + request + " (release request)");
}
@@ -3783,7 +3781,6 @@
private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) {
ensureRunningOnConnectivityServiceThread();
-
nri.unlinkDeathRecipient();
for (final NetworkRequest req : nri.mRequests) {
mNetworkRequests.remove(req);
@@ -3806,6 +3803,16 @@
cancelNpiRequests(nri);
}
+ private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
+ for (final NetworkRequestInfo nri : nris) {
+ if (mDefaultRequest == nri) {
+ // Make sure we never remove the default request.
+ continue;
+ }
+ handleRemoveNetworkRequest(nri);
+ }
+ }
+
private void cancelNpiRequests(@NonNull final NetworkRequestInfo nri) {
for (final NetworkRequest req : nri.mRequests) {
cancelNpiRequest(req);
@@ -4976,12 +4983,12 @@
}
}
- private void onUserAdded(int userId) {
- mPermissionMonitor.onUserAdded(userId);
+ private void onUserAdded(UserHandle user) {
+ mPermissionMonitor.onUserAdded(user);
}
- private void onUserRemoved(int userId) {
- mPermissionMonitor.onUserRemoved(userId);
+ private void onUserRemoved(UserHandle user) {
+ mPermissionMonitor.onUserRemoved(user);
}
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -4989,15 +4996,18 @@
public void onReceive(Context context, Intent intent) {
ensureRunningOnConnectivityServiceThread();
final String action = intent.getAction();
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
- // UserId should be filled for below intents, check the existence.
- if (userId == UserHandle.USER_NULL) return;
+ // User should be filled for below intents, check the existence.
+ if (user == null) {
+ Log.wtf(TAG, intent.getAction() + " broadcast without EXTRA_USER");
+ return;
+ }
if (Intent.ACTION_USER_ADDED.equals(action)) {
- onUserAdded(userId);
+ onUserAdded(user);
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
- onUserRemoved(userId);
+ onUserRemoved(user);
} else {
Log.wtf(TAG, "received unexpected intent: " + action);
}
@@ -5110,6 +5120,21 @@
final int mUid;
@Nullable
final String mCallingAttributionTag;
+ // In order to preserve the mapping of NetworkRequest-to-callback when apps register
+ // callbacks using a returned NetworkRequest, the original NetworkRequest needs to be
+ // maintained for keying off of. This is only a concern when the original nri
+ // mNetworkRequests changes which happens currently for apps that register callbacks to
+ // track the default network. In those cases, the nri is updated to have mNetworkRequests
+ // that match the per-app default nri that currently tracks the calling app's uid so that
+ // callbacks are fired at the appropriate time. When the callbacks fire,
+ // mNetworkRequestForCallback will be used so as to preserve the caller's mapping. When
+ // callbacks are updated to key off of an nri vs NetworkRequest, this stops being an issue.
+ // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
+ @NonNull
+ private final NetworkRequest mNetworkRequestForCallback;
+ NetworkRequest getNetworkRequestForCallback() {
+ return mNetworkRequestForCallback;
+ }
/**
* Get the list of UIDs this nri applies to.
@@ -5125,13 +5150,15 @@
NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi,
@Nullable String callingAttributionTag) {
- this(Collections.singletonList(r), pi, callingAttributionTag);
+ this(Collections.singletonList(r), r, pi, callingAttributionTag);
}
NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
- @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) {
+ @NonNull final NetworkRequest requestForCallback, @Nullable final PendingIntent pi,
+ @Nullable String callingAttributionTag) {
+ ensureAllNetworkRequestsHaveType(r);
mRequests = initializeRequests(r);
- ensureAllNetworkRequestsHaveType(mRequests);
+ mNetworkRequestForCallback = requestForCallback;
mPendingIntent = pi;
mMessenger = null;
mBinder = null;
@@ -5143,15 +5170,17 @@
NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m,
@Nullable final IBinder binder, @Nullable String callingAttributionTag) {
- this(Collections.singletonList(r), m, binder, callingAttributionTag);
+ this(Collections.singletonList(r), r, m, binder, callingAttributionTag);
}
- NetworkRequestInfo(@NonNull final List<NetworkRequest> r, @Nullable final Messenger m,
+ NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
+ @NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m,
@Nullable final IBinder binder, @Nullable String callingAttributionTag) {
super();
+ ensureAllNetworkRequestsHaveType(r);
mRequests = initializeRequests(r);
+ mNetworkRequestForCallback = requestForCallback;
mMessenger = m;
- ensureAllNetworkRequestsHaveType(mRequests);
mBinder = binder;
mPid = getCallingPid();
mUid = mDeps.getCallingUid();
@@ -5166,12 +5195,26 @@
}
}
+ NetworkRequestInfo(@NonNull final NetworkRequestInfo nri,
+ @NonNull final List<NetworkRequest> r) {
+ super();
+ ensureAllNetworkRequestsHaveType(r);
+ mRequests = initializeRequests(r);
+ mNetworkRequestForCallback = nri.getNetworkRequestForCallback();
+ mMessenger = nri.mMessenger;
+ mBinder = nri.mBinder;
+ mPid = nri.mPid;
+ mUid = nri.mUid;
+ mPendingIntent = nri.mPendingIntent;
+ mCallingAttributionTag = nri.mCallingAttributionTag;
+ }
+
NetworkRequestInfo(@NonNull final NetworkRequest r) {
this(Collections.singletonList(r));
}
NetworkRequestInfo(@NonNull final List<NetworkRequest> r) {
- this(r, null /* pi */, null /* callingAttributionTag */);
+ this(r, r.get(0), null /* pi */, null /* callingAttributionTag */);
}
// True if this NRI is being satisfied. It also accounts for if the nri has its satisifer
@@ -5330,7 +5373,8 @@
// If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities}
// is unused and will be replaced by ones appropriate for the caller.
// This allows callers to keep track of the default network for their app.
- networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid);
+ networkCapabilities = copyDefaultNetworkCapabilitiesForUid(
+ defaultNc, callingUid, callingPackageName);
enforceAccessPermission();
break;
case TRACK_SYSTEM_DEFAULT:
@@ -5369,10 +5413,10 @@
}
ensureValid(networkCapabilities);
- NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
+ final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId(), reqType);
- NetworkRequestInfo nri =
- new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag);
+ final NetworkRequestInfo nri = getNriToRegister(
+ networkRequest, messenger, binder, callingAttributionTag);
if (DBG) log("requestNetwork for " + nri);
// For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
@@ -5394,6 +5438,30 @@
return networkRequest;
}
+ /**
+ * Return the nri to be used when registering a network request. Specifically, this is used with
+ * requests registered to track the default request. If there is currently a per-app default
+ * tracking the app requestor, then we need to create a version of this nri that mirrors that of
+ * the tracking per-app default so that callbacks are sent to the app requestor appropriately.
+ * @param nr the network request for the nri.
+ * @param msgr the messenger for the nri.
+ * @param binder the binder for the nri.
+ * @param callingAttributionTag the calling attribution tag for the nri.
+ * @return the nri to register.
+ */
+ private NetworkRequestInfo getNriToRegister(@NonNull final NetworkRequest nr,
+ @Nullable final Messenger msgr, @Nullable final IBinder binder,
+ @Nullable String callingAttributionTag) {
+ final List<NetworkRequest> requests;
+ if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) {
+ requests = copyDefaultNetworkRequestsForUid(
+ nr.getRequestorUid(), nr.getRequestorPackageName());
+ } else {
+ requests = Collections.singletonList(nr);
+ }
+ return new NetworkRequestInfo(requests, nr, msgr, binder, callingAttributionTag);
+ }
+
private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
String callingPackageName, String callingAttributionTag) {
if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
@@ -5687,6 +5755,102 @@
}
/**
+ * Return the default network request currently tracking the given uid.
+ * @param uid the uid to check.
+ * @return the NetworkRequestInfo tracking the given uid.
+ */
+ @NonNull
+ private NetworkRequestInfo getDefaultRequestTrackingUid(@NonNull final int uid) {
+ for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
+ if (nri == mDefaultRequest) {
+ continue;
+ }
+ // Checking the first request is sufficient as only multilayer requests will have more
+ // than one request and for multilayer, all requests will track the same uids.
+ if (nri.mRequests.get(0).networkCapabilities.appliesToUid(uid)) {
+ return nri;
+ }
+ }
+ return mDefaultRequest;
+ }
+
+ /**
+ * Get a copy of the network requests of the default request that is currently tracking the
+ * given uid.
+ * @param requestorUid the uid to check the default for.
+ * @param requestorPackageName the requestor's package name.
+ * @return a copy of the default's NetworkRequest that is tracking the given uid.
+ */
+ @NonNull
+ private List<NetworkRequest> copyDefaultNetworkRequestsForUid(
+ @NonNull final int requestorUid, @NonNull final String requestorPackageName) {
+ return copyNetworkRequestsForUid(
+ getDefaultRequestTrackingUid(requestorUid).mRequests,
+ requestorUid, requestorPackageName);
+ }
+
+ /**
+ * Copy the given nri's NetworkRequest collection.
+ * @param requestsToCopy the NetworkRequest collection to be copied.
+ * @param requestorUid the uid to set on the copied collection.
+ * @param requestorPackageName the package name to set on the copied collection.
+ * @return the copied NetworkRequest collection.
+ */
+ @NonNull
+ private List<NetworkRequest> copyNetworkRequestsForUid(
+ @NonNull final List<NetworkRequest> requestsToCopy, @NonNull final int requestorUid,
+ @NonNull final String requestorPackageName) {
+ final List<NetworkRequest> requests = new ArrayList<>();
+ for (final NetworkRequest nr : requestsToCopy) {
+ requests.add(new NetworkRequest(copyDefaultNetworkCapabilitiesForUid(
+ nr.networkCapabilities, requestorUid, requestorPackageName),
+ nr.legacyType, nextNetworkRequestId(), nr.type));
+ }
+ return requests;
+ }
+
+ @NonNull
+ private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid(
+ @NonNull final NetworkCapabilities netCapToCopy, @NonNull final int requestorUid,
+ @NonNull final String requestorPackageName) {
+ final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy);
+ netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
+ netCap.setSingleUid(requestorUid);
+ netCap.setUids(new ArraySet<>());
+ restrictRequestUidsForCallerAndSetRequestorInfo(
+ netCap, requestorUid, requestorPackageName);
+ return netCap;
+ }
+
+ /**
+ * Get the nri that is currently being tracked for callbacks by per-app defaults.
+ * @param nr the network request to check for equality against.
+ * @return the nri if one exists, null otherwise.
+ */
+ @Nullable
+ private NetworkRequestInfo getNriForAppRequest(@NonNull final NetworkRequest nr) {
+ for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (nri.getNetworkRequestForCallback().equals(nr)) {
+ return nri;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Check if an nri is currently being managed by per-app default networking.
+ * @param nri the nri to check.
+ * @return true if this nri is currently being managed by per-app default networking.
+ */
+ private boolean isPerAppTrackedNri(@NonNull final NetworkRequestInfo nri) {
+ // nri.mRequests.get(0) is only different from the original request filed in
+ // nri.getNetworkRequestForCallback() if nri.mRequests was changed by per-app default
+ // functionality therefore if these two don't match, it means this particular nri is
+ // currently being managed by a per-app default.
+ return nri.getNetworkRequestForCallback() != nri.mRequests.get(0);
+ }
+
+ /**
* Determine if an nri is a managed default request that disallows default networking.
* @param nri the request to evaluate
* @return true if device-default networking is disallowed
@@ -6764,13 +6928,9 @@
return;
}
Bundle bundle = new Bundle();
- // In the case of multi-layer NRIs, the first request is not necessarily the one that
- // is satisfied. This is vexing, but the ConnectivityManager code that receives this
- // callback is only using the request as a token to identify the callback, so it doesn't
- // matter too much at this point as long as the callback can be found.
// TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
// TODO: check if defensive copies of data is needed.
- final NetworkRequest nrForCallback = new NetworkRequest(nri.mRequests.get(0));
+ final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();
putParcelable(bundle, nrForCallback);
Message msg = Message.obtain();
if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
@@ -8710,7 +8870,7 @@
if (DBG) {
log("set OEM network preferences :" + preference.toString());
}
- final List<NetworkRequestInfo> nris =
+ final ArraySet<NetworkRequestInfo> nris =
new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
updateDefaultNetworksForOemNetworkPreference(nris);
mOemNetworkPreferences = preference;
@@ -8722,27 +8882,88 @@
}
private void updateDefaultNetworksForOemNetworkPreference(
- @NonNull final List<NetworkRequestInfo> nris) {
- ensureRunningOnConnectivityServiceThread();
- clearNonDefaultNetworkAgents();
- addDefaultNetworkRequests(nris);
+ @NonNull final Set<NetworkRequestInfo> nris) {
+ handleRemoveNetworkRequests(mDefaultNetworkRequests);
+ addPerAppDefaultNetworkRequests(nris);
}
- private void clearNonDefaultNetworkAgents() {
- // Copy mDefaultNetworkRequests to iterate and remove elements from it in
- // handleRemoveNetworkRequest() without getting a ConcurrentModificationException.
- final NetworkRequestInfo[] nris =
- mDefaultNetworkRequests.toArray(new NetworkRequestInfo[0]);
+ private void addPerAppDefaultNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
+ ensureRunningOnConnectivityServiceThread();
+ mDefaultNetworkRequests.addAll(nris);
+ final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate =
+ getPerAppCallbackRequestsToUpdate();
+ handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate);
+ final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris);
+ nrisToRegister.addAll(
+ createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate));
+ handleRegisterNetworkRequests(nrisToRegister);
+ }
+
+ /**
+ * All current requests that are tracking the default network need to be assessed as to whether
+ * or not the current set of per-application default requests will be changing their default
+ * network. If so, those requests will need to be updated so that they will send callbacks for
+ * default network changes at the appropriate time. Additionally, those requests tracking the
+ * default that were previously updated by this flow will need to be reassessed.
+ * @return the nris which will need to be updated.
+ */
+ private ArraySet<NetworkRequestInfo> getPerAppCallbackRequestsToUpdate() {
+ final ArraySet<NetworkRequestInfo> defaultCallbackRequests = new ArraySet<>();
+ // Get the distinct nris to check since for multilayer requests, it is possible to have the
+ // same nri in the map's values for each of its NetworkRequest objects.
+ final ArraySet<NetworkRequestInfo> nris = new ArraySet<>(mNetworkRequests.values());
for (final NetworkRequestInfo nri : nris) {
- if (mDefaultRequest != nri) {
- handleRemoveNetworkRequest(nri);
+ // Include this nri if it is currently being tracked.
+ if (isPerAppTrackedNri(nri)) {
+ defaultCallbackRequests.add(nri);
+ continue;
+ }
+ // We only track callbacks for requests tracking the default.
+ if (NetworkRequest.Type.TRACK_DEFAULT != nri.mRequests.get(0).type) {
+ continue;
+ }
+ // Include this nri if it will be tracked by the new per-app default requests.
+ final boolean isNriGoingToBeTracked =
+ getDefaultRequestTrackingUid(nri.mUid) != mDefaultRequest;
+ if (isNriGoingToBeTracked) {
+ defaultCallbackRequests.add(nri);
}
}
+ return defaultCallbackRequests;
}
- private void addDefaultNetworkRequests(@NonNull final List<NetworkRequestInfo> nris) {
- mDefaultNetworkRequests.addAll(nris);
- handleRegisterNetworkRequest(nris);
+ /**
+ * Create nris for those network requests that are currently tracking the default network that
+ * are being controlled by a per-application default.
+ * @param perAppCallbackRequestsForUpdate the baseline network requests to be used as the
+ * foundation when creating the nri. Important items include the calling uid's original
+ * NetworkRequest to be used when mapping callbacks as well as the caller's uid and name. These
+ * requests are assumed to have already been validated as needing to be updated.
+ * @return the Set of nris to use when registering network requests.
+ */
+ private ArraySet<NetworkRequestInfo> createPerAppCallbackRequestsToRegister(
+ @NonNull final ArraySet<NetworkRequestInfo> perAppCallbackRequestsForUpdate) {
+ final ArraySet<NetworkRequestInfo> callbackRequestsToRegister = new ArraySet<>();
+ for (final NetworkRequestInfo callbackRequest : perAppCallbackRequestsForUpdate) {
+ final NetworkRequestInfo trackingNri =
+ getDefaultRequestTrackingUid(callbackRequest.mUid);
+
+ // If this nri is not being tracked, the change it back to an untracked nri.
+ if (trackingNri == mDefaultRequest) {
+ callbackRequestsToRegister.add(new NetworkRequestInfo(
+ callbackRequest,
+ Collections.singletonList(callbackRequest.getNetworkRequestForCallback())));
+ continue;
+ }
+
+ final String requestorPackageName =
+ callbackRequest.mRequests.get(0).getRequestorPackageName();
+ callbackRequestsToRegister.add(new NetworkRequestInfo(
+ callbackRequest,
+ copyNetworkRequestsForUid(
+ trackingNri.mRequests, callbackRequest.mUid, requestorPackageName)));
+ }
+ return callbackRequestsToRegister;
}
/**
@@ -8750,9 +8971,9 @@
*/
@VisibleForTesting
final class OemNetworkRequestFactory {
- List<NetworkRequestInfo> createNrisFromOemNetworkPreferences(
+ ArraySet<NetworkRequestInfo> createNrisFromOemNetworkPreferences(
@NonNull final OemNetworkPreferences preference) {
- final List<NetworkRequestInfo> nris = new ArrayList<>();
+ final ArraySet<NetworkRequestInfo> nris = new ArraySet<>();
final SparseArray<Set<Integer>> uids =
createUidsFromOemNetworkPreferences(preference);
for (int i = 0; i < uids.size(); i++) {
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 41903fc..67f6ec9 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -902,10 +902,13 @@
if (registeredObserver != null) {
Iterator<MonitoredPackage> it = failedPackages.iterator();
while (it.hasNext()) {
- VersionedPackage versionedPkg = it.next().mPackage;
- Slog.i(TAG, "Explicit health check failed for package " + versionedPkg);
- registeredObserver.execute(versionedPkg,
- PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+ VersionedPackage versionedPkg = getVersionedPackage(it.next().getName());
+ if (versionedPkg != null) {
+ Slog.i(TAG,
+ "Explicit health check failed for package " + versionedPkg);
+ registeredObserver.execute(versionedPkg,
+ PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+ }
}
}
}
@@ -1342,11 +1345,7 @@
MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) {
- VersionedPackage pkg = getVersionedPackage(name);
- if (pkg == null) {
- return null;
- }
- return new MonitoredPackage(pkg, durationMs, healthCheckDurationMs,
+ return new MonitoredPackage(name, durationMs, healthCheckDurationMs,
hasPassedHealthCheck, mitigationCalls);
}
@@ -1371,7 +1370,7 @@
* instances of this class.
*/
class MonitoredPackage {
- private final VersionedPackage mPackage;
+ private final String mPackageName;
// Times when package failures happen sorted in ascending order
@GuardedBy("mLock")
private final LongArrayQueue mFailureHistory = new LongArrayQueue();
@@ -1399,10 +1398,10 @@
@GuardedBy("mLock")
private long mHealthCheckDurationMs = Long.MAX_VALUE;
- MonitoredPackage(VersionedPackage pkg, long durationMs,
+ MonitoredPackage(String packageName, long durationMs,
long healthCheckDurationMs, boolean hasPassedHealthCheck,
LongArrayQueue mitigationCalls) {
- mPackage = pkg;
+ mPackageName = packageName;
mDurationMs = durationMs;
mHealthCheckDurationMs = healthCheckDurationMs;
mHasPassedHealthCheck = hasPassedHealthCheck;
@@ -1556,7 +1555,7 @@
/** Returns the monitored package name. */
private String getName() {
- return mPackage.getPackageName();
+ return mPackageName;
}
/**
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 0aee780..7f0b962 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -568,7 +568,7 @@
// User may no longer exist or isn't set
continue;
}
- int sensor = parser.getAttributeIndex(null, XML_ATTRIBUTE_SENSOR);
+ int sensor = parser.getAttributeInt(null, XML_ATTRIBUTE_SENSOR);
boolean isEnabled = parser.getAttributeBoolean(null,
XML_ATTRIBUTE_ENABLED);
SparseBooleanArray userIndividualEnabled = individualEnabled.get(
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bedd19b..0b1c115 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -186,6 +186,7 @@
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -5626,6 +5627,23 @@
? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
}
+ @Override
+ public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+ final int modeFlags, IBinder callerToken) {
+ final int size = uris.size();
+ int[] res = new int[size];
+ // Default value DENIED.
+ Arrays.fill(res, PackageManager.PERMISSION_DENIED);
+
+ for (int i = 0; i < size; i++) {
+ final Uri uri = uris.get(i);
+ final int userId = ContentProvider.getUserIdFromUri(uri, mContext.getUserId());
+ res[i] = checkUriPermission(ContentProvider.getUriWithoutUserId(uri), pid, uid,
+ modeFlags, userId, callerToken);
+ }
+ return res;
+ }
+
/**
* @param uri This uri must NOT contain an embedded userId.
* @param userId The userId in which the uri is to be resolved.
@@ -10747,6 +10765,15 @@
ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GRAPHICS];
ss[INDEX_TOTAL_PSS] += dmabufMapped;
}
+
+ // totalDmabufHeapExported is included in totalExportedDmabuf above and hence do not
+ // need to be added to kernelUsed.
+ final long totalDmabufHeapExported = Debug.getDmabufHeapTotalExportedKb();
+ if (totalDmabufHeapExported >= 0) {
+ pw.print("DMA-BUF Heaps: ");
+ pw.println(stringifyKBSize(totalDmabufHeapExported));
+ }
+
final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
if (totalDmabufHeapPool >= 0) {
pw.print("DMA-BUF Heaps pool: ");
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index fc7a476..c8630fa 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1536,6 +1536,14 @@
totalPss -= totalMemtrackGraphics;
totalPss += dmabufMapped;
}
+ // These are included in the totalExportedDmabuf above and hence do not need to be added
+ // to kernelUsed.
+ final long totalExportedDmabufHeap = Debug.getDmabufHeapTotalExportedKb();
+ if (totalExportedDmabufHeap >= 0) {
+ memInfoBuilder.append("DMA-BUF Heap: ");
+ memInfoBuilder.append(stringifyKBSize(totalExportedDmabufHeap));
+ memInfoBuilder.append("\n");
+ }
final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
if (totalDmabufHeapPool >= 0) {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 68a084e..8af1b5be 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -264,8 +264,8 @@
*/
public void playerEvent(int piid, int event, int deviceId, int binderUid) {
if (DEBUG) {
- Log.v(TAG, String.format("playerEvent(piid=%d, deviceId=%d, event=%d)",
- piid, deviceId, event));
+ Log.v(TAG, String.format("playerEvent(piid=%d, deviceId=%d, event=%s)",
+ piid, deviceId, AudioPlaybackConfiguration.playerStateToString(event)));
}
final boolean change;
synchronized(mPlayerLock) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 0b78dd0..a4b3ac5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -46,7 +46,7 @@
/**
* Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
*/
class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 1a7544fc..fc1200a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -41,7 +41,7 @@
/**
* Face-specific enroll client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
*/
public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
@@ -103,18 +103,9 @@
disabledFeatures.add(disabledFeature);
}
- android.hardware.biometrics.face.V1_1.IBiometricsFace daemon11 =
- android.hardware.biometrics.face.V1_1.IBiometricsFace.castFrom(getFreshDaemon());
try {
- final int status;
- if (daemon11 != null) {
- status = daemon11.enroll_1_1(token, mTimeoutSec, disabledFeatures, mSurfaceHandle);
- } else if (mSurfaceHandle == null) {
- status = getFreshDaemon().enroll(token, mTimeoutSec, disabledFeatures);
- } else {
- Slog.e(TAG, "enroll(): surface is only supported in @1.1 HAL");
- status = BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS;
- }
+ final int status = getFreshDaemon().enroll(token, mTimeoutSec, disabledFeatures);
+
if (status != Status.OK) {
onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
index c3d54c2..72c5ee5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
@@ -29,8 +29,7 @@
/**
* Face-specific generateChallenge client supporting the
- * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1}
- * HIDL interfaces.
+ * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
*/
public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiometricsFace> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 722a3b8..b1083d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -33,7 +33,7 @@
/**
* Face-specific getFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
*/
public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
index abfda49..1e3b92d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
@@ -33,8 +33,7 @@
/**
* Face-specific internal cleanup client supporting the
- * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1}
- * HIDL interfaces.
+ * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
*/
class FaceInternalCleanupClient extends InternalCleanupClient<Face, IBiometricsFace> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
index 9a0974b..f2a9afc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
@@ -32,8 +32,7 @@
/**
* Face-specific internal enumerate client supporting the
- * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1}
- * HIDL interfaces.
+ * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
*/
class FaceInternalEnumerateClient extends InternalEnumerateClient<IBiometricsFace> {
private static final String TAG = "FaceInternalEnumerateClient";
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
index acae899..d63791c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
@@ -33,7 +33,7 @@
/**
* Face-specific removal client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
*/
class FaceRemovalClient extends RemovalClient<Face, IBiometricsFace> {
private static final String TAG = "FaceRemovalClient";
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
index 14a4648..9d977d6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
@@ -30,7 +30,7 @@
/**
* Face-specific resetLockout client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
*/
public class FaceResetLockoutClient extends HalClientMonitor<IBiometricsFace> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
index e5edfaf..28580de 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
@@ -27,7 +27,7 @@
/**
* Face-specific revokeChallenge client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
*/
public class FaceRevokeChallengeClient extends RevokeChallengeClient<IBiometricsFace> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
index 6290e00..cc3d8f0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
@@ -33,7 +33,7 @@
/**
* Face-specific setFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
*/
public class FaceSetFeatureClient extends HalClientMonitor<IBiometricsFace> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
index 13bd1c2..4cdb68d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
@@ -18,11 +18,11 @@
import android.annotation.Nullable;
import android.hardware.biometrics.face.V1_0.FaceError;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
import android.hardware.biometrics.face.V1_0.OptionalBool;
import android.hardware.biometrics.face.V1_0.OptionalUint64;
import android.hardware.biometrics.face.V1_0.Status;
-import android.hardware.biometrics.face.V1_1.IBiometricsFace;
import android.os.NativeHandle;
import android.os.RemoteException;
import android.util.Slog;
@@ -129,15 +129,4 @@
return 0;
}
- @Override
- public int enrollRemotely(ArrayList<Byte> hat, int timeoutSec,
- ArrayList<Integer> disabledFeatures) {
- return 0;
- }
-
- @Override
- public int enroll_1_1(ArrayList<Byte> hat, int timeoutSec, ArrayList<Integer> disabledFeatures,
- NativeHandle nativeHandle) {
- return 0;
- }
}
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index e3757df..df83df9 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -16,15 +16,24 @@
package com.android.server.compat;
+import static android.app.compat.PackageOverride.VALUE_DISABLED;
+import static android.app.compat.PackageOverride.VALUE_ENABLED;
+import static android.app.compat.PackageOverride.VALUE_UNDEFINED;
+
import android.annotation.Nullable;
+import android.app.compat.PackageOverride;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.OverrideAllowedState;
import com.android.server.compat.config.Change;
import com.android.server.compat.overrides.ChangeOverrides;
import com.android.server.compat.overrides.OverrideValue;
+import com.android.server.compat.overrides.RawOverrideValue;
import java.util.HashMap;
import java.util.List;
@@ -36,7 +45,7 @@
* <p>A compatibility change has a default setting, determined by the {@code enableAfterTargetSdk}
* and {@code disabled} constructor parameters. If a change is {@code disabled}, this overrides any
* target SDK criteria set. These settings can be overridden for a specific package using
- * {@link #addPackageOverride(String, boolean)}.
+ * {@link #addPackageOverrideInternal(String, boolean)}.
*
* <p>Note, this class is not thread safe so callers must ensure thread safety.
*/
@@ -63,8 +72,8 @@
ChangeListener mListener = null;
- private Map<String, Boolean> mPackageOverrides;
- private Map<String, Boolean> mDeferredOverrides;
+ private Map<String, Boolean> mEvaluatedOverrides;
+ private Map<String, PackageOverride> mRawOverrides;
public CompatChange(long changeId) {
this(changeId, null, -1, -1, false, false, null, false);
@@ -113,18 +122,26 @@
* @param pname Package name to enable the change for.
* @param enabled Whether or not to enable the change.
*/
- void addPackageOverride(String pname, boolean enabled) {
+ private void addPackageOverrideInternal(String pname, boolean enabled) {
if (getLoggingOnly()) {
throw new IllegalArgumentException(
"Can't add overrides for a logging only change " + toString());
}
- if (mPackageOverrides == null) {
- mPackageOverrides = new HashMap<>();
+ if (mEvaluatedOverrides == null) {
+ mEvaluatedOverrides = new HashMap<>();
}
- mPackageOverrides.put(pname, enabled);
+ mEvaluatedOverrides.put(pname, enabled);
notifyListener(pname);
}
+ private void removePackageOverrideInternal(String pname) {
+ if (mEvaluatedOverrides != null) {
+ if (mEvaluatedOverrides.remove(pname) != null) {
+ notifyListener(pname);
+ }
+ }
+ }
+
/**
* Tentatively set the state of this change for a given package name.
* The override will only take effect after that package is installed, if applicable.
@@ -132,17 +149,19 @@
* <p>Note, this method is not thread safe so callers must ensure thread safety.
*
* @param packageName Package name to tentatively enable the change for.
- * @param enabled Whether or not to enable the change.
+ * @param override The package override to be set
*/
- void addPackageDeferredOverride(String packageName, boolean enabled) {
+ void addPackageOverride(String packageName, PackageOverride override,
+ OverrideAllowedState allowedState, Context context) {
if (getLoggingOnly()) {
throw new IllegalArgumentException(
"Can't add overrides for a logging only change " + toString());
}
- if (mDeferredOverrides == null) {
- mDeferredOverrides = new HashMap<>();
+ if (mRawOverrides == null) {
+ mRawOverrides = new HashMap<>();
}
- mDeferredOverrides.put(packageName, enabled);
+ mRawOverrides.put(packageName, override);
+ recheckOverride(packageName, allowedState, context);
}
/**
@@ -157,24 +176,44 @@
* @return {@code true} if the recheck yielded a result that requires invalidating caches
* (a deferred override was consolidated or a regular override was removed).
*/
- boolean recheckOverride(String packageName, boolean allowed) {
- // A deferred override now is allowed by the policy, so promote it to a regular override.
- if (hasDeferredOverride(packageName) && allowed) {
- boolean overrideValue = mDeferredOverrides.remove(packageName);
- addPackageOverride(packageName, overrideValue);
- return true;
+ boolean recheckOverride(String packageName, OverrideAllowedState allowedState,
+ Context context) {
+ boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED);
+
+ Long version = null;
+ try {
+ ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(
+ packageName, 0);
+ version = applicationInfo.longVersionCode;
+ } catch (PackageManager.NameNotFoundException e) {
+ // Do nothing
}
- // A previously set override is no longer allowed by the policy, so make it deferred.
- if (hasOverride(packageName) && !allowed) {
- boolean overrideValue = mPackageOverrides.remove(packageName);
- addPackageDeferredOverride(packageName, overrideValue);
- // Notify because the override was removed.
- notifyListener(packageName);
- return true;
+
+ // If the app is not installed or no longer has raw overrides, evaluate to false
+ if (version == null || !hasRawOverride(packageName) || !allowed) {
+ removePackageOverrideInternal(packageName);
+ return false;
}
- return false;
+
+ // Evaluate the override based on its version
+ int overrideValue = mRawOverrides.get(packageName).evaluate(version);
+ switch (overrideValue) {
+ case VALUE_UNDEFINED:
+ removePackageOverrideInternal(packageName);
+ break;
+ case VALUE_ENABLED:
+ addPackageOverrideInternal(packageName, true);
+ break;
+ case VALUE_DISABLED:
+ addPackageOverrideInternal(packageName, false);
+ break;
+ }
+ return true;
}
+ boolean hasPackageOverride(String pname) {
+ return mRawOverrides != null && mRawOverrides.containsKey(pname);
+ }
/**
* Remove any package override for the given package name, restoring the default behaviour.
*
@@ -182,15 +221,13 @@
*
* @param pname Package name to reset to defaults for.
*/
- void removePackageOverride(String pname) {
- if (mPackageOverrides != null) {
- if (mPackageOverrides.remove(pname) != null) {
- notifyListener(pname);
- }
+ boolean removePackageOverride(String pname, OverrideAllowedState allowedState,
+ Context context) {
+ if (mRawOverrides != null && (mRawOverrides.remove(pname) != null)) {
+ recheckOverride(pname, allowedState, context);
+ return true;
}
- if (mDeferredOverrides != null) {
- mDeferredOverrides.remove(pname);
- }
+ return false;
}
/**
@@ -204,8 +241,8 @@
if (app == null) {
return defaultValue();
}
- if (mPackageOverrides != null && mPackageOverrides.containsKey(app.packageName)) {
- return mPackageOverrides.get(app.packageName);
+ if (mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(app.packageName)) {
+ return mEvaluatedOverrides.get(app.packageName);
}
if (getDisabled()) {
return false;
@@ -223,8 +260,16 @@
* @return {@code true} if the change should be enabled for the package.
*/
boolean willBeEnabled(String packageName) {
- if (hasDeferredOverride(packageName)) {
- return mDeferredOverrides.get(packageName);
+ if (hasRawOverride(packageName)) {
+ int eval = mRawOverrides.get(packageName).evaluateForAllVersions();
+ switch (eval) {
+ case VALUE_ENABLED:
+ return true;
+ case VALUE_DISABLED:
+ return false;
+ case VALUE_UNDEFINED:
+ return defaultValue();
+ }
}
return defaultValue();
}
@@ -243,8 +288,8 @@
* @param packageName name of the package
* @return true if there is such override
*/
- boolean hasOverride(String packageName) {
- return mPackageOverrides != null && mPackageOverrides.containsKey(packageName);
+ private boolean hasOverride(String packageName) {
+ return mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(packageName);
}
/**
@@ -252,65 +297,77 @@
* @param packageName name of the package
* @return true if there is such a deferred override
*/
- boolean hasDeferredOverride(String packageName) {
- return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName);
- }
-
- /**
- * Checks whether a change has any package overrides.
- * @return true if the change has at least one deferred override
- */
- boolean hasAnyPackageOverride() {
- return mDeferredOverrides != null && !mDeferredOverrides.isEmpty();
- }
-
- /**
- * Checks whether a change has any deferred overrides.
- * @return true if the change has at least one deferred override
- */
- boolean hasAnyDeferredOverride() {
- return mPackageOverrides != null && !mPackageOverrides.isEmpty();
+ private boolean hasRawOverride(String packageName) {
+ return mRawOverrides != null && mRawOverrides.containsKey(packageName);
}
void loadOverrides(ChangeOverrides changeOverrides) {
- if (mDeferredOverrides == null) {
- mDeferredOverrides = new HashMap<>();
+ if (mRawOverrides == null) {
+ mRawOverrides = new HashMap<>();
}
- mDeferredOverrides.clear();
- for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
- mDeferredOverrides.put(override.getPackageName(), override.getEnabled());
+ mRawOverrides.clear();
+
+ if (mEvaluatedOverrides == null) {
+ mEvaluatedOverrides = new HashMap<>();
+ }
+ mEvaluatedOverrides.clear();
+
+ // Load deferred overrides for backwards compatibility
+ if (changeOverrides.getDeferred() != null) {
+ for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
+ mRawOverrides.put(override.getPackageName(),
+ new PackageOverride.Builder().setEnabled(
+ override.getEnabled()).build());
+ }
}
- if (mPackageOverrides == null) {
- mPackageOverrides = new HashMap<>();
+ // Load validated overrides. For backwards compatibility, we also add them to raw overrides.
+ if (changeOverrides.getValidated() != null) {
+ for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) {
+ mEvaluatedOverrides.put(override.getPackageName(), override.getEnabled());
+ mRawOverrides.put(override.getPackageName(),
+ new PackageOverride.Builder().setEnabled(
+ override.getEnabled()).build());
+ }
}
- mPackageOverrides.clear();
- for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) {
- mPackageOverrides.put(override.getPackageName(), override.getEnabled());
+
+ // Load raw overrides
+ if (changeOverrides.getRaw() != null) {
+ for (RawOverrideValue override : changeOverrides.getRaw().getRawOverrideValue()) {
+ PackageOverride packageOverride = new PackageOverride.Builder()
+ .setMinVersionCode(override.getMinVersionCode())
+ .setMaxVersionCode(override.getMaxVersionCode())
+ .setEnabled(override.getEnabled())
+ .build();
+ mRawOverrides.put(override.getPackageName(), packageOverride);
+ }
}
}
ChangeOverrides saveOverrides() {
- if (!hasAnyDeferredOverride() && !hasAnyPackageOverride()) {
+ if (mRawOverrides == null || mRawOverrides.isEmpty()) {
return null;
}
ChangeOverrides changeOverrides = new ChangeOverrides();
changeOverrides.setChangeId(getId());
- ChangeOverrides.Deferred deferredOverrides = new ChangeOverrides.Deferred();
- List<OverrideValue> deferredList = deferredOverrides.getOverrideValue();
- if (mDeferredOverrides != null) {
- for (Map.Entry<String, Boolean> entry : mDeferredOverrides.entrySet()) {
- OverrideValue override = new OverrideValue();
+ ChangeOverrides.Raw rawOverrides = new ChangeOverrides.Raw();
+ List<RawOverrideValue> rawList = rawOverrides.getRawOverrideValue();
+ if (mRawOverrides != null) {
+ for (Map.Entry<String, PackageOverride> entry : mRawOverrides.entrySet()) {
+ RawOverrideValue override = new RawOverrideValue();
override.setPackageName(entry.getKey());
- override.setEnabled(entry.getValue());
- deferredList.add(override);
+ override.setMinVersionCode(entry.getValue().getMinVersionCode());
+ override.setMaxVersionCode(entry.getValue().getMaxVersionCode());
+ override.setEnabled(entry.getValue().getEnabled());
+ rawList.add(override);
}
}
- changeOverrides.setDeferred(deferredOverrides);
+ changeOverrides.setRaw(rawOverrides);
+
ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated();
List<OverrideValue> validatedList = validatedOverrides.getOverrideValue();
- if (mPackageOverrides != null) {
- for (Map.Entry<String, Boolean> entry : mPackageOverrides.entrySet()) {
+ if (mEvaluatedOverrides != null) {
+ for (Map.Entry<String, Boolean> entry : mEvaluatedOverrides.entrySet()) {
OverrideValue override = new OverrideValue();
override.setPackageName(entry.getKey());
override.setEnabled(entry.getValue());
@@ -337,11 +394,11 @@
if (getLoggingOnly()) {
sb.append("; loggingOnly");
}
- if (mPackageOverrides != null && mPackageOverrides.size() > 0) {
- sb.append("; packageOverrides=").append(mPackageOverrides);
+ if (mEvaluatedOverrides != null && mEvaluatedOverrides.size() > 0) {
+ sb.append("; packageOverrides=").append(mEvaluatedOverrides);
}
- if (mDeferredOverrides != null && mDeferredOverrides.size() > 0) {
- sb.append("; deferredOverrides=").append(mDeferredOverrides);
+ if (mRawOverrides != null && mRawOverrides.size() > 0) {
+ sb.append("; rawOverrides=").append(mRawOverrides);
}
if (getOverridable()) {
sb.append("; overridable");
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 6b77b9d..422991e 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -17,6 +17,7 @@
package com.android.server.compat;
import android.app.compat.ChangeIdStateCache;
+import android.app.compat.PackageOverride;
import android.compat.Compatibility.ChangeConfig;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -31,6 +32,7 @@
import com.android.internal.compat.AndroidBuildClassifier;
import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.CompatibilityOverrideConfig;
import com.android.internal.compat.IOverrideValidator;
import com.android.internal.compat.OverrideAllowedState;
import com.android.server.compat.config.Change;
@@ -70,11 +72,13 @@
private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
private final OverrideValidatorImpl mOverrideValidator;
+ private Context mContext;
private File mOverridesFile;
@VisibleForTesting
CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this);
+ mContext = context;
}
static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) {
@@ -210,17 +214,33 @@
* @throws IllegalStateException if overriding is not allowed
*/
boolean addOverride(long changeId, String packageName, boolean enabled) {
- boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, enabled);
+ boolean alreadyKnown = addOverrideUnsafe(changeId, packageName,
+ new PackageOverride.Builder().setEnabled(enabled).build());
saveOverrides();
invalidateCache();
return alreadyKnown;
}
/**
- * Unsafe version of {@link #addOverride(long, String, boolean)}.
- * It does not invalidate the cache nor save the overrides.
+ * Overrides the enabled state for a given change and app.
+ *
+ * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
+ *
+ * @param overrides list of overrides to default changes config.
+ * @param packageName app for which the overrides will be applied.
*/
- private boolean addOverrideUnsafe(long changeId, String packageName, boolean enabled) {
+ void addOverrides(CompatibilityOverrideConfig overrides, String packageName) {
+ synchronized (mChanges) {
+ for (Long changeId : overrides.overrides.keySet()) {
+ addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId));
+ }
+ saveOverrides();
+ invalidateCache();
+ }
+ }
+
+ private boolean addOverrideUnsafe(long changeId, String packageName,
+ PackageOverride overrides) {
boolean alreadyKnown = true;
OverrideAllowedState allowedState =
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
@@ -232,17 +252,8 @@
c = new CompatChange(changeId);
addChange(c);
}
- switch (allowedState.state) {
- case OverrideAllowedState.ALLOWED:
- c.addPackageOverride(packageName, enabled);
- break;
- case OverrideAllowedState.DEFERRED_VERIFICATION:
- c.addPackageDeferredOverride(packageName, enabled);
- break;
- default:
- throw new IllegalStateException("Should only be able to override changes that "
- + "are allowed or can be deferred.");
- }
+ c.addPackageOverride(packageName, overrides, allowedState, mContext);
+ invalidateCache();
}
return alreadyKnown;
}
@@ -311,47 +322,20 @@
* It does not invalidate the cache nor save the overrides.
*/
private boolean removeOverrideUnsafe(long changeId, String packageName) {
- boolean overrideExists = false;
synchronized (mChanges) {
CompatChange c = mChanges.get(changeId);
if (c != null) {
- // Always allow removing a deferred override.
- if (c.hasDeferredOverride(packageName)) {
- c.removePackageOverride(packageName);
- overrideExists = true;
- } else if (c.hasOverride(packageName)) {
- // Regular overrides need to pass the policy.
- overrideExists = true;
- OverrideAllowedState allowedState =
- mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+ OverrideAllowedState allowedState =
+ mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+ if (c.hasPackageOverride(packageName)) {
allowedState.enforce(changeId, packageName);
- c.removePackageOverride(packageName);
+ c.removePackageOverride(packageName, allowedState, mContext);
+ invalidateCache();
+ return true;
}
}
}
- return overrideExists;
- }
-
- /**
- * Overrides the enabled state for a given change and app.
- *
- * <p>Note: package overrides are not persistent and will be lost on system or runtime restart.
- *
- * @param overrides list of overrides to default changes config
- * @param packageName app for which the overrides will be applied
- */
- void addOverrides(CompatibilityChangeConfig overrides, String packageName) {
- synchronized (mChanges) {
- for (Long changeId : overrides.enabledChanges()) {
- addOverrideUnsafe(changeId, packageName, true);
- }
- for (Long changeId : overrides.disabledChanges()) {
- addOverrideUnsafe(changeId, packageName, false);
-
- }
- saveOverrides();
- invalidateCache();
- }
+ return false;
}
/**
@@ -402,7 +386,8 @@
int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
for (long changeId : changes) {
- addOverrideUnsafe(changeId, packageName, true);
+ addOverrideUnsafe(changeId, packageName,
+ new PackageOverride.Builder().setEnabled(true).build());
}
saveOverrides();
invalidateCache();
@@ -418,7 +403,8 @@
int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
for (long changeId : changes) {
- addOverrideUnsafe(changeId, packageName, false);
+ addOverrideUnsafe(changeId, packageName,
+ new PackageOverride.Builder().setEnabled(false).build());
}
saveOverrides();
invalidateCache();
@@ -615,8 +601,7 @@
CompatChange c = mChanges.valueAt(idx);
OverrideAllowedState allowedState =
mOverrideValidator.getOverrideAllowedState(c.getId(), packageName);
- boolean allowedOverride = (allowedState.state == OverrideAllowedState.ALLOWED);
- shouldInvalidateCache |= c.recheckOverride(packageName, allowedOverride);
+ shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, mContext);
}
if (shouldInvalidateCache) {
invalidateCache();
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 51ba5f7..d17753f 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -25,6 +25,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.IActivityManager;
+import android.app.compat.PackageOverride;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -43,6 +44,7 @@
import com.android.internal.compat.ChangeReporter;
import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.CompatibilityOverrideConfig;
import com.android.internal.compat.IOverrideValidator;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.util.DumpUtils;
@@ -51,6 +53,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
/**
* System server internal API for gating and reporting compatibility changes.
@@ -161,6 +165,22 @@
@Override
public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
checkCompatChangeOverridePermission();
+ Map<Long, PackageOverride> overridesMap = new HashMap<>();
+ for (long change : overrides.enabledChanges()) {
+ overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
+ }
+ for (long change : overrides.disabledChanges()) {
+ overridesMap.put(change, new PackageOverride.Builder().setEnabled(false)
+ .build());
+ }
+ mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName);
+ killPackage(packageName);
+ }
+
+ @Override
+ public void setOverridesFromInstaller(CompatibilityOverrideConfig overrides,
+ String packageName) {
+ checkCompatChangeOverridePermission();
mCompatConfig.addOverrides(overrides, packageName);
killPackage(packageName);
}
@@ -168,7 +188,15 @@
@Override
public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
checkCompatChangeOverridePermission();
- mCompatConfig.addOverrides(overrides, packageName);
+ Map<Long, PackageOverride> overridesMap = new HashMap<>();
+ for (long change : overrides.enabledChanges()) {
+ overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
+ }
+ for (long change : overrides.disabledChanges()) {
+ overridesMap.put(change, new PackageOverride.Builder().setEnabled(false)
+ .build());
+ }
+ mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName);
}
@Override
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 397af7b..61b11a5 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -16,7 +16,6 @@
package com.android.server.connectivity;
-import static android.net.NetworkCapabilities.MAX_TRANSPORT;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -25,15 +24,14 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
-import android.net.ConnectivityManager;
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
+import android.net.metrics.ConnectStats;
import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.DhcpClientEvent;
import android.net.metrics.DhcpErrorEvent;
import android.net.metrics.DnsEvent;
-import android.net.metrics.ConnectStats;
import android.net.metrics.IpManagerEvent;
import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.NetworkEvent;
@@ -41,12 +39,13 @@
import android.net.metrics.ValidationProbeEvent;
import android.net.metrics.WakeupStats;
import android.os.Parcelable;
-import android.util.SparseArray;
import android.util.SparseIntArray;
+
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.Pair;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -361,29 +360,22 @@
return IpConnectivityLogClass.UNKNOWN;
case 1:
int t = Long.numberOfTrailingZeros(transports);
- return transportToLinkLayer(t);
+ return TRANSPORT_LINKLAYER_MAP.get(t, IpConnectivityLogClass.UNKNOWN);
default:
return IpConnectivityLogClass.MULTIPLE;
}
}
- private static int transportToLinkLayer(int transport) {
- if (0 <= transport && transport < TRANSPORT_LINKLAYER_MAP.length) {
- return TRANSPORT_LINKLAYER_MAP[transport];
- }
- return IpConnectivityLogClass.UNKNOWN;
- }
-
- private static final int[] TRANSPORT_LINKLAYER_MAP = new int[MAX_TRANSPORT + 1];
+ private static final SparseIntArray TRANSPORT_LINKLAYER_MAP = new SparseIntArray();
static {
- TRANSPORT_LINKLAYER_MAP[TRANSPORT_CELLULAR] = IpConnectivityLogClass.CELLULAR;
- TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI] = IpConnectivityLogClass.WIFI;
- TRANSPORT_LINKLAYER_MAP[TRANSPORT_BLUETOOTH] = IpConnectivityLogClass.BLUETOOTH;
- TRANSPORT_LINKLAYER_MAP[TRANSPORT_ETHERNET] = IpConnectivityLogClass.ETHERNET;
- TRANSPORT_LINKLAYER_MAP[TRANSPORT_VPN] = IpConnectivityLogClass.UNKNOWN;
- TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI_AWARE] = IpConnectivityLogClass.WIFI_NAN;
- TRANSPORT_LINKLAYER_MAP[TRANSPORT_LOWPAN] = IpConnectivityLogClass.LOWPAN;
- };
+ TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_CELLULAR, IpConnectivityLogClass.CELLULAR);
+ TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI, IpConnectivityLogClass.WIFI);
+ TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_BLUETOOTH, IpConnectivityLogClass.BLUETOOTH);
+ TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_ETHERNET, IpConnectivityLogClass.ETHERNET);
+ TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_VPN, IpConnectivityLogClass.UNKNOWN);
+ TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI_AWARE, IpConnectivityLogClass.WIFI_NAN);
+ TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_LOWPAN, IpConnectivityLogClass.LOWPAN);
+ }
private static int ifnameToLinkLayer(String ifname) {
// Do not try to catch all interface names with regexes, instead only catch patterns that
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 8d21f6f..8bf1886 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -83,9 +83,8 @@
private final INetd mNetd;
private final Dependencies mDeps;
- // Values are User IDs.
@GuardedBy("this")
- private final Set<Integer> mUsers = new HashSet<>();
+ private final Set<UserHandle> mUsers = new HashSet<>();
// Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission.
@GuardedBy("this")
@@ -173,10 +172,7 @@
netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms);
}
- final List<UserHandle> users = mUserManager.getUserHandles(true /* excludeDying */);
- for (UserHandle user : users) {
- mUsers.add(user.getIdentifier());
- }
+ mUsers.addAll(mUserManager.getUserHandles(true /* excludeDying */));
final SparseArray<ArraySet<String>> systemPermission =
SystemConfig.getInstance().getSystemPermissions();
@@ -259,16 +255,15 @@
return array;
}
- private void update(Set<Integer> users, Map<Integer, Boolean> apps, boolean add) {
+ private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) {
List<Integer> network = new ArrayList<>();
List<Integer> system = new ArrayList<>();
for (Entry<Integer, Boolean> app : apps.entrySet()) {
List<Integer> list = app.getValue() ? system : network;
- for (int user : users) {
- final UserHandle handle = UserHandle.of(user);
- if (handle == null) continue;
+ for (UserHandle user : users) {
+ if (user == null) continue;
- list.add(UserHandle.getUid(handle, app.getKey()));
+ list.add(UserHandle.getUid(user, app.getKey()));
}
}
try {
@@ -291,14 +286,10 @@
*
* @hide
*/
- public synchronized void onUserAdded(int user) {
- if (user < 0) {
- loge("Invalid user in onUserAdded: " + user);
- return;
- }
+ public synchronized void onUserAdded(@NonNull UserHandle user) {
mUsers.add(user);
- Set<Integer> users = new HashSet<>();
+ Set<UserHandle> users = new HashSet<>();
users.add(user);
update(users, mApps, true);
}
@@ -310,14 +301,10 @@
*
* @hide
*/
- public synchronized void onUserRemoved(int user) {
- if (user < 0) {
- loge("Invalid user in onUserRemoved: " + user);
- return;
- }
+ public synchronized void onUserRemoved(@NonNull UserHandle user) {
mUsers.remove(user);
- Set<Integer> users = new HashSet<>();
+ Set<UserHandle> users = new HashSet<>();
users.add(user);
update(users, mApps, false);
}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 1b27572..09c0937 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -72,11 +72,11 @@
}
@Override
- public int updateFont(int baseVersion, @NonNull FontUpdateRequest request) {
+ public int updateFontFile(@NonNull FontUpdateRequest request, int baseVersion) {
+ Preconditions.checkArgumentNonnegative(baseVersion);
Objects.requireNonNull(request);
Objects.requireNonNull(request.getFd());
Objects.requireNonNull(request.getSignature());
- Preconditions.checkArgumentNonnegative(baseVersion);
getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
"UPDATE_FONTS permission required.");
try {
@@ -88,6 +88,21 @@
}
}
+ @Override
+ public int updateFontFamily(@NonNull List<FontUpdateRequest> requests, int baseVersion) {
+ Preconditions.checkArgumentNonnegative(baseVersion);
+ Objects.requireNonNull(requests);
+ getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
+ "UPDATE_FONTS permission required.");
+ try {
+ update(baseVersion, requests);
+ return FontManager.RESULT_SUCCESS;
+ } catch (SystemFontException e) {
+ Slog.e(TAG, "Failed to update font family", e);
+ return e.getErrorCode();
+ }
+ }
+
/* package */ static class SystemFontException extends AndroidException {
private final int mErrorCode;
diff --git a/services/core/java/com/android/server/graphics/fonts/TEST_MAPPING b/services/core/java/com/android/server/graphics/fonts/TEST_MAPPING
new file mode 100644
index 0000000..7fbf426
--- /dev/null
+++ b/services/core/java/com/android/server/graphics/fonts/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "UpdatableSystemFontTest"
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 45f2a38..08ddc6d 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -45,6 +45,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Manages set of updatable font files.
@@ -182,8 +183,12 @@
List<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
for (int i = 0; i < fontFamilies.size(); i++) {
FontConfig.FontFamily fontFamily = fontFamilies.get(i);
- // Ignore failures as updated fonts may be obsoleted by system OTA update.
- addFontFamily(fontFamily);
+ try {
+ addFontFamily(fontFamily);
+ } catch (SystemFontException e) {
+ // Ignore failures as updated fonts may be obsoleted by system OTA update.
+ Slog.i(TAG, "Obsolete font family: " + fontFamily.getName());
+ }
}
success = true;
} catch (Throwable t) {
@@ -236,10 +241,7 @@
request.getFd().getFileDescriptor(), request.getSignature());
break;
case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY:
- // TODO: define error code.
- if (!addFontFamily(request.getFontFamily())) {
- throw new IllegalArgumentException("Invalid font family");
- }
+ addFontFamily(request.getFontFamily());
break;
}
}
@@ -495,18 +497,15 @@
* Unnamed font families are used as other named font family's fallback fonts to guarantee a
* complete glyph coverage.
*/
- private boolean addFontFamily(FontConfig.FontFamily fontFamily) {
- if (fontFamily.getName() == null) {
- Slog.e(TAG, "Name is null.");
- return false;
- }
+ private void addFontFamily(FontConfig.FontFamily fontFamily) throws SystemFontException {
+ Objects.requireNonNull(fontFamily.getName());
FontConfig.FontFamily resolvedFontFamily = resolveFontFiles(fontFamily);
if (resolvedFontFamily == null) {
- Slog.e(TAG, "Required fonts are not available");
- return false;
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_FONT_NOT_FOUND,
+ "Required fonts are not available");
}
mFontFamilyMap.put(resolvedFontFamily.getName(), resolvedFontFamily);
- return true;
}
@Nullable
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
index cb47bb2..86a8e36 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
@@ -103,10 +103,11 @@
if (mIsCec20) {
sendSetStreamPath();
}
- if (!mIsCec20 || mTarget.getDevicePowerStatus()
- == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+ int targetPowerStatus = localDevice().mService.getHdmiCecNetwork()
+ .getCecDeviceInfo(getTargetAddress()).getDevicePowerStatus();
+ if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
queryDevicePowerStatus();
- } else if (mTarget.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON) {
+ } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
invokeCallbackAndFinish(HdmiControlManager.RESULT_SUCCESS);
return true;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 6918064..f64efe7 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -411,6 +411,12 @@
case Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED:
notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE);
break;
+ case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
+ notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY);
+ break;
+ case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
+ notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP);
+ break;
}
}
@@ -447,6 +453,8 @@
Global.HDMI_CEC_VERSION,
Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
+ Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
+ Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
};
for (String setting: settings) {
resolver.registerContentObserver(Global.getUriFor(setting), false,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 06bcada..1643ec1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -50,6 +50,7 @@
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.function.Predicate;
@@ -149,6 +150,12 @@
* returns {@code null}.
*/
static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) {
+ HdmiCecController controller = createWithNativeWrapper(service, new NativeWrapperImpl11(),
+ atomWriter);
+ if (controller != null) {
+ return controller;
+ }
+ HdmiLogger.warning("Unable to use cec@1.1");
return createWithNativeWrapper(service, new NativeWrapperImpl(), atomWriter);
}
@@ -312,7 +319,7 @@
}
/**
- * Return CEC version of the device.
+ * Return highest CEC version supported by this device.
*
* <p>Declared as package-private. accessed by {@link HdmiControlService} only.
*/
@@ -745,13 +752,202 @@
boolean nativeIsConnected(int port);
}
- private static final class NativeWrapperImpl implements NativeWrapper,
+ private static final class NativeWrapperImpl11 implements NativeWrapper,
IHwBinder.DeathRecipient, getPhysicalAddressCallback {
- private IHdmiCec mHdmiCec;
+ private android.hardware.tv.cec.V1_1.IHdmiCec mHdmiCec;
+ @Nullable private HdmiCecCallback mCallback;
+
private final Object mLock = new Object();
private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+
+ @Override
+ public String nativeInit() {
+ return (connectToHal() ? mHdmiCec.toString() : null);
+ }
+
+ boolean connectToHal() {
+ try {
+ mHdmiCec = android.hardware.tv.cec.V1_1.IHdmiCec.getService(true);
+ try {
+ mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Couldn't link to death : ", e);
+ }
+ } catch (RemoteException | NoSuchElementException e) {
+ HdmiLogger.error("Couldn't connect to cec@1.1", e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onValues(int result, short addr) {
+ if (result == Result.SUCCESS) {
+ synchronized (mLock) {
+ mPhysicalAddress = new Short(addr).intValue();
+ }
+ }
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) {
+ HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting");
+ connectToHal();
+ // Reconnect the callback
+ if (mCallback != null) {
+ setCallback(mCallback);
+ }
+ }
+ }
+
+ @Override
+ public void setCallback(HdmiCecCallback callback) {
+ mCallback = callback;
+ try {
+ mHdmiCec.setCallback_1_1(new HdmiCecCallback11(callback));
+ } catch (RemoteException e) {
+ HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
+ }
+ }
+
+ @Override
+ public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
+ android.hardware.tv.cec.V1_1.CecMessage message =
+ new android.hardware.tv.cec.V1_1.CecMessage();
+ message.initiator = srcAddress;
+ message.destination = dstAddress;
+ message.body = new ArrayList<>(body.length);
+ for (byte b : body) {
+ message.body.add(b);
+ }
+ try {
+ return mHdmiCec.sendMessage_1_1(message);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to send CEC message : ", e);
+ return SendMessageResult.FAIL;
+ }
+ }
+
+ @Override
+ public int nativeAddLogicalAddress(int logicalAddress) {
+ try {
+ return mHdmiCec.addLogicalAddress_1_1(logicalAddress);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to add a logical address : ", e);
+ return Result.FAILURE_INVALID_ARGS;
+ }
+ }
+
+ @Override
+ public void nativeClearLogicalAddress() {
+ try {
+ mHdmiCec.clearLogicalAddress();
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to clear logical address : ", e);
+ }
+ }
+
+ @Override
+ public int nativeGetPhysicalAddress() {
+ try {
+ mHdmiCec.getPhysicalAddress(this);
+ return mPhysicalAddress;
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get physical address : ", e);
+ return INVALID_PHYSICAL_ADDRESS;
+ }
+ }
+
+ @Override
+ public int nativeGetVersion() {
+ try {
+ return mHdmiCec.getCecVersion();
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get cec version : ", e);
+ return Result.FAILURE_UNKNOWN;
+ }
+ }
+
+ @Override
+ public int nativeGetVendorId() {
+ try {
+ return mHdmiCec.getVendorId();
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get vendor id : ", e);
+ return Result.FAILURE_UNKNOWN;
+ }
+ }
+
+ @Override
+ public HdmiPortInfo[] nativeGetPortInfos() {
+ try {
+ ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos =
+ mHdmiCec.getPortInfo();
+ HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
+ int i = 0;
+ for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
+ hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId,
+ portInfo.type,
+ portInfo.physicalAddress,
+ portInfo.cecSupported,
+ false,
+ portInfo.arcSupported);
+ i++;
+ }
+ return hdmiPortInfo;
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get port information : ", e);
+ return null;
+ }
+ }
+
+ @Override
+ public void nativeSetOption(int flag, boolean enabled) {
+ try {
+ mHdmiCec.setOption(flag, enabled);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to set option : ", e);
+ }
+ }
+
+ @Override
+ public void nativeSetLanguage(String language) {
+ try {
+ mHdmiCec.setLanguage(language);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to set language : ", e);
+ }
+ }
+
+ @Override
+ public void nativeEnableAudioReturnChannel(int port, boolean flag) {
+ try {
+ mHdmiCec.enableAudioReturnChannel(port, flag);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to enable/disable ARC : ", e);
+ }
+ }
+
+ @Override
+ public boolean nativeIsConnected(int port) {
+ try {
+ return mHdmiCec.isConnected(port);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get connection info : ", e);
+ return false;
+ }
+ }
+ }
+
+ private static final class NativeWrapperImpl implements NativeWrapper,
+ IHwBinder.DeathRecipient, getPhysicalAddressCallback {
+ private android.hardware.tv.cec.V1_0.IHdmiCec mHdmiCec;
@Nullable private HdmiCecCallback mCallback;
+ private final Object mLock = new Object();
+ private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+
@Override
public String nativeInit() {
return (connectToHal() ? mHdmiCec.toString() : null);
@@ -765,8 +961,8 @@
} catch (RemoteException e) {
HdmiLogger.error("Couldn't link to death : ", e);
}
- } catch (RemoteException e) {
- HdmiLogger.error("Couldn't get tv.cec service : ", e);
+ } catch (RemoteException | NoSuchElementException e) {
+ HdmiLogger.error("Couldn't connect to cec@1.0", e);
return false;
}
return true;
@@ -776,7 +972,7 @@
public void setCallback(@NonNull HdmiCecCallback callback) {
mCallback = callback;
try {
- mHdmiCec.setCallback(callback);
+ mHdmiCec.setCallback(new HdmiCecCallback10(callback));
} catch (RemoteException e) {
HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
}
@@ -931,20 +1127,69 @@
}
}
- final class HdmiCecCallback extends IHdmiCecCallback.Stub {
+ final class HdmiCecCallback {
+ public void onCecMessage(int initiator, int destination, byte[] body) {
+ runOnServiceThread(
+ () -> handleIncomingCecCommand(initiator, destination, body));
+ }
+
+ public void onHotplugEvent(int portId, boolean connected) {
+ runOnServiceThread(() -> handleHotplug(portId, connected));
+ }
+ }
+
+ private static final class HdmiCecCallback10 extends IHdmiCecCallback.Stub {
+ private final HdmiCecCallback mHdmiCecCallback;
+
+ HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) {
+ mHdmiCecCallback = hdmiCecCallback;
+ }
+
@Override
public void onCecMessage(CecMessage message) throws RemoteException {
byte[] body = new byte[message.body.size()];
for (int i = 0; i < message.body.size(); i++) {
body[i] = message.body.get(i);
}
- runOnServiceThread(
- () -> handleIncomingCecCommand(message.initiator, message.destination, body));
+ mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
}
@Override
public void onHotplugEvent(HotplugEvent event) throws RemoteException {
- runOnServiceThread(() -> handleHotplug(event.portId, event.connected));
+ mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
+ }
+ }
+
+ private static final class HdmiCecCallback11
+ extends android.hardware.tv.cec.V1_1.IHdmiCecCallback.Stub {
+ private final HdmiCecCallback mHdmiCecCallback;
+
+ HdmiCecCallback11(HdmiCecCallback hdmiCecCallback) {
+ mHdmiCecCallback = hdmiCecCallback;
+ }
+
+ @Override
+ public void onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message)
+ throws RemoteException {
+ byte[] body = new byte[message.body.size()];
+ for (int i = 0; i < message.body.size(); i++) {
+ body[i] = message.body.get(i);
+ }
+ mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
+ }
+
+ @Override
+ public void onCecMessage(CecMessage message) throws RemoteException {
+ byte[] body = new byte[message.body.size()];
+ for (int i = 0; i < message.body.size(); i++) {
+ body[i] = message.body.get(i);
+ }
+ mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
+ }
+
+ @Override
+ public void onHotplugEvent(HotplugEvent event) throws RemoteException {
+ mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 382f0f9..d8914b3 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -654,7 +654,9 @@
FOLLOWER_SAFETY_TIMEOUT);
return true;
}
- return false;
+
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
+ return true;
}
@ServiceThreadOnly
@@ -666,9 +668,8 @@
final long upTime = SystemClock.uptimeMillis();
injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
- return true;
}
- return false;
+ return true;
}
static void injectKeyEvent(long time, int action, int keycode, int repeat) {
@@ -788,10 +789,7 @@
}
protected boolean handleRecordTvScreen(HdmiCecMessage message) {
- // The default behavior of <Record TV Screen> is replying <Feature Abort> with
- // "Cannot provide source".
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
- return true;
+ return false;
}
protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index a3e18d1..8d6bcad 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1079,7 +1079,10 @@
message.getSource(),
HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
}
- return super.handleRecordTvScreen(message);
+ // The default behavior of <Record TV Screen> is replying <Feature Abort> with
+ // "Cannot provide source".
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
+ return true;
}
int recorderAddress = message.getSource();
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index c4c0f68..bd577f3 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -371,6 +371,9 @@
int durationMs) {
Slog.i(mTag, "setTemporaryService(" + userId + ") to " + componentName + " for "
+ durationMs + "ms");
+ if (mServiceNameResolver == null) {
+ return;
+ }
enforceCallingPermissionForManagement();
Objects.requireNonNull(componentName);
@@ -404,6 +407,9 @@
enforceCallingPermissionForManagement();
synchronized (mLock) {
+ if (mServiceNameResolver == null) {
+ return false;
+ }
final boolean changed = mServiceNameResolver.setDefaultServiceEnabled(userId, enabled);
if (!changed) {
if (verbose) {
@@ -434,6 +440,10 @@
public final boolean isDefaultServiceEnabled(@UserIdInt int userId) {
enforceCallingPermissionForManagement();
+ if (mServiceNameResolver == null) {
+ return false;
+ }
+
synchronized (mLock) {
return mServiceNameResolver.isDefaultServiceEnabled(userId);
}
@@ -958,6 +968,10 @@
public void onPackageModified(String packageName) {
if (verbose) Slog.v(mTag, "onPackageModified(): " + packageName);
+ if (mServiceNameResolver == null) {
+ return;
+ }
+
final int userId = getChangingUserId();
final String serviceName = mServiceNameResolver.getDefaultServiceName(userId);
if (serviceName == null) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 47cb43e..5eec315 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -78,6 +78,7 @@
import android.os.UserHandle;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
+import android.provider.Settings;
import android.stats.location.LocationStatsEnums;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
@@ -97,6 +98,8 @@
import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.AppOpsHelper;
+import com.android.server.location.injector.DeviceIdleHelper;
+import com.android.server.location.injector.DeviceStationaryHelper;
import com.android.server.location.injector.EmergencyHelper;
import com.android.server.location.injector.Injector;
import com.android.server.location.injector.LocationAttributionHelper;
@@ -108,6 +111,8 @@
import com.android.server.location.injector.SystemAlarmHelper;
import com.android.server.location.injector.SystemAppForegroundHelper;
import com.android.server.location.injector.SystemAppOpsHelper;
+import com.android.server.location.injector.SystemDeviceIdleHelper;
+import com.android.server.location.injector.SystemDeviceStationaryHelper;
import com.android.server.location.injector.SystemEmergencyHelper;
import com.android.server.location.injector.SystemLocationPermissionsHelper;
import com.android.server.location.injector.SystemLocationPowerSaveModeHelper;
@@ -120,6 +125,7 @@
import com.android.server.location.provider.MockLocationProvider;
import com.android.server.location.provider.PassiveLocationProvider;
import com.android.server.location.provider.PassiveLocationProviderManager;
+import com.android.server.location.provider.StationaryThrottlingLocationProvider;
import com.android.server.location.provider.proxy.ProxyLocationProvider;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
@@ -313,6 +319,18 @@
manager.startManager();
if (realProvider != null) {
+
+ // custom logic wrapping all non-passive providers
+ if (manager != mPassiveManager) {
+ boolean enableStationaryThrottling = Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, 1) != 0;
+ if (enableStationaryThrottling) {
+ realProvider = new StationaryThrottlingLocationProvider(manager.getName(),
+ mInjector, realProvider, mEventLog);
+ }
+ }
+
manager.setRealProvider(realProvider);
}
mProviderManagers.add(manager);
@@ -1368,6 +1386,8 @@
private final SystemAppForegroundHelper mAppForegroundHelper;
private final SystemLocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
private final SystemScreenInteractiveHelper mScreenInteractiveHelper;
+ private final SystemDeviceStationaryHelper mDeviceStationaryHelper;
+ private final SystemDeviceIdleHelper mDeviceIdleHelper;
private final LocationAttributionHelper mLocationAttributionHelper;
private final LocationUsageLogger mLocationUsageLogger;
@@ -1391,6 +1411,8 @@
mAppForegroundHelper = new SystemAppForegroundHelper(context);
mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog);
mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
+ mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
+ mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
mLocationUsageLogger = new LocationUsageLogger();
}
@@ -1451,6 +1473,16 @@
}
@Override
+ public DeviceStationaryHelper getDeviceStationaryHelper() {
+ return mDeviceStationaryHelper;
+ }
+
+ @Override
+ public DeviceIdleHelper getDeviceIdleHelper() {
+ return mDeviceIdleHelper;
+ }
+
+ @Override
public LocationAttributionHelper getLocationAttributionHelper() {
return mLocationAttributionHelper;
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index cc510fb..d3c853d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -19,11 +19,11 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.Manifest;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.hardware.contexthub.V1_0.ContextHubMsg;
-import android.hardware.contexthub.V1_0.IContexthub;
import android.hardware.contexthub.V1_0.Result;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubManager;
@@ -39,6 +39,7 @@
import com.android.server.location.ClientBrokerProto;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
@@ -67,7 +68,7 @@
/*
* The proxy to talk to the Context Hub HAL.
*/
- private final IContexthub mContextHubProxy;
+ private final IContextHubWrapper mContextHubProxy;
/*
* The manager that registered this client.
@@ -95,6 +96,12 @@
*/
private boolean mRegistered = true;
+ /**
+ * String containing an attribution tag that was denoted in the {@link Context} of the
+ * creator of this broker. This is used when attributing the permissions usage of the broker.
+ */
+ private @Nullable String mAttributionTag;
+
/*
* Internal interface used to invoke client callbacks.
*/
@@ -176,9 +183,9 @@
}
/* package */ ContextHubClientBroker(
- Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
- ContextHubInfo contextHubInfo, short hostEndPointId,
- IContextHubClientCallback callback) {
+ Context context, IContextHubWrapper contextHubProxy,
+ ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
+ short hostEndPointId, IContextHubClientCallback callback, String attributionTag) {
mContext = context;
mContextHubProxy = contextHubProxy;
mClientManager = clientManager;
@@ -187,15 +194,17 @@
mCallbackInterface = callback;
mPendingIntentRequest = new PendingIntentRequest();
mPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
+ mAttributionTag = attributionTag;
mHasAccessContextHubPermission = context.checkCallingPermission(
Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
}
/* package */ ContextHubClientBroker(
- Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
- ContextHubInfo contextHubInfo, short hostEndPointId, PendingIntent pendingIntent,
- long nanoAppId) {
+ Context context, IContextHubWrapper contextHubProxy,
+ ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
+ short hostEndPointId, PendingIntent pendingIntent, long nanoAppId,
+ String attributionTag) {
mContext = context;
mContextHubProxy = contextHubProxy;
mClientManager = clientManager;
@@ -203,6 +212,7 @@
mHostEndPointId = hostEndPointId;
mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
mPackage = pendingIntent.getCreatorPackage();
+ mAttributionTag = attributionTag;
mHasAccessContextHubPermission = context.checkCallingPermission(
Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
@@ -227,7 +237,10 @@
int contextHubId = mAttachedContextHubInfo.getId();
try {
- result = mContextHubProxy.sendMessageToHub(contextHubId, messageToNanoApp);
+ // TODO(166846988): Fill in host permissions before sending a message.
+ result = mContextHubProxy.sendMessageToHub(
+ contextHubId, messageToNanoApp,
+ new ArrayList<String>() /* hostPermissions */);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
+ contextHubId + ")", e);
@@ -263,6 +276,21 @@
}
/**
+ * Used to override the attribution tag with a newer value if a PendingIntent broker is
+ * retrieved.
+ */
+ /* package */ void setAttributionTag(String attributionTag) {
+ mAttributionTag = attributionTag;
+ }
+
+ /**
+ * @return the attribution tag associated with this broker.
+ */
+ /* package */ String getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ /**
* @return the ID of the context hub this client is attached to
*/
/* package */ int getAttachedContextHubId() {
@@ -508,6 +536,9 @@
String out = "[ContextHubClient ";
out += "endpointID: " + getHostEndPointId() + ", ";
out += "contextHub: " + getAttachedContextHubId() + ", ";
+ if (mAttributionTag != null) {
+ out += "attributionTag: " + getAttributionTag() + ", ";
+ }
if (mPendingIntentRequest.isValid()) {
out += "intentCreatorPackage: " + mPackage + ", ";
out += "nanoAppId: 0x" + Long.toHexString(mPendingIntentRequest.getNanoAppId());
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
index eda89ab..0351edb 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
@@ -20,7 +20,6 @@
import android.app.PendingIntent;
import android.content.Context;
import android.hardware.contexthub.V1_0.ContextHubMsg;
-import android.hardware.contexthub.V1_0.IContexthub;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
@@ -71,7 +70,7 @@
/*
* The proxy to talk to the Context Hub.
*/
- private final IContexthub mContextHubProxy;
+ private final IContextHubWrapper mContextHubProxy;
/*
* A mapping of host endpoint IDs to the ContextHubClientBroker object of registered clients.
@@ -138,7 +137,7 @@
}
/* package */ ContextHubClientManager(
- Context context, IContexthub contextHubProxy) {
+ Context context, IContextHubWrapper contextHubProxy) {
mContext = context;
mContextHubProxy = contextHubProxy;
}
@@ -148,19 +147,21 @@
*
* @param contextHubInfo the object describing the hub this client is attached to
* @param clientCallback the callback interface of the client to register
+ * @param attributionTag an optional attribution tag within the given package
*
* @return the client interface
*
* @throws IllegalStateException if max number of clients have already registered
*/
/* package */ IContextHubClient registerClient(
- ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback) {
+ ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback,
+ String attributionTag) {
ContextHubClientBroker broker;
synchronized (this) {
short hostEndPointId = getHostEndPointId();
broker = new ContextHubClientBroker(
mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
- hostEndPointId, clientCallback);
+ hostEndPointId, clientCallback, attributionTag);
mHostEndPointIdToClientMap.put(hostEndPointId, broker);
mRegistrationRecordDeque.add(
new RegistrationRecord(broker.toString(), ACTION_REGISTERED));
@@ -185,13 +186,15 @@
* @param pendingIntent the callback interface of the client to register
* @param contextHubInfo the object describing the hub this client is attached to
* @param nanoAppId the ID of the nanoapp to receive Intent events for
+ * @param attributionTag an optional attribution tag within the given package
*
* @return the client interface
*
* @throws IllegalStateException if there were too many registered clients at the service
*/
/* package */ IContextHubClient registerClient(
- ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId) {
+ ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId,
+ String attributionTag) {
ContextHubClientBroker broker;
String registerString = "Regenerated";
synchronized (this) {
@@ -201,11 +204,15 @@
short hostEndPointId = getHostEndPointId();
broker = new ContextHubClientBroker(
mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
- hostEndPointId, pendingIntent, nanoAppId);
+ hostEndPointId, pendingIntent, nanoAppId, attributionTag);
mHostEndPointIdToClientMap.put(hostEndPointId, broker);
registerString = "Registered";
mRegistrationRecordDeque.add(
new RegistrationRecord(broker.toString(), ACTION_REGISTERED));
+ } else {
+ // Update the attribution tag to the latest value provided by the client app in
+ // case the app was updated and decided to change its tag.
+ broker.setAttributionTag(attributionTag);
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 785e674..2eafe6a 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -27,10 +27,10 @@
import android.hardware.contexthub.V1_0.AsyncEventType;
import android.hardware.contexthub.V1_0.ContextHub;
import android.hardware.contexthub.V1_0.ContextHubMsg;
-import android.hardware.contexthub.V1_0.HubAppInfo;
-import android.hardware.contexthub.V1_0.IContexthubCallback;
import android.hardware.contexthub.V1_0.Result;
import android.hardware.contexthub.V1_0.TransactionResult;
+import android.hardware.contexthub.V1_2.HubAppInfo;
+import android.hardware.contexthub.V1_2.IContexthubCallback;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubMessage;
import android.hardware.location.ContextHubTransaction;
@@ -53,6 +53,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
+import android.util.Pair;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.DumpUtils;
@@ -137,7 +138,9 @@
@Override
public void handleClientMsg(ContextHubMsg message) {
- handleClientMessageCallback(mContextHubId, message);
+ handleClientMessageCallback(mContextHubId, message,
+ Collections.emptyList() /* nanoappPermissions */,
+ Collections.emptyList() /* messageContentPermissions */);
}
@Override
@@ -156,7 +159,21 @@
}
@Override
- public void handleAppsInfo(ArrayList<HubAppInfo> nanoAppInfoList) {
+ public void handleAppsInfo(
+ ArrayList<android.hardware.contexthub.V1_0.HubAppInfo> nanoAppInfoList) {
+ handleQueryAppsCallback(mContextHubId,
+ ContextHubServiceUtil.toHubAppInfo_1_2(nanoAppInfoList));
+ }
+
+ @Override
+ public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message,
+ ArrayList<String> messageContentPermissions) {
+ handleClientMessageCallback(mContextHubId, message.msg_1_0, message.permissions,
+ messageContentPermissions);
+ }
+
+ @Override
+ public void handleAppsInfo_1_2(ArrayList<HubAppInfo> nanoAppInfoList) {
handleQueryAppsCallback(mContextHubId, nanoAppInfoList);
}
}
@@ -174,30 +191,31 @@
return;
}
- mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper.getHub());
+ mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
mTransactionManager = new ContextHubTransactionManager(
mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
- List<ContextHub> hubList;
+ Pair<List<ContextHub>, List<String>> hubInfo;
try {
- hubList = mContextHubWrapper.getHub().getHubs();
+ hubInfo = mContextHubWrapper.getHubs();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while getting Context Hub info", e);
- hubList = Collections.emptyList();
+ hubInfo = new Pair(Collections.emptyList(), Collections.emptyList());
}
mContextHubIdToInfoMap = Collections.unmodifiableMap(
- ContextHubServiceUtil.createContextHubInfoMap(hubList));
+ ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first));
mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
IContextHubClient client = mClientManager.registerClient(
- contextHubInfo, createDefaultClientCallback(contextHubId));
+ contextHubInfo, createDefaultClientCallback(contextHubId),
+ null /* attributionTag */);
defaultClientMap.put(contextHubId, client);
try {
- mContextHubWrapper.getHub().registerCallback(
+ mContextHubWrapper.registerCallback(
contextHubId, new ContextHubServiceCallback(contextHubId));
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
@@ -596,7 +614,9 @@
* @param contextHubId the ID of the hub the message came from
* @param message the message contents
*/
- private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) {
+ private void handleClientMessageCallback(
+ int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
+ List<String> messageContentPermissions) {
mClientManager.onMessageFromNanoApp(contextHubId, message);
}
@@ -721,7 +741,7 @@
}
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
- return mClientManager.registerClient(contextHubInfo, clientCallback);
+ return mClientManager.registerClient(contextHubInfo, clientCallback, attributionTag);
}
/**
@@ -745,7 +765,8 @@
}
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
- return mClientManager.registerClient(contextHubInfo, pendingIntent, nanoAppId);
+ return mClientManager.registerClient(
+ contextHubInfo, pendingIntent, nanoAppId, attributionTag);
}
/**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index 88ed105..8361253 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -23,8 +23,8 @@
import android.hardware.contexthub.V1_0.ContextHub;
import android.hardware.contexthub.V1_0.ContextHubMsg;
import android.hardware.contexthub.V1_0.HostEndPoint;
-import android.hardware.contexthub.V1_0.HubAppInfo;
import android.hardware.contexthub.V1_0.Result;
+import android.hardware.contexthub.V1_2.HubAppInfo;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.NanoAppBinary;
@@ -161,7 +161,8 @@
ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>();
for (HubAppInfo appInfo : nanoAppInfoList) {
nanoAppStateList.add(
- new NanoAppState(appInfo.appId, appInfo.version, appInfo.enabled));
+ new NanoAppState(appInfo.info_1_0.appId, appInfo.info_1_0.version,
+ appInfo.info_1_0.enabled, appInfo.permissions));
}
return nanoAppStateList;
@@ -255,4 +256,26 @@
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
}
}
+
+ /**
+ * Converts old list of HubAppInfo received from the HAL to V1.2 HubAppInfo objects.
+ *
+ * @param oldInfoList list of V1.0 HubAppInfo objects
+ * @return list of V1.2 HubAppInfo objects
+ */
+ /* package */
+ static ArrayList<HubAppInfo> toHubAppInfo_1_2(
+ ArrayList<android.hardware.contexthub.V1_0.HubAppInfo> oldInfoList) {
+ ArrayList newAppInfo = new ArrayList<HubAppInfo>();
+ for (android.hardware.contexthub.V1_0.HubAppInfo oldInfo : oldInfoList) {
+ HubAppInfo newInfo = new HubAppInfo();
+ newInfo.info_1_0.appId = oldInfo.appId;
+ newInfo.info_1_0.version = oldInfo.version;
+ newInfo.info_1_0.memUsage = oldInfo.memUsage;
+ newInfo.info_1_0.enabled = oldInfo.enabled;
+ newInfo.permissions = new ArrayList<String>();
+ newAppInfo.add(newInfo);
+ }
+ return newAppInfo;
+ }
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index c11e289..c1d63dd 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -16,11 +16,17 @@
package com.android.server.location.contexthub;
import android.annotation.Nullable;
+import android.hardware.contexthub.V1_0.ContextHub;
import android.hardware.contexthub.V1_1.Setting;
import android.hardware.contexthub.V1_1.SettingValue;
+import android.hardware.contexthub.V1_2.IContexthubCallback;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pair;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.NoSuchElementException;
/**
@@ -87,6 +93,24 @@
}
/**
+ * Calls the appropriate getHubs function depending on the HAL version.
+ */
+ public abstract Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException;
+
+ /**
+ * Calls the appropriate registerCallback function depending on the HAL version.
+ */
+ public abstract void registerCallback(
+ int hubId, IContexthubCallback callback) throws RemoteException;
+
+ /**
+ * Calls the appropriate sendMessageToHub function depending on the HAL version.
+ */
+ public abstract int sendMessageToHub(
+ int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
+ ArrayList<String> hostPermissions) throws RemoteException;
+
+ /**
* @return A valid instance of Contexthub HAL 1.0.
*/
public abstract android.hardware.contexthub.V1_0.IContexthub getHub();
@@ -148,6 +172,21 @@
mHub = hub;
}
+ public Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException {
+ return new Pair(mHub.getHubs(), new ArrayList<String>());
+ }
+
+ public void registerCallback(
+ int hubId, IContexthubCallback callback) throws RemoteException {
+ mHub.registerCallback(hubId, callback);
+ }
+
+ public int sendMessageToHub(
+ int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
+ ArrayList<String> hostPermissions) throws RemoteException {
+ return mHub.sendMessageToHub(hubId, message);
+ }
+
public android.hardware.contexthub.V1_0.IContexthub getHub() {
return mHub;
}
@@ -188,6 +227,21 @@
mHub = hub;
}
+ public Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException {
+ return new Pair(mHub.getHubs(), new ArrayList<String>());
+ }
+
+ public void registerCallback(
+ int hubId, IContexthubCallback callback) throws RemoteException {
+ mHub.registerCallback(hubId, callback);
+ }
+
+ public int sendMessageToHub(
+ int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
+ ArrayList<String> hostPermissions) throws RemoteException {
+ return mHub.sendMessageToHub(hubId, message);
+ }
+
public android.hardware.contexthub.V1_0.IContexthub getHub() {
return mHub;
}
@@ -227,13 +281,42 @@
}
}
- private static class ContextHubWrapperV1_2 extends IContextHubWrapper {
- private android.hardware.contexthub.V1_2.IContexthub mHub;
+ private static class ContextHubWrapperV1_2 extends IContextHubWrapper
+ implements android.hardware.contexthub.V1_2.IContexthub.getHubs_1_2Callback {
+ private final android.hardware.contexthub.V1_2.IContexthub mHub;
+
+ private Pair<List<ContextHub>, List<String>> mHubInfo =
+ new Pair<>(Collections.emptyList(), Collections.emptyList());
ContextHubWrapperV1_2(android.hardware.contexthub.V1_2.IContexthub hub) {
mHub = hub;
}
+ @Override
+ public void onValues(ArrayList<ContextHub> hubs, ArrayList<String> supportedPermissions) {
+ mHubInfo = new Pair(hubs, supportedPermissions);
+ }
+
+ public Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException {
+ mHub.getHubs_1_2(this);
+ return mHubInfo;
+ }
+
+ public void registerCallback(
+ int hubId, IContexthubCallback callback) throws RemoteException {
+ mHub.registerCallback_1_2(hubId, callback);
+ }
+
+ public int sendMessageToHub(
+ int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
+ ArrayList<String> hostPermissions) throws RemoteException {
+ android.hardware.contexthub.V1_2.ContextHubMsg newMessage =
+ new android.hardware.contexthub.V1_2.ContextHubMsg();
+ newMessage.msg_1_0 = message;
+ newMessage.permissions = hostPermissions;
+ return mHub.sendMessageToHub_1_2(hubId, newMessage);
+ }
+
public android.hardware.contexthub.V1_0.IContexthub getHub() {
return mHub;
}
diff --git a/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java b/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java
index 60109fe..667fb98 100644
--- a/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java
+++ b/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java
@@ -17,7 +17,7 @@
package com.android.server.location.contexthub;
import android.annotation.Nullable;
-import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.contexthub.V1_2.HubAppInfo;
import android.hardware.location.NanoAppInstanceInfo;
import android.util.Log;
@@ -154,8 +154,8 @@
synchronized void updateCache(int contextHubId, List<HubAppInfo> nanoAppInfoList) {
HashSet<Long> nanoAppIdSet = new HashSet<>();
for (HubAppInfo appInfo : nanoAppInfoList) {
- handleQueryAppEntry(contextHubId, appInfo.appId, appInfo.version);
- nanoAppIdSet.add(appInfo.appId);
+ handleQueryAppEntry(contextHubId, appInfo.info_1_0.appId, appInfo.info_1_0.version);
+ nanoAppIdSet.add(appInfo.info_1_0.appId);
}
Iterator<NanoAppInstanceInfo> iterator = mNanoAppHash.values().iterator();
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 865d41f..dbfd0a5 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -60,7 +60,8 @@
private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
- private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 9;
+ private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 9;
+ private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 10;
@GuardedBy("mAggregateStats")
private final ArrayMap<String, ArrayMap<String, AggregateStats>> mAggregateStats;
@@ -167,6 +168,11 @@
getAggregateStats(provider, identity.getPackageName()).markLocationDelivered();
}
+ /** Logs that a provider has entered or exited stationary throttling. */
+ public void logProviderStationaryThrottled(String provider, boolean throttled) {
+ addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled);
+ }
+
/** Logs that the location power save mode has changed. */
public void logLocationPowerSaveMode(
@LocationPowerSaveMode int locationPowerSaveMode) {
@@ -198,6 +204,9 @@
case EVENT_PROVIDER_DELIVER_LOCATION:
return new ProviderDeliverLocationEvent(timeDelta, (String) args[0],
(Integer) args[1], (CallerIdentity) args[2]);
+ case EVENT_PROVIDER_STATIONARY_THROTTLED:
+ return new ProviderStationaryThrottledEvent(timeDelta, (String) args[0],
+ (Boolean) args[1]);
case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
default:
@@ -332,6 +341,23 @@
}
}
+ private static final class ProviderStationaryThrottledEvent extends ProviderEvent {
+
+ private final boolean mStationaryThrottled;
+
+ private ProviderStationaryThrottledEvent(long timeDelta, String provider,
+ boolean stationaryThrottled) {
+ super(timeDelta, provider);
+ mStationaryThrottled = stationaryThrottled;
+ }
+
+ @Override
+ public String getLogString() {
+ return mProvider + " provider stationary/idle " + (mStationaryThrottled ? "throttled"
+ : "unthrottled");
+ }
+ }
+
private static final class LocationPowerSaveModeEvent extends LogEvent {
@LocationPowerSaveMode
diff --git a/services/core/java/com/android/server/location/injector/DeviceIdleHelper.java b/services/core/java/com/android/server/location/injector/DeviceIdleHelper.java
new file mode 100644
index 0000000..e820b00
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/DeviceIdleHelper.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides accessors and listeners for device idle status.
+ */
+public abstract class DeviceIdleHelper {
+
+ /**
+ * Listener for device stationary status.
+ */
+ public interface DeviceIdleListener {
+ /**
+ * Called when device idle state has changed.
+ */
+ void onDeviceIdleChanged(boolean deviceIdle);
+ }
+
+ private final CopyOnWriteArrayList<DeviceIdleListener> mListeners;
+
+ protected DeviceIdleHelper() {
+ mListeners = new CopyOnWriteArrayList<>();
+ }
+
+ /**
+ * Adds a listener for device idle status.
+ */
+ public final synchronized void addListener(DeviceIdleListener listener) {
+ if (mListeners.add(listener) && mListeners.size() == 1) {
+ registerInternal();
+ }
+ }
+
+ /**
+ * Removes a listener for device idle status.
+ */
+ public final synchronized void removeListener(DeviceIdleListener listener) {
+ if (mListeners.remove(listener) && mListeners.isEmpty()) {
+ unregisterInternal();
+ }
+ }
+
+ protected final void notifyDeviceIdleChanged() {
+ boolean deviceIdle = isDeviceIdle();
+
+ for (DeviceIdleListener listener : mListeners) {
+ listener.onDeviceIdleChanged(deviceIdle);
+ }
+ }
+
+ protected abstract void registerInternal();
+
+ protected abstract void unregisterInternal();
+
+ /**
+ * Returns true if the device is currently idle.
+ */
+ public abstract boolean isDeviceIdle();
+}
diff --git a/services/core/java/com/android/server/location/injector/DeviceStationaryHelper.java b/services/core/java/com/android/server/location/injector/DeviceStationaryHelper.java
new file mode 100644
index 0000000..b77c0f7
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/DeviceStationaryHelper.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import com.android.server.DeviceIdleInternal;
+
+/**
+ * Provides accessors and listeners for device stationary status.
+ */
+public abstract class DeviceStationaryHelper {
+
+ /**
+ * Adds a listener for stationary status. The listener will be immediately invoked with the
+ * current stationary status.
+ */
+ public abstract void addListener(DeviceIdleInternal.StationaryListener listener);
+
+ /**
+ * Removes a listener for stationary status.
+ */
+ public abstract void removeListener(DeviceIdleInternal.StationaryListener listener);
+}
diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java
index 0e157c2..b035118 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -48,6 +48,12 @@
/** Returns a ScreenInteractiveHelper. */
ScreenInteractiveHelper getScreenInteractiveHelper();
+ /** Returns a DeviceStationaryHelper. */
+ DeviceStationaryHelper getDeviceStationaryHelper();
+
+ /** Returns a DeviceIdleHelper. */
+ DeviceIdleHelper getDeviceIdleHelper();
+
/** Returns a LocationAttributionHelper. */
LocationAttributionHelper getLocationAttributionHelper();
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
new file mode 100644
index 0000000..6a89079
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PowerManager;
+
+import com.android.server.FgThread;
+
+/**
+ * Provides accessors and listeners for device stationary state.
+ */
+public class SystemDeviceIdleHelper extends DeviceIdleHelper {
+
+ private final Context mContext;
+ private final PowerManager mPowerManager;
+
+ private @Nullable BroadcastReceiver mReceiver;
+
+ public SystemDeviceIdleHelper(Context context) {
+ mContext = context;
+ mPowerManager = context.getSystemService(PowerManager.class);
+ }
+
+ @Override
+ protected void registerInternal() {
+ if (mReceiver == null) {
+ mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ notifyDeviceIdleChanged();
+ }
+ };
+
+ mContext.registerReceiver(mReceiver,
+ new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
+ FgThread.getHandler());
+ }
+ }
+
+ @Override
+ protected void unregisterInternal() {
+ if (mReceiver != null) {
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+
+ @Override
+ public boolean isDeviceIdle() {
+ return mPowerManager.isDeviceIdleMode();
+ }
+}
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
new file mode 100644
index 0000000..6f0e681
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import com.android.server.DeviceIdleInternal;
+import com.android.server.LocalServices;
+
+import java.util.Objects;
+
+/**
+ * Provides accessors and listeners for device stationary state.
+ */
+public class SystemDeviceStationaryHelper extends DeviceStationaryHelper {
+
+ private final DeviceIdleInternal mDeviceIdle;
+
+ public SystemDeviceStationaryHelper() {
+ mDeviceIdle = Objects.requireNonNull(LocalServices.getService(DeviceIdleInternal.class));
+ }
+
+ @Override
+ public void addListener(DeviceIdleInternal.StationaryListener listener) {
+ mDeviceIdle.registerStationaryListener(listener);
+ }
+
+ @Override
+ public void removeListener(DeviceIdleInternal.StationaryListener listener) {
+ mDeviceIdle.unregisterStationaryListener(listener);
+ }
+}
diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
index e22a014..4e0a0b8 100644
--- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
@@ -337,8 +337,10 @@
@Override
public State setListener(@Nullable Listener listener) {
- return mInternalState.updateAndGet(
- internalState -> internalState.withListener(listener)).state;
+ InternalState oldInternalState = mInternalState.getAndUpdate(
+ internalState -> internalState.withListener(listener));
+ Preconditions.checkState(listener == null || oldInternalState.listener == null);
+ return oldInternalState.state;
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java
new file mode 100644
index 0000000..a3ec867
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.provider;
+
+import android.location.LocationResult;
+import android.location.provider.ProviderRequest;
+import android.os.Bundle;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class for wrapping location providers. Subclasses MUST ensure that
+ * {@link #initializeDelegate()} is invoked before the delegate is used.
+ */
+class DelegateLocationProvider extends AbstractLocationProvider
+ implements AbstractLocationProvider.Listener {
+
+ private final Object mInitializationLock = new Object();
+
+ protected final AbstractLocationProvider mDelegate;
+
+ private boolean mInitialized = false;
+
+ DelegateLocationProvider(Executor executor, AbstractLocationProvider delegate) {
+ super(executor, null, null);
+
+ mDelegate = delegate;
+ }
+
+ /**
+ * This function initializes state from the delegate and allows all other callbacks to be
+ * immediately invoked. If this method is invoked from a subclass constructor, it should be the
+ * last statement in the constructor since it allows the subclass' reference to escape.
+ */
+ protected void initializeDelegate() {
+ synchronized (mInitializationLock) {
+ Preconditions.checkState(!mInitialized);
+ setState(previousState -> mDelegate.getController().setListener(this));
+ mInitialized = true;
+ }
+ }
+
+ // must be invoked in every listener callback to ensure they don't run until initialized
+ protected final void waitForInitialization() {
+ // callbacks can start coming as soon as the setListener() call in initializeDelegate
+ // completes - but we can't allow any to proceed until the setState call afterwards
+ // completes. acquiring the initialization lock here blocks until initialization is
+ // complete, and we verify this wasn't called before initializeDelegate for some reason.
+ synchronized (mInitializationLock) {
+ Preconditions.checkState(mInitialized);
+ }
+ }
+
+ @Override
+ public void onStateChanged(State oldState, State newState) {
+ waitForInitialization();
+ setState(previousState -> newState);
+ }
+
+ @Override
+ public void onReportLocation(LocationResult locationResult) {
+ waitForInitialization();
+ reportLocation(locationResult);
+ }
+
+ @Override
+ protected void onStart() {
+ Preconditions.checkState(mInitialized);
+ mDelegate.getController().start();
+ }
+
+ @Override
+ protected void onStop() {
+ Preconditions.checkState(mInitialized);
+ mDelegate.getController().stop();
+ }
+
+ @Override
+ protected void onSetRequest(ProviderRequest request) {
+ Preconditions.checkState(mInitialized);
+ mDelegate.getController().setRequest(request);
+ }
+
+ @Override
+ protected void onFlush(Runnable callback) {
+ Preconditions.checkState(mInitialized);
+ mDelegate.getController().flush(callback);
+ }
+
+ @Override
+ protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
+ Preconditions.checkState(mInitialized);
+ mDelegate.getController().sendExtraCommand(uid, pid, command, extras);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ Preconditions.checkState(mInitialized);
+ mDelegate.dump(fd, pw, args);
+ }
+}
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
new file mode 100644
index 0000000..6f4aa64
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.provider;
+
+import static android.location.provider.ProviderRequest.INTERVAL_DISABLED;
+
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+import static com.android.server.location.LocationManagerService.D;
+import static com.android.server.location.LocationManagerService.TAG;
+
+import android.annotation.Nullable;
+import android.location.Location;
+import android.location.LocationResult;
+import android.location.provider.ProviderRequest;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.server.DeviceIdleInternal;
+import com.android.server.FgThread;
+import com.android.server.location.eventlog.LocationEventLog;
+import com.android.server.location.injector.DeviceIdleHelper;
+import com.android.server.location.injector.DeviceStationaryHelper;
+import com.android.server.location.injector.Injector;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Throttles location providers completely while the device is in doze and stationary, and returns
+ * the last known location as a new location at the appropriate interval instead. Hypothetically,
+ * this throttling could be applied only when the device is stationary - however we don't trust the
+ * accuracy of the on-device SMD (which could allow for 100s of feet of movement without triggering
+ * in some use cases) enough to rely just on this. Instead we require the device to be in doze mode
+ * and stationary to narrow down the effect of false positives/negatives.
+ */
+public final class StationaryThrottlingLocationProvider extends DelegateLocationProvider
+ implements DeviceIdleHelper.DeviceIdleListener, DeviceIdleInternal.StationaryListener {
+
+ private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000;
+
+ private final Object mLock = new Object();
+
+ private final String mName;
+ private final DeviceIdleHelper mDeviceIdleHelper;
+ private final DeviceStationaryHelper mDeviceStationaryHelper;
+ private final LocationEventLog mEventLog;
+
+ @GuardedBy("mLock")
+ private boolean mDeviceIdle = false;
+ @GuardedBy("mLock")
+ private boolean mDeviceStationary = false;
+ @GuardedBy("mLock")
+ private long mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
+ @GuardedBy("mLock")
+ private ProviderRequest mIncomingRequest = ProviderRequest.EMPTY_REQUEST;
+ @GuardedBy("mLock")
+ private ProviderRequest mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
+ @GuardedBy("mLock")
+ private long mThrottlingIntervalMs = INTERVAL_DISABLED;
+ @GuardedBy("mLock")
+ private @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
+
+ @GuardedBy("mLock")
+ private @Nullable Location mLastLocation;
+
+ public StationaryThrottlingLocationProvider(String name, Injector injector,
+ AbstractLocationProvider delegate, LocationEventLog eventLog) {
+ super(DIRECT_EXECUTOR, delegate);
+
+ mName = name;
+ mDeviceIdleHelper = injector.getDeviceIdleHelper();
+ mDeviceStationaryHelper = injector.getDeviceStationaryHelper();
+ mEventLog = eventLog;
+
+ // must be last statement in the constructor because reference is escaping
+ initializeDelegate();
+ }
+
+ @Override
+ public void onReportLocation(LocationResult locationResult) {
+ super.onReportLocation(locationResult);
+
+ synchronized (mLock) {
+ mLastLocation = locationResult.getLastLocation();
+ onThrottlingChangedLocked(false);
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ mDelegate.getController().start();
+
+ synchronized (mLock) {
+ mDeviceIdleHelper.addListener(this);
+ mDeviceIdle = mDeviceIdleHelper.isDeviceIdle();
+ mDeviceStationaryHelper.addListener(this);
+ mDeviceStationary = false;
+ mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
+
+ onThrottlingChangedLocked(false);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ synchronized (mLock) {
+ mDeviceStationaryHelper.removeListener(this);
+ mDeviceIdleHelper.removeListener(this);
+
+ mIncomingRequest = ProviderRequest.EMPTY_REQUEST;
+ mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
+ mThrottlingIntervalMs = INTERVAL_DISABLED;
+
+ if (mDeliverLastLocationCallback != null) {
+ FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
+ mDeliverLastLocationCallback = null;
+ }
+
+ mLastLocation = null;
+ }
+
+ mDelegate.getController().stop();
+ }
+
+ @Override
+ protected void onSetRequest(ProviderRequest request) {
+ synchronized (mLock) {
+ mIncomingRequest = request;
+ onThrottlingChangedLocked(true);
+ }
+ }
+
+ @Override
+ public void onDeviceIdleChanged(boolean deviceIdle) {
+ synchronized (mLock) {
+ if (deviceIdle == mDeviceIdle) {
+ return;
+ }
+
+ mDeviceIdle = deviceIdle;
+ onThrottlingChangedLocked(false);
+ }
+ }
+
+ @Override
+ public void onDeviceStationaryChanged(boolean deviceStationary) {
+ synchronized (mLock) {
+ if (mDeviceStationary == deviceStationary) {
+ return;
+ }
+
+ mDeviceStationary = deviceStationary;
+ if (mDeviceStationary) {
+ mDeviceStationaryRealtimeMs = SystemClock.elapsedRealtime();
+ } else {
+ mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
+ }
+ onThrottlingChangedLocked(false);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onThrottlingChangedLocked(boolean deliverImmediate) {
+ long throttlingIntervalMs = INTERVAL_DISABLED;
+ if (mDeviceStationary && mDeviceIdle && !mIncomingRequest.isLocationSettingsIgnored()
+ && mLastLocation != null
+ && mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs)
+ <= MAX_STATIONARY_LOCATION_AGE_MS) {
+ throttlingIntervalMs = mIncomingRequest.getIntervalMillis();
+ }
+
+ ProviderRequest newRequest;
+ if (throttlingIntervalMs != INTERVAL_DISABLED) {
+ newRequest = ProviderRequest.EMPTY_REQUEST;
+ } else {
+ newRequest = mIncomingRequest;
+ }
+
+ if (!newRequest.equals(mOutgoingRequest)) {
+ mOutgoingRequest = newRequest;
+ mDelegate.getController().setRequest(mOutgoingRequest);
+ }
+
+ if (throttlingIntervalMs == mThrottlingIntervalMs) {
+ return;
+ }
+
+ long oldThrottlingIntervalMs = mThrottlingIntervalMs;
+ mThrottlingIntervalMs = throttlingIntervalMs;
+
+ if (mThrottlingIntervalMs != INTERVAL_DISABLED) {
+ if (oldThrottlingIntervalMs == INTERVAL_DISABLED) {
+ if (D) {
+ Log.d(TAG, mName + " provider stationary throttled");
+ }
+ mEventLog.logProviderStationaryThrottled(mName, true);
+ }
+
+ if (mDeliverLastLocationCallback != null) {
+ FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
+ }
+ mDeliverLastLocationCallback = new DeliverLastLocationRunnable();
+
+ Preconditions.checkState(mLastLocation != null);
+
+ if (deliverImmediate) {
+ FgThread.getHandler().post(mDeliverLastLocationCallback);
+ } else {
+ long delayMs = mThrottlingIntervalMs - mLastLocation.getElapsedRealtimeAgeMillis();
+ FgThread.getHandler().postDelayed(mDeliverLastLocationCallback, delayMs);
+ }
+ } else {
+ if (oldThrottlingIntervalMs != INTERVAL_DISABLED) {
+ mEventLog.logProviderStationaryThrottled(mName, false);
+ if (D) {
+ Log.d(TAG, mName + " provider stationary unthrottled");
+ }
+ }
+
+ FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
+ mDeliverLastLocationCallback = null;
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mThrottlingIntervalMs != INTERVAL_DISABLED) {
+ pw.println("stationary throttled=" + mLastLocation);
+ } else {
+ pw.print("stationary throttled=false");
+ if (!mDeviceIdle) {
+ pw.print(" (not idle)");
+ }
+ if (!mDeviceStationary) {
+ pw.print(" (not stationary)");
+ }
+ pw.println();
+ }
+
+ mDelegate.dump(fd, pw, args);
+ }
+
+ private class DeliverLastLocationRunnable implements Runnable {
+ @Override
+ public void run() {
+ Location location;
+ synchronized (mLock) {
+ if (mDeliverLastLocationCallback != this) {
+ return;
+ }
+ if (mLastLocation == null) {
+ return;
+ }
+
+ location = new Location(mLastLocation);
+ location.setTime(System.currentTimeMillis());
+ location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+ if (location.hasSpeed()) {
+ location.removeSpeed();
+ if (location.hasSpeedAccuracy()) {
+ location.removeSpeedAccuracy();
+ }
+ }
+ if (location.hasBearing()) {
+ location.removeBearing();
+ if (location.hasBearingAccuracy()) {
+ location.removeBearingAccuracy();
+ }
+ }
+
+ mLastLocation = location;
+ FgThread.getHandler().postDelayed(this, mThrottlingIntervalMs);
+ }
+
+ reportLocation(LocationResult.wrap(location));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 32d637f..4c97f64 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -140,7 +140,7 @@
}
@Override
- public void onSetRequest(ProviderRequest request) {
+ protected void onSetRequest(ProviderRequest request) {
mRequest = request;
mServiceWatcher.runOnBinder(binder -> {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
index 38eeb88..af0774c 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowData.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
@@ -35,6 +35,12 @@
*/
private static final int CURRENT_VERSION = 2;
+ /**
+ * This is the legacy version of the escrow data format for R builds. The escrow data is only
+ * encrypted by the escrow key, without additional wrap of another key from keystore.
+ */
+ private static final int LEGACY_SINGLE_ENCRYPTED_VERSION = 1;
+
private RebootEscrowData(byte spVersion, byte[] syntheticPassword, byte[] blob,
RebootEscrowKey key) {
mSpVersion = spVersion;
@@ -64,6 +70,19 @@
return mKey;
}
+ private static byte[] decryptBlobCurrentVersion(SecretKey kk, RebootEscrowKey ks,
+ DataInputStream dis) throws IOException {
+ if (kk == null) {
+ throw new IOException("Failed to find wrapper key in keystore, cannot decrypt the"
+ + " escrow data");
+ }
+
+ // Decrypt the blob with the key from keystore first, then decrypt again with the reboot
+ // escrow key.
+ byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis);
+ return AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob);
+ }
+
static RebootEscrowData fromEncryptedData(RebootEscrowKey ks, byte[] blob, SecretKey kk)
throws IOException {
Objects.requireNonNull(ks);
@@ -71,17 +90,20 @@
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
int version = dis.readInt();
- if (version != CURRENT_VERSION) {
- throw new IOException("Unsupported version " + version);
- }
byte spVersion = dis.readByte();
-
- // Decrypt the blob with the key from keystore first, then decrypt again with the reboot
- // escrow key.
- byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis);
- final byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob);
-
- return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
+ switch (version) {
+ case CURRENT_VERSION: {
+ byte[] syntheticPassword = decryptBlobCurrentVersion(kk, ks, dis);
+ return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
+ }
+ case LEGACY_SINGLE_ENCRYPTED_VERSION: {
+ // Decrypt the blob with the escrow key directly.
+ byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), dis);
+ return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
+ }
+ default:
+ throw new IOException("Unsupported version " + version);
+ }
}
static RebootEscrowData fromSyntheticPassword(RebootEscrowKey ks, byte spVersion,
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 53b62ca..30ea555 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -146,6 +146,7 @@
RebootEscrowProviderInterface rebootEscrowProvider;
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
"server_based_ror_enabled", false)) {
+ Slog.i(TAG, "Using server based resume on reboot");
rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage);
} else {
rebootEscrowProvider = new RebootEscrowProviderHalImpl();
@@ -272,6 +273,10 @@
// generated before reboot. Note that we will clear the escrow key even if the keystore key
// is null.
SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
+ if (kk == null) {
+ Slog.i(TAG, "Failed to load the key for resume on reboot from key store.");
+ }
+
RebootEscrowKey escrowKey;
try {
escrowKey = getAndClearRebootEscrowKey(kk);
@@ -281,7 +286,7 @@
return;
}
- if (kk == null || escrowKey == null) {
+ if (escrowKey == null) {
onGetRebootEscrowKeyFailed(users);
return;
}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
index 9d09637..b3b4546 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
@@ -136,6 +136,11 @@
Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
return null;
}
+ if (decryptionKey == null) {
+ Slog.w(TAG, "Failed to decrypt the escrow key; decryption key from keystore is"
+ + " null.");
+ return null;
+ }
Slog.i(TAG, "Loaded reboot escrow server blob from storage");
try {
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index d48b9a4..76bdf44 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -83,7 +83,14 @@
@Override
public void reportPlaybackStateEvent(
String sessionId, PlaybackStateEvent event, int userId) {
- // TODO: log it to statsd
+ StatsEvent statsEvent = StatsEvent.newBuilder()
+ .setAtomId(322)
+ .writeString(sessionId)
+ .writeInt(event.getState())
+ .writeLong(event.getTimeSinceCreatedMillis())
+ .usePooledBuffer()
+ .build();
+ StatsLog.write(statsEvent);
}
@Override
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index b06e84d..d3a56c6 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -22,6 +22,7 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
+import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1170,6 +1171,8 @@
ret = ShortcutInfo.FLAG_CACHED_NOTIFICATIONS;
} else if (cacheFlags == FLAG_CACHE_BUBBLE_SHORTCUTS) {
ret = ShortcutInfo.FLAG_CACHED_BUBBLES;
+ } else if (cacheFlags == FLAG_CACHE_PEOPLE_TILE_SHORTCUTS) {
+ ret = ShortcutInfo.FLAG_CACHED_PEOPLE_TILE;
}
Preconditions.checkArgumentPositive(ret, "Invalid cache owner");
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7b9cf73..e6789d4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -193,7 +193,6 @@
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
import android.content.pm.ModuleInfo;
-import android.content.pm.overlay.OverlayPaths;
import android.content.pm.PackageChangeEvent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
@@ -462,6 +461,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -2586,49 +2586,42 @@
Intent intent, int matchFlags, List<ResolveInfo> candidates,
CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) {
final ArrayList<ResolveInfo> result = new ArrayList<>();
- final ArrayList<ResolveInfo> alwaysList = new ArrayList<>();
- final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
- final int count = candidates.size();
- // First, try to use linked apps. Partition the candidates into four lists:
- // one for the final results, one for the "do not use ever", one for "undefined status"
- // and finally one for "browser app type".
- for (int n=0; n<count; n++) {
- ResolveInfo info = candidates.get(n);
- String packageName = info.activityInfo.packageName;
- PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps != null) {
- // Add to the special match all list (Browser use case)
- if (info.handleAllWebDataURI) {
- matchAllList.add(info);
- continue;
- }
- boolean isAlways = mDomainVerificationManager
- .isApprovedForDomain(ps, intent, userId);
- if (isAlways) {
- alwaysList.add(info);
- } else {
- undefinedList.add(info);
- }
- continue;
+ final int count = candidates.size();
+ // First, try to use approved apps.
+ for (int n = 0; n < count; n++) {
+ ResolveInfo info = candidates.get(n);
+ // Add to the special match all list (Browser use case)
+ if (info.handleAllWebDataURI) {
+ matchAllList.add(info);
}
}
+ Pair<List<ResolveInfo>, Integer> infosAndLevel = mDomainVerificationManager
+ .filterToApprovedApp(intent, candidates, userId, mSettings::getPackageLPr);
+ List<ResolveInfo> approvedInfos = infosAndLevel.first;
+ Integer highestApproval = infosAndLevel.second;
+
// We'll want to include browser possibilities in a few cases
boolean includeBrowser = false;
- // First try to add the "always" resolution(s) for the current user, if any
- if (alwaysList.size() > 0) {
- result.addAll(alwaysList);
+ // If no apps are approved for the domain, resolve only to browsers
+ if (approvedInfos.isEmpty()) {
+ // If the other profile has a result, include that and delegate to ResolveActivity
+ if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel
+ > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) {
+ result.add(xpDomainInfo.resolveInfo);
+ } else {
+ includeBrowser = true;
+ }
} else {
- // Add all undefined apps as we want them to appear in the disambiguation dialog.
- result.addAll(undefinedList);
- // Maybe add one for the other profile.
- if (xpDomainInfo != null && xpDomainInfo.wereAnyDomainsVerificationApproved) {
+ result.addAll(approvedInfos);
+
+ // If the other profile has an app that's of equal or higher approval, add it
+ if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel >= highestApproval) {
result.add(xpDomainInfo.resolveInfo);
}
- includeBrowser = true;
}
if (includeBrowser) {
@@ -2676,9 +2669,7 @@
}
}
- // If there is nothing selected, add all candidates and remove the ones that the
- //user
- // has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state
+ // If there is nothing selected, add all candidates
if (result.size() == 0) {
result.addAll(candidates);
}
@@ -2780,10 +2771,12 @@
sourceUserId, parentUserId);
}
- result.wereAnyDomainsVerificationApproved |= mDomainVerificationManager
- .isApprovedForDomain(ps, intent, riTargetUser.targetUserId);
+ result.highestApprovalLevel = Math.max(mDomainVerificationManager
+ .approvalLevelForDomain(ps, intent, riTargetUser.targetUserId),
+ result.highestApprovalLevel);
}
- if (result != null && !result.wereAnyDomainsVerificationApproved) {
+ if (result != null && result.highestApprovalLevel
+ <= DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) {
return null;
}
return result;
@@ -3026,9 +3019,10 @@
final String packageName = info.activityInfo.packageName;
final PackageSetting ps = mSettings.getPackageLPr(packageName);
if (ps.getInstantApp(userId)) {
- if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
+ if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent,
+ userId)) {
if (DEBUG_INSTANT) {
- Slog.v(TAG, "Instant app approvd for intent; pkg: "
+ Slog.v(TAG, "Instant app approved for intent; pkg: "
+ packageName);
}
localInstantApp = info;
@@ -3953,7 +3947,8 @@
if (ps != null) {
// only check domain verification status if the app is not a browser
if (!info.handleAllWebDataURI) {
- if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
+ if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent,
+ userId)) {
if (DEBUG_INSTANT) {
Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName
+ ", approved");
@@ -9369,8 +9364,8 @@
if (ri.activityInfo.applicationInfo.isInstantApp()) {
final String packageName = ri.activityInfo.packageName;
final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps != null && mDomainVerificationManager
- .isApprovedForDomain(ps, intent, userId)) {
+ if (ps != null && hasAnyDomainApproval(mDomainVerificationManager, ps,
+ intent, userId)) {
return ri;
}
}
@@ -9420,6 +9415,19 @@
}
/**
+ * Do NOT use for intent resolution filtering. That should be done with
+ * {@link DomainVerificationManagerInternal#filterToApprovedApp(Intent, List, int, Function)}.
+ *
+ * @return if the package is approved at any non-zero level for the domain in the intent
+ */
+ private static boolean hasAnyDomainApproval(
+ @NonNull DomainVerificationManagerInternal manager, @NonNull PackageSetting pkgSetting,
+ @NonNull Intent intent, @UserIdInt int userId) {
+ return manager.approvalLevelForDomain(pkgSetting, intent, userId)
+ > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
+ }
+
+ /**
* Return true if the given list is not empty and all of its contents have
* an activityInfo with the given package name.
*/
@@ -9862,7 +9870,7 @@
private static class CrossProfileDomainInfo {
/* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */
ResolveInfo resolveInfo;
- boolean wereAnyDomainsVerificationApproved;
+ int highestApprovalLevel = DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
}
private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
@@ -25917,6 +25925,17 @@
}
@Override
+ public boolean isPackageDebuggable(String packageName) throws RemoteException {
+ int callingUser = UserHandle.getCallingUserId();
+ ApplicationInfo appInfo = getApplicationInfo(packageName, 0, callingUser);
+ if (appInfo != null) {
+ return (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
+ }
+
+ throw new RemoteException("Couldn't get debug flag for package " + packageName);
+ }
+
+ @Override
public boolean[] isAudioPlaybackCaptureAllowed(String[] packageNames)
throws RemoteException {
int callingUser = UserHandle.getUserId(Binder.getCallingUid());
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 5364cbf..a83a3f8 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -783,6 +783,10 @@
incrementalStates.onStorageHealthStatusChanged(status);
}
+ public long getFirstInstallTime() {
+ return firstInstallTime;
+ }
+
protected PackageSettingBase updateFrom(PackageSettingBase other) {
super.copyFrom(other);
setPath(other.getPath());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index a8a6bce..2112247 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -72,7 +72,6 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.incremental.IncrementalManager;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.service.pm.PackageServiceDumpProto;
@@ -2615,6 +2614,8 @@
} else {
serializer.attributeInt(null, "sharedUserId", pkg.appId);
}
+ serializer.attributeFloat(null, "loadingProgress",
+ pkg.getIncrementalStates().getProgress());
writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
@@ -3389,6 +3390,9 @@
if (ps.appId <= 0) {
ps.appId = parser.getAttributeInt(null, "sharedUserId", 0);
}
+ final float loadingProgress =
+ parser.getAttributeFloat(null, "loadingProgress", 0);
+ ps.setLoadingProgress(loadingProgress);
int outerDepth = parser.getDepth();
int type;
@@ -4582,7 +4586,7 @@
pw.print(prefix); pw.print(" installerAttributionTag=");
pw.println(ps.installSource.installerAttributionTag);
}
- if (IncrementalManager.isIncrementalPath(ps.getPathString())) {
+ if (ps.isPackageLoading()) {
pw.print(prefix); pw.println(" loadingProgress="
+ (int) (ps.getIncrementalStates().getProgress() * 100) + "%");
}
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 37dfea4..5ee612b 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -125,8 +125,10 @@
public static void validatePackageDexMetadata(AndroidPackage pkg)
throws PackageParserException {
Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values();
+ String packageName = pkg.getPackageName();
+ long versionCode = pkg.toAppInfoWithoutState().longVersionCode;
for (String dexMetadata : apkToDexMetadataList) {
- DexMetadataHelper.validateDexMetadataFile(dexMetadata);
+ DexMetadataHelper.validateDexMetadataFile(dexMetadata, packageName, versionCode);
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
index 1925590..b3108c5 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -38,8 +38,6 @@
import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
import java.util.Arrays;
-import java.util.Objects;
-import java.util.Set;
import java.util.function.Function;
@SuppressWarnings("PointlessBooleanExpression")
@@ -202,8 +200,7 @@
printedHeader = true;
}
- boolean isLinkHandlingAllowed = userState == null
- || !userState.isDisallowLinkHandling();
+ boolean isLinkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed();
writer.increaseIndent();
writer.print("User ");
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 50fd6e3..5d4370a 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -16,18 +16,22 @@
package com.android.server.pm.verify.domain;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.content.Intent;
import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.os.Binder;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
+import android.util.Pair;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
@@ -39,6 +43,7 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
@@ -48,6 +53,78 @@
UUID DISABLED_ID = new UUID(0, 0);
/**
+ * The app has not been approved for this domain and should never be able to open it through
+ * an implicit web intent.
+ */
+ int APPROVAL_LEVEL_NONE = 0;
+
+ /**
+ * The app has been approved through the legacy
+ * {@link PackageManager#updateIntentVerificationStatusAsUser(String, int, int)} API, which has
+ * been preserved for migration purposes, but is otherwise ignored. Corresponds to
+ * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} and
+ * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK}.
+ *
+ * This should be used as the cutoff for showing a picker if no better approved app exists
+ * during the legacy transition period.
+ *
+ * TODO(b/177923646): The legacy values can be removed once the Settings API changes are
+ * shipped. These values are not stable, so just deleting the constant and shifting others is
+ * fine.
+ */
+ int APPROVAL_LEVEL_LEGACY_ASK = 1;
+
+ /**
+ * The app has been approved through the legacy
+ * {@link PackageManager#updateIntentVerificationStatusAsUser(String, int, int)} API, which has
+ * been preserved for migration purposes, but is otherwise ignored. Corresponds to
+ * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS}.
+ */
+ int APPROVAL_LEVEL_LEGACY_ALWAYS = 1;
+
+ /**
+ * The app has been chosen by the user through
+ * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)}, indictag an explicit
+ * choice to use this app to open an unverified domain.
+ */
+ int APPROVAL_LEVEL_SELECTION = 2;
+
+ /**
+ * The app is approved through the digital asset link statement being hosted at the domain
+ * it is capturing. This is set through {@link #setDomainVerificationStatus(UUID, Set, int)} by
+ * the domain verification agent on device.
+ */
+ int APPROVAL_LEVEL_VERIFIED = 3;
+
+ /**
+ * The app has been installed as an instant app, which grants it total authority on the domains
+ * that it declares. It is expected that the package installer validate the domains the app
+ * declares against the digital asset link statements before allowing it to be installed.
+ *
+ * The user is still able to disable instant app link handling through
+ * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
+ */
+ int APPROVAL_LEVEL_INSTANT_APP = 4;
+
+ /**
+ * Defines the possible values for {@link #approvalLevelForDomain(PackageSetting, Intent, int)}
+ * which sorts packages by approval priority. A higher numerical value means the package should
+ * override all lower values. This means that comparison using less/greater than IS valid.
+ *
+ * Negative values are possible, although not implemented, reserved if explicit disable of a
+ * package for a domain needs to be tracked.
+ */
+ @IntDef({
+ APPROVAL_LEVEL_NONE,
+ APPROVAL_LEVEL_LEGACY_ASK,
+ APPROVAL_LEVEL_LEGACY_ALWAYS,
+ APPROVAL_LEVEL_SELECTION,
+ APPROVAL_LEVEL_VERIFIED,
+ APPROVAL_LEVEL_INSTANT_APP
+ })
+ @interface ApprovalLevel{}
+
+ /**
* Generate a new domain set ID to be used for attaching new packages.
*/
@NonNull
@@ -211,11 +288,28 @@
DomainVerificationCollector getCollector();
/**
- * Check if a resolving URI is approved to takeover the domain as the sole resolved target.
- * This can be because the domain was auto-verified for the package, or if the user manually
- * chose to enable the domain for the package.
+ * Filters the provided list down to the {@link ResolveInfo} objects that should be allowed
+ * to open the domain inside the {@link Intent}. It is possible for no packages represented in
+ * the list to be approved, in which case an empty list will be returned.
+ *
+ * @return the filtered list and the corresponding approval level
*/
- boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+ @NonNull
+ Pair<List<ResolveInfo>, Integer> filterToApprovedApp(@NonNull Intent intent,
+ @NonNull List<ResolveInfo> infos, @UserIdInt int userId,
+ @NonNull Function<String, PackageSetting> pkgSettingFunction);
+
+ /**
+ * Check at what precedence a package resolving a URI is approved to takeover the domain.
+ * This can be because the domain was auto-verified for the package, or if the user manually
+ * chose to enable the domain for the package. If an app is auto-verified, it will be
+ * preferred over apps that were manually selected.
+ *
+ * NOTE: This should not be used for filtering intent resolution. See
+ * {@link #filterToApprovedApp(Intent, List, int, Function)} for that.
+ */
+ @ApprovalLevel
+ int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
@UserIdInt int userId);
/**
@@ -231,8 +325,7 @@
throws IllegalArgumentException, NameNotFoundException;
- interface Connection extends DomainVerificationEnforcer.Callback,
- Function<String, PackageSetting> {
+ interface Connection extends DomainVerificationEnforcer.Callback {
/**
* Notify that a settings change has been made and that eventually
@@ -265,10 +358,5 @@
@Nullable
AndroidPackage getPackageLocked(@NonNull String pkgName);
-
- @Override
- default PackageSetting apply(@NonNull String pkgName) {
- return getPackageSettingLocked(pkgName);
- }
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
index 679f948..c864b29 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
@@ -53,7 +53,7 @@
public static final String TAG_USER_STATE = "user-state";
public static final String ATTR_USER_ID = "userId";
- public static final String ATTR_DISALLOW_LINK_HANDLING = "disallowLinkHandling";
+ public static final String ATTR_ALLOW_LINK_HANDLING = "allowLinkHandling";
public static final String TAG_ENABLED_HOSTS = "enabled-hosts";
public static final String TAG_HOST = "host";
@@ -252,7 +252,7 @@
return null;
}
- boolean disallowLinkHandling = section.getBoolean(ATTR_DISALLOW_LINK_HANDLING);
+ boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, true);
ArraySet<String> enabledHosts = new ArraySet<>();
SettingsXml.ChildSection child = section.children();
@@ -260,7 +260,7 @@
readEnabledHosts(child, enabledHosts);
}
- return new DomainVerificationUserState(userId, enabledHosts, disallowLinkHandling);
+ return new DomainVerificationUserState(userId, enabledHosts, allowLinkHandling);
}
private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section,
@@ -279,8 +279,8 @@
try (SettingsXml.WriteSection section =
parentSection.startSection(TAG_USER_STATE)
.attribute(ATTR_USER_ID, userState.getUserId())
- .attribute(ATTR_DISALLOW_LINK_HANDLING,
- userState.isDisallowLinkHandling())) {
+ .attribute(ATTR_ALLOW_LINK_HANDLING,
+ userState.isLinkHandlingAllowed())) {
ArraySet<String> enabledHosts = userState.getEnabledHosts();
if (!enabledHosts.isEmpty()) {
try (SettingsXml.WriteSection enabledHostsSection =
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index fa03274..86a92d79 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -16,6 +16,8 @@
package com.android.server.pm.verify.domain;
+import static java.util.Collections.emptyList;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -26,6 +28,8 @@
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.parsing.component.ParsedActivity;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
@@ -35,6 +39,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -62,6 +67,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
@@ -401,7 +407,7 @@
}
pkgState.getOrCreateUserSelectionState(userId)
- .setDisallowLinkHandling(!allowed);
+ .setLinkHandlingAllowed(allowed);
}
mConnection.scheduleWriteSettings();
@@ -423,11 +429,11 @@
for (int userStateIndex = 0; userStateIndex < userStatesSize;
userStateIndex++) {
userStates.valueAt(userStateIndex)
- .setDisallowLinkHandling(!allowed);
+ .setLinkHandlingAllowed(allowed);
}
} else {
pkgState.getOrCreateUserSelectionState(userId)
- .setDisallowLinkHandling(!allowed);
+ .setLinkHandlingAllowed(allowed);
}
}
@@ -440,7 +446,7 @@
}
pkgState.getOrCreateUserSelectionState(userId)
- .setDisallowLinkHandling(!allowed);
+ .setLinkHandlingAllowed(allowed);
}
}
@@ -468,6 +474,17 @@
throw new InvalidDomainSetException(domainSetId, null,
InvalidDomainSetException.REASON_ID_INVALID);
}
+
+ if (enabled) {
+ for (String domain : domains) {
+ if (!getApprovedPackages(domain, userId, APPROVAL_LEVEL_LEGACY_ALWAYS + 1,
+ mConnection::getPackageSettingLocked).first.isEmpty()) {
+ throw new InvalidDomainSetException(domainSetId, null,
+ InvalidDomainSetException.REASON_UNABLE_TO_APPROVE);
+ }
+ }
+ }
+
DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
false /* forAutoVerify */, callingUid, userId);
DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
@@ -589,10 +606,10 @@
hostToUserSelectionMap.put(domains.valueAt(index), false);
}
- boolean openVerifiedLinks = false;
DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+ boolean linkHandlingAllowed = true;
if (userState != null) {
- openVerifiedLinks = !userState.isDisallowLinkHandling();
+ linkHandlingAllowed = userState.isLinkHandlingAllowed();
ArraySet<String> enabledHosts = userState.getEnabledHosts();
int hostsSize = enabledHosts.size();
for (int index = 0; index < hostsSize; index++) {
@@ -601,7 +618,7 @@
}
return new DomainVerificationUserSelection(pkgState.getId(), packageName,
- UserHandle.of(userId), openVerifiedLinks, hostToUserSelectionMap);
+ UserHandle.of(userId), linkHandlingAllowed, hostToUserSelectionMap);
}
}
@@ -683,7 +700,7 @@
ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts);
newEnabledHosts.retainAll(newWebDomains);
DomainVerificationUserState newUserState = new DomainVerificationUserState(
- userId, newEnabledHosts, oldUserState.isDisallowLinkHandling());
+ userId, newEnabledHosts, oldUserState.isLinkHandlingAllowed());
newUserStates.put(userId, newUserState);
}
}
@@ -910,7 +927,7 @@
// This method is only used by DomainVerificationShell, which doesn't lock PMS, so it's
// safe to pass mConnection directly here and lock PMS. This method is not exposed
// to the general system server/PMS.
- printState(writer, packageName, userId, mConnection);
+ printState(writer, packageName, userId, mConnection::getPackageSettingLocked);
}
@Override
@@ -919,7 +936,7 @@
@Nullable Function<String, PackageSetting> pkgSettingFunction)
throws NameNotFoundException {
if (pkgSettingFunction == null) {
- pkgSettingFunction = mConnection;
+ pkgSettingFunction = mConnection::getPackageSettingLocked;
}
synchronized (mLock) {
@@ -1156,46 +1173,211 @@
mConnection.scheduleWriteSettings();
}
+ /**
+ * {@inheritDoc}
+ *
+ * Resolving an Intent to an approved app happens in stages:
+ * <ol>
+ * <li>Find all non-zero approved packages for the {@link Intent}'s domain</li>
+ * <li>Filter to packages with the highest approval level, see {@link ApprovalLevel}</li>
+ * <li>Filter out {@link ResolveInfo}s that don't match that approved packages</li>
+ * <li>Take the approved packages with the latest install time</li>
+ * <li>Take the ResolveInfo representing the Activity declared last in the manifest</li>
+ * <li>Return remaining results if any exist</li>
+ * </ol>
+ */
+ @NonNull
@Override
- public boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+ public Pair<List<ResolveInfo>, Integer> filterToApprovedApp(@NonNull Intent intent,
+ @NonNull List<ResolveInfo> infos, @UserIdInt int userId,
+ @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+ String domain = intent.getData().getHost();
+
+ // Collect package names
+ ArrayMap<String, Integer> packageApprovals = new ArrayMap<>();
+ int infosSize = infos.size();
+ for (int index = 0; index < infosSize; index++) {
+ packageApprovals.put(infos.get(index).getComponentInfo().packageName,
+ APPROVAL_LEVEL_NONE);
+ }
+
+ // Find all approval levels
+ int highestApproval = fillMapWithApprovalLevels(packageApprovals, domain, userId,
+ pkgSettingFunction);
+ if (highestApproval == APPROVAL_LEVEL_NONE) {
+ return Pair.create(emptyList(), highestApproval);
+ }
+
+ // Filter to highest, non-zero packages
+ ArraySet<String> approvedPackages = new ArraySet<>();
+ for (int index = 0; index < infosSize; index++) {
+ if (packageApprovals.valueAt(index) == highestApproval) {
+ approvedPackages.add(packageApprovals.keyAt(index));
+ }
+ }
+
+ ArraySet<String> filteredPackages = new ArraySet<>();
+ if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
+ // To maintain legacy behavior while the Settings API is not implemented,
+ // show the chooser if all approved apps are marked ask, skipping the
+ // last app, last declaration filtering.
+ filteredPackages.addAll(approvedPackages);
+ } else {
+ // Filter to last installed package
+ long latestInstall = Long.MIN_VALUE;
+ int approvedSize = approvedPackages.size();
+ for (int index = 0; index < approvedSize; index++) {
+ String packageName = approvedPackages.valueAt(index);
+ PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+ if (pkgSetting == null) {
+ continue;
+ }
+ long installTime = pkgSetting.getFirstInstallTime();
+ if (installTime > latestInstall) {
+ latestInstall = installTime;
+ filteredPackages.clear();
+ filteredPackages.add(packageName);
+ } else if (installTime == latestInstall) {
+ filteredPackages.add(packageName);
+ }
+ }
+ }
+
+ // Filter to approved ResolveInfos
+ ArrayMap<String, List<ResolveInfo>> approvedInfos = new ArrayMap<>();
+ for (int index = 0; index < infosSize; index++) {
+ ResolveInfo info = infos.get(index);
+ String packageName = info.getComponentInfo().packageName;
+ if (filteredPackages.contains(packageName)) {
+ List<ResolveInfo> infosPerPackage = approvedInfos.get(packageName);
+ if (infosPerPackage == null) {
+ infosPerPackage = new ArrayList<>();
+ approvedInfos.put(packageName, infosPerPackage);
+ }
+ infosPerPackage.add(info);
+ }
+ }
+
+ List<ResolveInfo> finalList;
+ if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
+ // If legacy ask, skip the last declaration filtering
+ finalList = new ArrayList<>();
+ int size = approvedInfos.size();
+ for (int index = 0; index < size; index++) {
+ finalList.addAll(approvedInfos.valueAt(index));
+ }
+ } else {
+ // Find the last declared ResolveInfo per package
+ finalList = filterToLastDeclared(approvedInfos, pkgSettingFunction);
+ }
+
+ return Pair.create(finalList, highestApproval);
+ }
+
+ /**
+ * @return highest approval level found
+ */
+ private int fillMapWithApprovalLevels(@NonNull ArrayMap<String, Integer> inputMap,
+ @NonNull String domain, @UserIdInt int userId,
+ @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+ int highestApproval = APPROVAL_LEVEL_NONE;
+ int size = inputMap.size();
+ for (int index = 0; index < size; index++) {
+ String packageName = inputMap.keyAt(index);
+ PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+ if (pkgSetting == null) {
+ inputMap.setValueAt(index, APPROVAL_LEVEL_NONE);
+ continue;
+ }
+ int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain);
+ highestApproval = Math.max(highestApproval, approval);
+ inputMap.setValueAt(index, approval);
+ }
+
+ return highestApproval;
+ }
+
+ @NonNull
+ private List<ResolveInfo> filterToLastDeclared(
+ @NonNull ArrayMap<String, List<ResolveInfo>> inputMap,
+ @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+ List<ResolveInfo> finalList = new ArrayList<>(inputMap.size());
+
+ int inputSize = inputMap.size();
+ for (int inputIndex = 0; inputIndex < inputSize; inputIndex++) {
+ String packageName = inputMap.keyAt(inputIndex);
+ List<ResolveInfo> infos = inputMap.valueAt(inputIndex);
+ PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+ AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
+ if (pkg == null) {
+ continue;
+ }
+
+ ResolveInfo result = null;
+ int highestIndex = -1;
+ int infosSize = infos.size();
+ for (int infoIndex = 0; infoIndex < infosSize; infoIndex++) {
+ ResolveInfo info = infos.get(infoIndex);
+ List<ParsedActivity> activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+ for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+ if (Objects.equals(activities.get(activityIndex).getComponentName(),
+ info.getComponentInfo().getComponentName())) {
+ if (activityIndex > highestIndex) {
+ highestIndex = activityIndex;
+ result = info;
+ }
+ break;
+ }
+ }
+ }
+
+ // Shouldn't be null, but might as well be safe
+ if (result != null) {
+ finalList.add(result);
+ }
+ }
+
+ return finalList;
+ }
+
+ @Override
+ public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
@UserIdInt int userId) {
String packageName = pkgSetting.name;
if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) {
if (DEBUG_APPROVAL) {
debugApproval(packageName, intent, userId, false, "not valid intent");
}
- return false;
+ return APPROVAL_LEVEL_NONE;
}
- String host = intent.getData().getHost();
+ return approvalLevelForDomain(pkgSetting, intent.getData().getHost(), userId, intent);
+ }
+
+ /**
+ * @param debugObject Should be an {@link Intent} if checking for resolution or a {@link String}
+ * otherwise.
+ */
+ private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host,
+ @UserIdInt int userId, @NonNull Object debugObject) {
+ String packageName = pkgSetting.name;
final AndroidPackage pkg = pkgSetting.getPkg();
// Should never be null, but if it is, skip this and assume that v2 is enabled
- if (pkg != null) {
- // To allow an instant app to immediately open domains after being installed by the
- // user, auto approve them for any declared autoVerify domains.
- if (pkgSetting.getInstantApp(userId)
- && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
- return true;
- }
-
- if (!DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, SETTINGS_API_V2)) {
- int legacyState = mLegacySettings.getUserState(packageName, userId);
- switch (legacyState) {
- case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
- // If nothing specifically set, assume v2 rules
- break;
- case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
- case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
- case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
- // With v2 split into 2 lists, always and undefined, the concept of whether
- // or not to ask is irrelevant. Assume the user wants this application to
- // open the domain.
- return true;
- case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
- // Never has the same semantics are before
- return false;
- }
+ if (pkg != null && !DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg,
+ SETTINGS_API_V2)) {
+ switch (mLegacySettings.getUserState(packageName, userId)) {
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+ // If nothing specifically set, assume v2 rules
+ break;
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
+ return APPROVAL_LEVEL_NONE;
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
+ return APPROVAL_LEVEL_LEGACY_ASK;
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
+ return APPROVAL_LEVEL_LEGACY_ALWAYS;
}
}
@@ -1203,59 +1385,76 @@
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
if (DEBUG_APPROVAL) {
- debugApproval(packageName, intent, userId, false, "pkgState unavailable");
+ debugApproval(packageName, debugObject, userId, false, "pkgState unavailable");
}
- return false;
+ return APPROVAL_LEVEL_NONE;
+ }
+
+ DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+
+ if (userState != null && !userState.isLinkHandlingAllowed()) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, debugObject, userId, false,
+ "link handling not allowed");
+ }
+ return APPROVAL_LEVEL_NONE;
+ }
+
+ // The instant app branch must be run after the link handling check,
+ // since that should also disable instant apps if toggled
+ if (pkg != null) {
+ // To allow an instant app to immediately open domains after being installed by the
+ // user, auto approve them for any declared autoVerify domains.
+ if (pkgSetting.getInstantApp(userId)
+ && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
+ return APPROVAL_LEVEL_INSTANT_APP;
+ }
}
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
- DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+ // Check if the exact host matches
+ Integer state = stateMap.get(host);
+ if (state != null && DomainVerificationManager.isStateVerified(state)) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, debugObject, userId, true,
+ "host verified exactly");
+ }
+ return APPROVAL_LEVEL_VERIFIED;
+ }
- // Only allow autoVerify approval if the user hasn't disabled it
- if (userState == null || !userState.isDisallowLinkHandling()) {
- // Check if the exact host matches
- Integer state = stateMap.get(host);
- if (state != null && DomainVerificationManager.isStateVerified(state)) {
- if (DEBUG_APPROVAL) {
- debugApproval(packageName, intent, userId, true, "host verified exactly");
- }
- return true;
+ // Otherwise see if the host matches a verified domain by wildcard
+ int stateMapSize = stateMap.size();
+ for (int index = 0; index < stateMapSize; index++) {
+ if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
+ continue;
}
- // Otherwise see if the host matches a verified domain by wildcard
- int stateMapSize = stateMap.size();
- for (int index = 0; index < stateMapSize; index++) {
- if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
- continue;
+ String domain = stateMap.keyAt(index);
+ if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, debugObject, userId, true,
+ "host verified by wildcard");
}
-
- String domain = stateMap.keyAt(index);
- if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
- if (DEBUG_APPROVAL) {
- debugApproval(packageName, intent, userId, true,
- "host verified by wildcard");
- }
- return true;
- }
+ return APPROVAL_LEVEL_VERIFIED;
}
}
// Check user state if available
if (userState == null) {
if (DEBUG_APPROVAL) {
- debugApproval(packageName, intent, userId, false, "userState unavailable");
+ debugApproval(packageName, debugObject, userId, false, "userState unavailable");
}
- return false;
+ return APPROVAL_LEVEL_NONE;
}
// See if the user has approved the exact host
ArraySet<String> enabledHosts = userState.getEnabledHosts();
if (enabledHosts.contains(host)) {
if (DEBUG_APPROVAL) {
- debugApproval(packageName, intent, userId, true,
+ debugApproval(packageName, debugObject, userId, true,
"host enabled by user exactly");
}
- return true;
+ return APPROVAL_LEVEL_SELECTION;
}
// See if the host matches a user selection by wildcard
@@ -1264,24 +1463,85 @@
String domain = enabledHosts.valueAt(index);
if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
if (DEBUG_APPROVAL) {
- debugApproval(packageName, intent, userId, true,
+ debugApproval(packageName, debugObject, userId, true,
"host enabled by user through wildcard");
}
- return true;
+ return APPROVAL_LEVEL_SELECTION;
}
}
if (DEBUG_APPROVAL) {
- debugApproval(packageName, intent, userId, false, "not approved");
+ debugApproval(packageName, debugObject, userId, false, "not approved");
}
- return false;
+ return APPROVAL_LEVEL_NONE;
}
}
- private void debugApproval(@NonNull String packageName, @NonNull Intent intent,
+ /**
+ * @return the filtered list paired with the corresponding approval level
+ */
+ @NonNull
+ private Pair<List<String>, Integer> getApprovedPackages(@NonNull String domain,
+ @UserIdInt int userId, int minimumApproval,
+ @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+ int highestApproval = minimumApproval;
+ List<String> approvedPackages = emptyList();
+
+ synchronized (mLock) {
+ final int size = mAttachedPkgStates.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+ String packageName = pkgState.getPackageName();
+ PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+ if (pkgSetting == null) {
+ continue;
+ }
+
+ int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
+ if (level < minimumApproval) {
+ continue;
+ }
+
+ if (level > highestApproval) {
+ approvedPackages.clear();
+ approvedPackages = CollectionUtils.add(approvedPackages, packageName);
+ highestApproval = level;
+ } else if (level == highestApproval) {
+ approvedPackages = CollectionUtils.add(approvedPackages, packageName);
+ }
+ }
+ }
+
+ if (approvedPackages.isEmpty()) {
+ return Pair.create(approvedPackages, APPROVAL_LEVEL_NONE);
+ }
+
+ List<String> filteredPackages = new ArrayList<>();
+ long latestInstall = Long.MIN_VALUE;
+ final int approvedSize = approvedPackages.size();
+ for (int index = 0; index < approvedSize; index++) {
+ String packageName = approvedPackages.get(index);
+ PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+ if (pkgSetting == null) {
+ continue;
+ }
+ long installTime = pkgSetting.getFirstInstallTime();
+ if (installTime > latestInstall) {
+ latestInstall = installTime;
+ filteredPackages.clear();
+ filteredPackages.add(packageName);
+ } else if (installTime == latestInstall) {
+ filteredPackages.add(packageName);
+ }
+ }
+
+ return Pair.create(filteredPackages, highestApproval);
+ }
+
+ private void debugApproval(@NonNull String packageName, @NonNull Object debugObject,
@UserIdInt int userId, boolean approved, @NonNull String reason) {
String approvalString = approved ? "approved" : "denied";
- Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for " + intent
- + " for user " + userId + ": " + reason);
+ Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for "
+ + debugObject + " for user " + userId + ": " + reason);
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
index 073967e..a8e937c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -23,7 +23,6 @@
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.Pair;
import android.util.SparseArray;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
@@ -229,14 +228,14 @@
DomainVerificationUserState oldUserState =
oldSelectionStates.get(UserHandle.USER_SYSTEM);
- boolean disallowLinkHandling = newUserState.isDisallowLinkHandling();
+ boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed();
if (oldUserState == null) {
oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM,
- newEnabledHosts, disallowLinkHandling);
+ newEnabledHosts, linkHandlingAllowed);
oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
} else {
oldUserState.addHosts(newEnabledHosts)
- .setDisallowLinkHandling(disallowLinkHandling);
+ .setLinkHandlingAllowed(linkHandlingAllowed);
}
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
index 8e82608..2246864 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
@@ -25,10 +25,10 @@
import java.util.Set;
/**
- * Tracks which domains have been explicitly enabled by the user, allowing it to automatically open
- * that domain when a web URL Intent is sent ft.
+ * Tracks which domains have been explicitly enabled by the user, allowing it to open that domain
+ * when a web URL Intent is sent and the application is the highest priority for that domain.
*/
-@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true)
+@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true, genBuilder = false)
public class DomainVerificationUserState {
@UserIdInt
@@ -38,8 +38,10 @@
@NonNull
private final ArraySet<String> mEnabledHosts;
- /** Whether to disallow this package from automatically opening links by auto verification. */
- private boolean mDisallowLinkHandling;
+ /**
+ * Whether to allow this package to automatically open links by auto verification.
+ */
+ private boolean mLinkHandlingAllowed = true;
public DomainVerificationUserState(@UserIdInt int userId) {
mUserId = userId;
@@ -67,13 +69,15 @@
}
+
// Code below generated by codegen v1.0.22.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm
+ // /verify/domain/models/DomainVerificationUserState.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -85,19 +89,21 @@
*
* @param enabledHosts
* List of domains which have been enabled by the user. *
+ * @param linkHandlingAllowed
+ * Whether to allow this package to automatically open links by auto verification.
*/
@DataClass.Generated.Member
public DomainVerificationUserState(
@UserIdInt int userId,
@NonNull ArraySet<String> enabledHosts,
- boolean disallowLinkHandling) {
+ boolean linkHandlingAllowed) {
this.mUserId = userId;
com.android.internal.util.AnnotationValidations.validate(
UserIdInt.class, null, mUserId);
this.mEnabledHosts = enabledHosts;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mEnabledHosts);
- this.mDisallowLinkHandling = disallowLinkHandling;
+ this.mLinkHandlingAllowed = linkHandlingAllowed;
// onConstructed(); // You can define this method to get a callback
}
@@ -115,14 +121,20 @@
return mEnabledHosts;
}
+ /**
+ * Whether to allow this package to automatically open links by auto verification.
+ */
@DataClass.Generated.Member
- public boolean isDisallowLinkHandling() {
- return mDisallowLinkHandling;
+ public boolean isLinkHandlingAllowed() {
+ return mLinkHandlingAllowed;
}
+ /**
+ * Whether to allow this package to automatically open links by auto verification.
+ */
@DataClass.Generated.Member
- public @NonNull DomainVerificationUserState setDisallowLinkHandling( boolean value) {
- mDisallowLinkHandling = value;
+ public @NonNull DomainVerificationUserState setLinkHandlingAllowed( boolean value) {
+ mLinkHandlingAllowed = value;
return this;
}
@@ -135,7 +147,7 @@
return "DomainVerificationUserState { " +
"userId = " + mUserId + ", " +
"enabledHosts = " + mEnabledHosts + ", " +
- "disallowLinkHandling = " + mDisallowLinkHandling +
+ "linkHandlingAllowed = " + mLinkHandlingAllowed +
" }";
}
@@ -154,7 +166,7 @@
return true
&& mUserId == that.mUserId
&& java.util.Objects.equals(mEnabledHosts, that.mEnabledHosts)
- && mDisallowLinkHandling == that.mDisallowLinkHandling;
+ && mLinkHandlingAllowed == that.mLinkHandlingAllowed;
}
@Override
@@ -166,15 +178,15 @@
int _hash = 1;
_hash = 31 * _hash + mUserId;
_hash = 31 * _hash + java.util.Objects.hashCode(mEnabledHosts);
- _hash = 31 * _hash + Boolean.hashCode(mDisallowLinkHandling);
+ _hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed);
return _hash;
}
@DataClass.Generated(
- time = 1608234273324L,
+ time = 1612894390039L,
codegenVersion = "1.0.22",
- sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java",
- inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mDisallowLinkHandling\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true)")
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java",
+ inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mLinkHandlingAllowed\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 88fdc4a..c0b8202 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -5138,10 +5138,15 @@
// Get current time before acquiring the lock so that the calculated end time is as
// accurate as possible.
final long nowElapsed = SystemClock.elapsedRealtime();
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.BATTERY_PREDICTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.DEVICE_POWER, "setBatteryDischargePrediction");
+ }
final long timeRemainingMs = timeRemaining.getDuration().toMillis();
- // A non-positive number means the battery should be dead right now...
+ // A non-positive number means the battery should be dead right now...
Preconditions.checkArgumentPositive(timeRemainingMs,
"Given time remaining is not positive: " + timeRemainingMs);
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 0d43600..15c72b3 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -139,6 +139,7 @@
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
+import com.android.internal.os.KernelCpuBpfTracking;
import com.android.internal.os.KernelCpuThreadReader;
import com.android.internal.os.KernelCpuThreadReaderDiff;
import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
@@ -1455,7 +1456,7 @@
}
private void registerCpuTimePerClusterFreq() {
- if (KernelCpuTotalBpfMapReader.isSupported()) {
+ if (KernelCpuBpfTracking.isSupported()) {
int tagId = FrameworkStatsLog.CPU_TIME_PER_CLUSTER_FREQ;
PullAtomMetadata metadata = new PullAtomMetadata.Builder()
.setAdditiveFields(new int[] {3})
@@ -1649,17 +1650,18 @@
}
private void registerCpuCyclesPerThreadGroupCluster() {
- // TODO(b/173227907): Register only when supported.
- int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER;
- PullAtomMetadata metadata = new PullAtomMetadata.Builder()
- .setAdditiveFields(new int[] {3, 4})
- .build();
- mStatsManager.setPullAtomCallback(
- tagId,
- metadata,
- DIRECT_EXECUTOR,
- mStatsCallbackImpl
- );
+ if (KernelCpuBpfTracking.isSupported()) {
+ int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER;
+ PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+ .setAdditiveFields(new int[] {3, 4})
+ .build();
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ metadata,
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl
+ );
+ }
}
int pullCpuCyclesPerThreadGroupCluster(int atomTag, List<StatsEvent> pulledData) {
@@ -1718,7 +1720,7 @@
}
for (int cluster = 0; cluster < clusters; ++cluster) {
pulledData.add(FrameworkStatsLog.buildStatsEvent(
- atomTag, threadGroup, cluster, aggregatedCycles[cluster],
+ atomTag, threadGroup, cluster, aggregatedCycles[cluster] / 1_000_000L,
aggregatedTimesUs[cluster] / 1_000));
}
}
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 37d13fb..12590eb 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -27,6 +27,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.IpSecManager;
@@ -37,10 +38,12 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgent;
+import android.net.NetworkAgent.ValidationStatus;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier;
+import android.net.Uri;
import android.net.annotations.PolicyDirection;
import android.net.ipsec.ike.ChildSessionCallback;
import android.net.ipsec.ike.ChildSessionConfiguration;
@@ -58,6 +61,9 @@
import android.os.HandlerExecutor;
import android.os.Message;
import android.os.ParcelUuid;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.SystemClock;
import android.util.ArraySet;
import android.util.Slog;
@@ -65,6 +71,7 @@
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
@@ -120,6 +127,15 @@
* +----------------------------+
* </pre>
*
+ * <p>All messages in VcnGatewayConnection <b>should</b> be enqueued using {@link
+ * #sendMessageAndAcquireWakeLock}. Careful consideration should be given to any uses of {@link
+ * #sendMessage} directly, as they are not guaranteed to be processed in a timely manner (due to the
+ * lack of WakeLocks).
+ *
+ * <p>Any attempt to remove messages from the Handler should be done using {@link
+ * #removeEqualMessages}. This is necessary to ensure that the WakeLock is correctly released when
+ * no messages remain in the Handler queue.
+ *
* @hide
*/
public class VcnGatewayConnection extends StateMachine {
@@ -128,6 +144,18 @@
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final String TEARDOWN_TIMEOUT_ALARM = TAG + "_TEARDOWN_TIMEOUT_ALARM";
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final String DISCONNECT_REQUEST_ALARM = TAG + "_DISCONNECT_REQUEST_ALARM";
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM";
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final String SAFEMODE_TIMEOUT_ALARM = TAG + "_SAFEMODE_TIMEOUT_ALARM";
+
private static final int[] MERGED_CAPABILITIES =
new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING};
private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
@@ -138,11 +166,15 @@
private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel";
private static final int TOKEN_ALL = Integer.MIN_VALUE;
- private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final int TEARDOWN_TIMEOUT_SECONDS = 5;
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final int SAFEMODE_TIMEOUT_SECONDS = 30;
+
private interface EventInfo {}
/**
@@ -385,6 +417,23 @@
// TODO(b/178426520): implement handling of this event
private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
+ /**
+ * Sent when this VcnGatewayConnection has entered Safemode.
+ *
+ * <p>A VcnGatewayConnection enters Safemode when it takes over {@link
+ * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}.
+ *
+ * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link
+ * VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down
+ * its VcnGatewayConnectin(s).
+ *
+ * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not
+ * validated yet), and RetryTimeoutState.
+ *
+ * @param arg1 The "all" token; this signal is always honored.
+ */
+ private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10;
+
@VisibleForTesting(visibility = Visibility.PRIVATE)
@NonNull
final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -419,6 +468,21 @@
@Nullable private IpSecTunnelInterface mTunnelIface = null;
+ /**
+ * WakeLock to be held when processing messages on the Handler queue.
+ *
+ * <p>Used to prevent the device from going to sleep while there are VCN-related events to
+ * process for this VcnGatewayConnection.
+ *
+ * <p>Obtain a WakeLock when enquing messages onto the Handler queue. Once all messages in the
+ * Handler queue have been processed, the WakeLock can be released and cleared.
+ *
+ * <p>This WakeLock is also used for handling delayed messages by using WakeupMessages to send
+ * delayed messages to the Handler. When the WakeupMessage fires, it will obtain the WakeLock
+ * before enquing the delayed event to the Handler.
+ */
+ @NonNull private final VcnWakeLock mWakeLock;
+
/** Running state of this VcnGatewayConnection. */
private boolean mIsRunning = true;
@@ -481,7 +545,13 @@
* <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable
* otherwise.
*/
- private NetworkAgent mNetworkAgent;
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ NetworkAgent mNetworkAgent;
+
+ @Nullable private WakeupMessage mTeardownTimeoutAlarm;
+ @Nullable private WakeupMessage mDisconnectRequestAlarm;
+ @Nullable private WakeupMessage mRetryTimeoutAlarm;
+ @Nullable private WakeupMessage mSafemodeTimeoutAlarm;
public VcnGatewayConnection(
@NonNull VcnContext vcnContext,
@@ -518,6 +588,9 @@
mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback();
+ mWakeLock =
+ mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
mUnderlyingNetworkTracker =
mDeps.newUnderlyingNetworkTracker(
mVcnContext,
@@ -544,7 +617,7 @@
* <p>Once torn down, this VcnTunnel CANNOT be started again.
*/
public void teardownAsynchronously() {
- sendMessage(
+ sendMessageAndAcquireWakeLock(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN));
@@ -560,6 +633,13 @@
mTunnelIface.close();
}
+ releaseWakeLock();
+
+ cancelTeardownTimeoutAlarm();
+ cancelDisconnectRequestAlarm();
+ cancelRetryTimeoutAlarm();
+ cancelSafemodeAlarm();
+
mUnderlyingNetworkTracker.teardown();
}
@@ -576,79 +656,324 @@
mLastSnapshot = snapshot;
mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot);
- sendMessage(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
+ sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
}
private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback {
@Override
public void onSelectedUnderlyingNetworkChanged(
@Nullable UnderlyingNetworkRecord underlying) {
+ // TODO(b/180132994): explore safely removing this Thread check
+ mVcnContext.ensureRunningOnLooperThread();
+
// TODO(b/179091925): Move the delayed-message handling to BaseState
// If underlying is null, all underlying networks have been lost. Disconnect VCN after a
// timeout.
if (underlying == null) {
- sendMessageDelayed(
- EVENT_DISCONNECT_REQUESTED,
- TOKEN_ALL,
- new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST),
- TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
- } else if (getHandler() != null) {
- // Cancel any existing disconnect due to loss of underlying network
- // getHandler() can return null if the state machine has already quit. Since this is
- // called from other classes, this condition must be verified.
-
- getHandler()
- .removeEqualMessages(
- EVENT_DISCONNECT_REQUESTED,
- new EventDisconnectRequestedInfo(
- DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+ setDisconnectRequestAlarm();
+ } else {
+ // Received a new Network so any previous alarm is irrelevant - cancel + clear it,
+ // and cancel any queued EVENT_DISCONNECT_REQUEST messages
+ cancelDisconnectRequestAlarm();
}
- sendMessage(
+ sendMessageAndAcquireWakeLock(
EVENT_UNDERLYING_NETWORK_CHANGED,
TOKEN_ALL,
new EventUnderlyingNetworkChangedInfo(underlying));
}
}
- private void sendMessage(int what, int token, EventInfo data) {
+ private void acquireWakeLock() {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ if (mIsRunning) {
+ mWakeLock.acquire();
+ }
+ }
+
+ private void releaseWakeLock() {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ mWakeLock.release();
+ }
+
+ /**
+ * Attempt to release mWakeLock - this can only be done if the Handler is null (meaning the
+ * StateMachine has been shutdown and thus has no business keeping the WakeLock) or if there are
+ * no more messags left to process in the Handler queue (at which point the WakeLock can be
+ * released until more messages must be processed).
+ */
+ private void maybeReleaseWakeLock() {
+ final Handler handler = getHandler();
+ if (handler == null || !handler.hasMessagesOrCallbacks()) {
+ releaseWakeLock();
+ }
+ }
+
+ @Override
+ public void sendMessage(int what) {
+ Slog.wtf(
+ TAG,
+ "sendMessage should not be used in VcnGatewayConnection. See"
+ + " sendMessageAndAcquireWakeLock()");
+ super.sendMessage(what);
+ }
+
+ @Override
+ public void sendMessage(int what, Object obj) {
+ Slog.wtf(
+ TAG,
+ "sendMessage should not be used in VcnGatewayConnection. See"
+ + " sendMessageAndAcquireWakeLock()");
+ super.sendMessage(what, obj);
+ }
+
+ @Override
+ public void sendMessage(int what, int arg1) {
+ Slog.wtf(
+ TAG,
+ "sendMessage should not be used in VcnGatewayConnection. See"
+ + " sendMessageAndAcquireWakeLock()");
+ super.sendMessage(what, arg1);
+ }
+
+ @Override
+ public void sendMessage(int what, int arg1, int arg2) {
+ Slog.wtf(
+ TAG,
+ "sendMessage should not be used in VcnGatewayConnection. See"
+ + " sendMessageAndAcquireWakeLock()");
+ super.sendMessage(what, arg1, arg2);
+ }
+
+ @Override
+ public void sendMessage(int what, int arg1, int arg2, Object obj) {
+ Slog.wtf(
+ TAG,
+ "sendMessage should not be used in VcnGatewayConnection. See"
+ + " sendMessageAndAcquireWakeLock()");
+ super.sendMessage(what, arg1, arg2, obj);
+ }
+
+ @Override
+ public void sendMessage(Message msg) {
+ Slog.wtf(
+ TAG,
+ "sendMessage should not be used in VcnGatewayConnection. See"
+ + " sendMessageAndAcquireWakeLock()");
+ super.sendMessage(msg);
+ }
+
+ // TODO(b/180146061): also override and Log.wtf() other Message handling methods
+ // In mind are sendMessageDelayed(), sendMessageAtFrontOfQueue, removeMessages, and
+ // removeDeferredMessages
+
+ /**
+ * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+ * go to sleep before processing the sent message.
+ */
+ private void sendMessageAndAcquireWakeLock(int what, int token) {
+ acquireWakeLock();
+ super.sendMessage(what, token);
+ }
+
+ /**
+ * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+ * go to sleep before processing the sent message.
+ */
+ private void sendMessageAndAcquireWakeLock(int what, int token, EventInfo data) {
+ acquireWakeLock();
super.sendMessage(what, token, ARG_NOT_PRESENT, data);
}
- private void sendMessage(int what, int token, int arg2, EventInfo data) {
+ /**
+ * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+ * go to sleep before processing the sent message.
+ */
+ private void sendMessageAndAcquireWakeLock(int what, int token, int arg2, EventInfo data) {
+ acquireWakeLock();
super.sendMessage(what, token, arg2, data);
}
- private void sendMessageDelayed(int what, int token, EventInfo data, long timeout) {
- super.sendMessageDelayed(what, token, ARG_NOT_PRESENT, data, timeout);
+ /**
+ * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+ * go to sleep before processing the sent message.
+ */
+ private void sendMessageAndAcquireWakeLock(Message msg) {
+ acquireWakeLock();
+ super.sendMessage(msg);
}
- private void sendMessageDelayed(int what, int token, int arg2, EventInfo data, long timeout) {
- super.sendMessageDelayed(what, token, arg2, data, timeout);
+ /**
+ * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
+ * Handler is empty.
+ *
+ * @param what the Message.what value to be removed
+ */
+ private void removeEqualMessages(int what) {
+ removeEqualMessages(what, null /* obj */);
+ }
+
+ /**
+ * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
+ * Handler is empty.
+ *
+ * @param what the Message.what value to be removed
+ * @param obj the Message.obj to to be removed, or null if all messages matching Message.what
+ * should be removed
+ */
+ private void removeEqualMessages(int what, @Nullable Object obj) {
+ final Handler handler = getHandler();
+ if (handler != null) {
+ handler.removeEqualMessages(what, obj);
+ }
+
+ maybeReleaseWakeLock();
+ }
+
+ private WakeupMessage createScheduledAlarm(
+ @NonNull String cmdName, Message delayedMessage, long delay) {
+ // WakeupMessage uses Handler#dispatchMessage() to immediately handle the specified Runnable
+ // at the scheduled time. dispatchMessage() immediately executes and there may be queued
+ // events that resolve the scheduled alarm pending in the queue. So, use the Runnable to
+ // place the alarm event at the end of the queue with sendMessageAndAcquireWakeLock (which
+ // guarantees the device will stay awake).
+ final WakeupMessage alarm =
+ mDeps.newWakeupMessage(
+ mVcnContext,
+ getHandler(),
+ cmdName,
+ () -> sendMessageAndAcquireWakeLock(delayedMessage));
+ alarm.schedule(mDeps.getElapsedRealTime() + delay);
+ return alarm;
+ }
+
+ private void setTeardownTimeoutAlarm() {
+ // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
+ // either case, there is nothing to cancel.
+ if (mTeardownTimeoutAlarm != null) {
+ Slog.wtf(TAG, "mTeardownTimeoutAlarm should be null before being set");
+ }
+
+ final Message delayedMessage = obtainMessage(EVENT_TEARDOWN_TIMEOUT_EXPIRED, mCurrentToken);
+ mTeardownTimeoutAlarm =
+ createScheduledAlarm(
+ TEARDOWN_TIMEOUT_ALARM,
+ delayedMessage,
+ TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+ }
+
+ private void cancelTeardownTimeoutAlarm() {
+ if (mTeardownTimeoutAlarm != null) {
+ mTeardownTimeoutAlarm.cancel();
+ mTeardownTimeoutAlarm = null;
+ }
+
+ // Cancel any existing teardown timeouts
+ removeEqualMessages(EVENT_TEARDOWN_TIMEOUT_EXPIRED);
+ }
+
+ private void setDisconnectRequestAlarm() {
+ // Only schedule a NEW alarm if none is already set.
+ if (mDisconnectRequestAlarm != null) {
+ return;
+ }
+
+ final Message delayedMessage =
+ obtainMessage(
+ EVENT_DISCONNECT_REQUESTED,
+ TOKEN_ALL,
+ 0 /* arg2 */,
+ new EventDisconnectRequestedInfo(
+ DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+ mDisconnectRequestAlarm =
+ createScheduledAlarm(
+ DISCONNECT_REQUEST_ALARM,
+ delayedMessage,
+ TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
+ }
+
+ private void cancelDisconnectRequestAlarm() {
+ if (mDisconnectRequestAlarm != null) {
+ mDisconnectRequestAlarm.cancel();
+ mDisconnectRequestAlarm = null;
+ }
+
+ // Cancel any existing disconnect due to previous loss of underlying network
+ removeEqualMessages(
+ EVENT_DISCONNECT_REQUESTED,
+ new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+ }
+
+ private void setRetryTimeoutAlarm(long delay) {
+ // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
+ // either case, there is nothing to cancel.
+ if (mRetryTimeoutAlarm != null) {
+ Slog.wtf(TAG, "mRetryTimeoutAlarm should be null before being set");
+ }
+
+ final Message delayedMessage = obtainMessage(EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken);
+ mRetryTimeoutAlarm = createScheduledAlarm(RETRY_TIMEOUT_ALARM, delayedMessage, delay);
+ }
+
+ private void cancelRetryTimeoutAlarm() {
+ if (mRetryTimeoutAlarm != null) {
+ mRetryTimeoutAlarm.cancel();
+ mRetryTimeoutAlarm = null;
+ }
+
+ removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ void setSafemodeAlarm() {
+ // Only schedule a NEW alarm if none is already set.
+ if (mSafemodeTimeoutAlarm != null) {
+ return;
+ }
+
+ final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
+ mSafemodeTimeoutAlarm =
+ createScheduledAlarm(
+ SAFEMODE_TIMEOUT_ALARM,
+ delayedMessage,
+ TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+ }
+
+ private void cancelSafemodeAlarm() {
+ if (mSafemodeTimeoutAlarm != null) {
+ mSafemodeTimeoutAlarm.cancel();
+ mSafemodeTimeoutAlarm = null;
+ }
+
+ removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED);
}
private void sessionLost(int token, @Nullable Exception exception) {
- sendMessage(EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
+ sendMessageAndAcquireWakeLock(
+ EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
}
private void sessionClosed(int token, @Nullable Exception exception) {
// SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the
// Disconnecting state.
sessionLost(token, exception);
- sendMessage(EVENT_SESSION_CLOSED, token);
+ sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token);
}
private void childTransformCreated(
int token, @NonNull IpSecTransform transform, int direction) {
- sendMessage(
+ sendMessageAndAcquireWakeLock(
EVENT_TRANSFORM_CREATED,
token,
new EventTransformCreatedInfo(direction, transform));
}
private void childOpened(int token, @NonNull VcnChildSessionConfiguration childConfig) {
- sendMessage(EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig));
+ sendMessageAndAcquireWakeLock(
+ EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig));
}
private abstract class BaseState extends State {
@@ -658,7 +983,7 @@
enterState();
} catch (Exception e) {
Slog.wtf(TAG, "Uncaught exception", e);
- sendMessage(
+ sendMessageAndAcquireWakeLock(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(
@@ -669,22 +994,47 @@
protected void enterState() throws Exception {}
/**
+ * Returns whether the given token is valid.
+ *
+ * <p>By default, States consider any and all token to be 'valid'.
+ *
+ * <p>States should override this method if they want to restrict message handling to
+ * specific tokens.
+ */
+ protected boolean isValidToken(int token) {
+ return true;
+ }
+
+ /**
* Top-level processMessage with safeguards to prevent crashing the System Server on non-eng
* builds.
+ *
+ * <p>Here be dragons: processMessage() is final to ensure that mWakeLock is released once
+ * the Handler queue is empty. Future changes (or overrides) to processMessage() to MUST
+ * ensure that mWakeLock is correctly released.
*/
@Override
- public boolean processMessage(Message msg) {
+ public final boolean processMessage(Message msg) {
+ final int token = msg.arg1;
+ if (!isValidToken(token)) {
+ Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what);
+ return HANDLED;
+ }
+
try {
processStateMsg(msg);
} catch (Exception e) {
Slog.wtf(TAG, "Uncaught exception", e);
- sendMessage(
+ sendMessageAndAcquireWakeLock(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(
DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
}
+ // Attempt to release the WakeLock - only possible if the Handler queue is empty
+ maybeReleaseWakeLock();
+
return HANDLED;
}
@@ -696,7 +1046,7 @@
exitState();
} catch (Exception e) {
Slog.wtf(TAG, "Uncaught exception", e);
- sendMessage(
+ sendMessageAndAcquireWakeLock(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(
@@ -774,6 +1124,8 @@
if (mIkeSession != null || mNetworkAgent != null) {
Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
}
+
+ cancelSafemodeAlarm();
}
@Override
@@ -797,27 +1149,16 @@
break;
}
}
+
+ @Override
+ protected void exitState() {
+ // Safe to blindly set up, as it is cancelled and cleared on entering this state
+ setSafemodeAlarm();
+ }
}
private abstract class ActiveBaseState extends BaseState {
- /**
- * Handles all incoming messages, discarding messages for previous networks.
- *
- * <p>States that handle mobility events may need to override this method to receive
- * messages for all underlying networks.
- */
@Override
- public boolean processMessage(Message msg) {
- final int token = msg.arg1;
- // Only process if a valid token is presented.
- if (isValidToken(token)) {
- return super.processMessage(msg);
- }
-
- Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what);
- return HANDLED;
- }
-
protected boolean isValidToken(int token) {
return (token == TOKEN_ALL || token == mCurrentToken);
}
@@ -848,7 +1189,7 @@
protected void enterState() throws Exception {
if (mIkeSession == null) {
Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state.");
- sendMessage(EVENT_SESSION_CLOSED, mCurrentToken);
+ sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, mCurrentToken);
return;
}
@@ -860,10 +1201,9 @@
}
mIkeSession.close();
- sendMessageDelayed(
- EVENT_TEARDOWN_TIMEOUT_EXPIRED,
- mCurrentToken,
- TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+
+ // Safe to blindly set up, as it is cancelled and cleared on exiting this state
+ setTeardownTimeoutAlarm();
}
@Override
@@ -905,6 +1245,10 @@
transitionTo(mDisconnectedState);
}
break;
+ case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+ mGatewayStatusCallback.onEnteredSafemode();
+ mSafemodeTimeoutAlarm = null;
+ break;
default:
logUnhandledMessage(msg);
break;
@@ -914,6 +1258,8 @@
@Override
protected void exitState() throws Exception {
mSkipRetryTimeout = false;
+
+ cancelTeardownTimeoutAlarm();
}
}
@@ -985,6 +1331,10 @@
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
break;
+ case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+ mGatewayStatusCallback.onEnteredSafemode();
+ mSafemodeTimeoutAlarm = null;
+ break;
default:
logUnhandledMessage(msg);
break;
@@ -1028,6 +1378,14 @@
public void unwanted() {
teardownAsynchronously();
}
+
+ @Override
+ public void onValidationStatus(
+ @ValidationStatus int status, @Nullable Uri redirectUri) {
+ if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
+ clearFailedAttemptCounterAndSafeModeAlarm();
+ }
+ }
};
agent.register();
@@ -1036,6 +1394,14 @@
return agent;
}
+ protected void clearFailedAttemptCounterAndSafeModeAlarm() {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ // Validated connection, clear failed attempt counter
+ mFailedAttempts = 0;
+ cancelSafemodeAlarm();
+ }
+
protected void applyTransform(
int token,
@NonNull IpSecTunnelInterface tunnelIface,
@@ -1043,7 +1409,7 @@
@NonNull IpSecTransform transform,
int direction) {
try {
- // TODO: Set underlying network of tunnel interface
+ // TODO(b/180163196): Set underlying network of tunnel interface
// Transforms do not need to be persisted; the IkeSession will keep them alive
mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
@@ -1115,9 +1481,6 @@
teardownAsynchronously();
}
}
-
- // Successful connection, clear failed attempt counter
- mFailedAttempts = 0;
}
@Override
@@ -1154,6 +1517,10 @@
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
break;
+ case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+ mGatewayStatusCallback.onEnteredSafemode();
+ mSafemodeTimeoutAlarm = null;
+ break;
default:
logUnhandledMessage(msg);
break;
@@ -1173,6 +1540,7 @@
// mUnderlying assumed non-null, given check above.
// If network changed, migrate. Otherwise, update any existing networkAgent.
if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) {
+ Slog.v(TAG, "Migrating to new network: " + mUnderlying.network);
mIkeSession.setNetwork(mUnderlying.network);
} else {
// oldUnderlying is non-null & underlying network itself has not changed
@@ -1196,8 +1564,19 @@
mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig);
} else {
updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig);
+
+ // mNetworkAgent not null, so the VCN Network has already been established. Clear
+ // the failed attempt counter and safe mode alarm since this transition is complete.
+ clearFailedAttemptCounterAndSafeModeAlarm();
}
}
+
+ @Override
+ protected void exitState() {
+ // Attempt to set the safe mode alarm - this requires the Vcn Network being validated
+ // while in ConnectedState (which cancels the previous alarm)
+ setSafemodeAlarm();
+ }
}
/**
@@ -1215,8 +1594,8 @@
Slog.wtf(TAG, "Underlying network was null in retry state");
transitionTo(mDisconnectedState);
} else {
- sendMessageDelayed(
- EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken, getNextRetryIntervalsMs());
+ // Safe to blindly set up, as it is cancelled and cleared on exiting this state
+ setRetryTimeoutAlarm(getNextRetryIntervalsMs());
}
}
@@ -1229,8 +1608,6 @@
// If new underlying is null, all networks were lost; go back to disconnected.
if (mUnderlying == null) {
- removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
-
transitionTo(mDisconnectedState);
return;
} else if (oldUnderlying != null
@@ -1241,19 +1618,26 @@
// Fallthrough
case EVENT_RETRY_TIMEOUT_EXPIRED:
- removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
-
transitionTo(mConnectingState);
break;
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
break;
+ case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+ mGatewayStatusCallback.onEnteredSafemode();
+ mSafemodeTimeoutAlarm = null;
+ break;
default:
logUnhandledMessage(msg);
break;
}
}
+ @Override
+ public void exitState() {
+ cancelRetryTimeoutAlarm();
+ }
+
private long getNextRetryIntervalsMs() {
final int retryDelayIndex = mFailedAttempts - 1;
final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMs();
@@ -1425,6 +1809,15 @@
}
@Override
+ public void onIpSecTransformsMigrated(
+ @NonNull IpSecTransform inIpSecTransform,
+ @NonNull IpSecTransform outIpSecTransform) {
+ Slog.v(TAG, "ChildTransformsMigrated; token " + mToken);
+ onIpSecTransformCreated(inIpSecTransform, IpSecManager.DIRECTION_IN);
+ onIpSecTransformCreated(outIpSecTransform, IpSecManager.DIRECTION_OUT);
+ }
+
+ @Override
public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) {
// Nothing to be done; no references to the IpSecTransform are held, and this transform
// will be closed by the IKE library.
@@ -1526,6 +1919,26 @@
ikeSessionCallback,
childSessionCallback);
}
+
+ /** Builds a new WakeLock. */
+ public VcnWakeLock newWakeLock(
+ @NonNull Context context, int wakeLockFlag, @NonNull String wakeLockTag) {
+ return new VcnWakeLock(context, wakeLockFlag, wakeLockTag);
+ }
+
+ /** Builds a new WakeupMessage. */
+ public WakeupMessage newWakeupMessage(
+ @NonNull VcnContext vcnContext,
+ @NonNull Handler handler,
+ @NonNull String tag,
+ @NonNull Runnable runnable) {
+ return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable);
+ }
+
+ /** Gets the elapsed real time since boot, in millis. */
+ public long getElapsedRealTime() {
+ return SystemClock.elapsedRealtime();
+ }
}
/**
@@ -1605,4 +2018,34 @@
mImpl.setNetwork(network);
}
}
+
+ /** Proxy Implementation of WakeLock, used for testing. */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class VcnWakeLock {
+ private final WakeLock mImpl;
+
+ public VcnWakeLock(@NonNull Context context, int flags, @NonNull String tag) {
+ final PowerManager powerManager = context.getSystemService(PowerManager.class);
+ mImpl = powerManager.newWakeLock(flags, tag);
+ mImpl.setReferenceCounted(false /* isReferenceCounted */);
+ }
+
+ /**
+ * Acquire this WakeLock.
+ *
+ * <p>Synchronize this action to minimize locking around WakeLock use.
+ */
+ public synchronized void acquire() {
+ mImpl.acquire();
+ }
+
+ /**
+ * Release this Wakelock.
+ *
+ * <p>Synchronize this action to minimize locking around WakeLock use.
+ */
+ public synchronized void release() {
+ mImpl.release();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 260d79a..1750854 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -352,10 +352,7 @@
return;
}
- VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
- mBatteryStatsService, mVibrationCallbacks);
-
- ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vibThread);
+ ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vib);
if (ignoreStatus != null) {
endVibrationLocked(vib, ignoreStatus);
return;
@@ -366,7 +363,7 @@
if (mCurrentVibration != null) {
mCurrentVibration.cancel();
}
- Vibration.Status status = startVibrationLocked(vibThread);
+ Vibration.Status status = startVibrationLocked(vib);
if (status != Vibration.Status.RUNNING) {
endVibrationLocked(vib, status);
}
@@ -491,19 +488,19 @@
}
@GuardedBy("mLock")
- private Vibration.Status startVibrationLocked(VibrationThread vibThread) {
+ private Vibration.Status startVibrationLocked(Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
- Vibration vib = vibThread.getVibration();
vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
-
boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
-
if (inputDevicesAvailable) {
return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
}
+ VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
+ mBatteryStatsService, mVibrationCallbacks);
+
if (mCurrentVibration == null) {
return startVibrationThreadLocked(vibThread);
}
@@ -595,8 +592,8 @@
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.Status shouldIgnoreVibrationForCurrentLocked(VibrationThread vibThread) {
- if (vibThread.getVibration().isRepeating()) {
+ private Vibration.Status shouldIgnoreVibrationForCurrentLocked(Vibration vibration) {
+ if (vibration.isRepeating()) {
// Repeating vibrations always take precedence.
return null;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5697564..370d921 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -31,6 +31,7 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.ILocalWallpaperColorConsumer;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
import android.app.PendingIntent;
@@ -59,6 +60,7 @@
import android.graphics.BitmapRegionDecoder;
import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Bundle;
@@ -85,7 +87,10 @@
import android.service.wallpaper.WallpaperService;
import android.system.ErrnoException;
import android.system.Os;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.EventLog;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -105,7 +110,6 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.WindowManagerInternal;
@@ -136,6 +140,8 @@
private static final String TAG = "WallpaperManagerService";
private static final boolean DEBUG = false;
private static final boolean DEBUG_LIVE = true;
+ private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+ new RectF(0, 0, 1, 1);
public static class Lifecycle extends SystemService {
private IWallpaperManagerService mService;
@@ -866,6 +872,12 @@
private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
private int mCurrentUserId = UserHandle.USER_NULL;
private boolean mInAmbientMode;
+ private ArrayMap<IBinder, ArraySet<RectF>> mLocalColorCallbackAreas =
+ new ArrayMap<>();
+ private ArrayMap<RectF, RemoteCallbackList<ILocalWallpaperColorConsumer>>
+ mLocalColorAreaCallbacks = new ArrayMap<>();
+ private ArrayMap<Integer, ArraySet<RectF>> mLocalColorDisplayIdAreas = new ArrayMap<>();
+ private ArrayMap<IBinder, Integer> mLocalColorCallbackDisplayId = new ArrayMap<>();
static class WallpaperData {
@@ -1276,6 +1288,32 @@
}
@Override
+ public void onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors,
+ int displayId) {
+ forEachDisplayConnector(displayConnector -> {
+ if (displayConnector.mDisplayId == displayId) {
+ RemoteCallbackList<ILocalWallpaperColorConsumer> callbacks;
+ ArrayMap<IBinder, Integer> callbackDisplayIds;
+ synchronized (mLock) {
+ callbacks = mLocalColorAreaCallbacks.get(area);
+ callbackDisplayIds = new ArrayMap<>(mLocalColorCallbackDisplayId);
+ }
+ if (callbacks == null) return;
+ callbacks.broadcast(c -> {
+ try {
+ int targetDisplayId =
+ callbackDisplayIds.get(c.asBinder());
+ if (targetDisplayId == displayId) c.onColorsChanged(area, colors);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+ });
+ }
+
+
+ @Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
Slog.w(TAG, "Wallpaper service gone: " + name);
@@ -1437,6 +1475,15 @@
} catch (RemoteException e) {
Slog.w(TAG, "Failed to request wallpaper colors", e);
}
+
+ ArraySet<RectF> areas = mLocalColorDisplayIdAreas.get(displayId);
+ if (areas != null && areas.size() != 0) {
+ try {
+ connector.mEngine.addLocalColorsAreas(new ArrayList<>(areas));
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to register local colors areas", e);
+ }
+ }
}
}
@@ -2340,6 +2387,115 @@
return true;
}
+ private IWallpaperEngine getEngine(int which, int userId, int displayId) {
+ WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId);
+ if (wallpaperData == null) return null;
+ IWallpaperEngine engine = null;
+ synchronized (mLock) {
+ for (int i = 0; i < wallpaperData.connection.mDisplayConnector.size(); i++) {
+ int id = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
+ int currentWhich = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
+ if (id != displayId && currentWhich != which) continue;
+ engine = wallpaperData.connection.mDisplayConnector.get(i).mEngine;
+ break;
+ }
+ }
+ return engine;
+ }
+
+ @Override
+ public void addOnLocalColorsChangedListener(@NonNull ILocalWallpaperColorConsumer callback,
+ @NonNull List<RectF> regions, int which, int userId, int displayId)
+ throws RemoteException {
+ if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
+ throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
+ }
+ IWallpaperEngine engine = getEngine(which, userId, displayId);
+ if (engine == null) return;
+ ArrayList<RectF> validAreas = new ArrayList<>(regions.size());
+ synchronized (mLock) {
+ ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback);
+ if (areas == null) areas = new ArraySet<>(regions.size());
+ areas.addAll(regions);
+ mLocalColorCallbackAreas.put(callback.asBinder(), areas);
+ }
+ for (int i = 0; i < regions.size(); i++) {
+ if (!LOCAL_COLOR_BOUNDS.contains(regions.get(i))) {
+ continue;
+ }
+ RemoteCallbackList callbacks;
+ synchronized (mLock) {
+ callbacks = mLocalColorAreaCallbacks.get(
+ regions.get(i));
+ if (callbacks == null) {
+ callbacks = new RemoteCallbackList();
+ mLocalColorAreaCallbacks.put(regions.get(i), callbacks);
+ }
+ mLocalColorCallbackDisplayId.put(callback.asBinder(), displayId);
+ ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
+ if (displayAreas == null) {
+ displayAreas = new ArraySet<>(1);
+ mLocalColorDisplayIdAreas.put(displayId, displayAreas);
+ }
+ displayAreas.add(regions.get(i));
+ }
+ validAreas.add(regions.get(i));
+ callbacks.register(callback);
+ }
+ engine.addLocalColorsAreas(validAreas);
+ }
+
+ @Override
+ public void removeOnLocalColorsChangedListener(
+ @NonNull ILocalWallpaperColorConsumer callback, int which, int userId,
+ int displayId) throws RemoteException {
+ if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
+ throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
+ }
+ final UserHandle callingUser = Binder.getCallingUserHandle();
+ if (callingUser.getIdentifier() != userId) {
+ throw new SecurityException("calling user id does not match");
+ }
+ final long identity = Binder.clearCallingIdentity();
+ ArrayList<RectF> removeAreas = new ArrayList<>();
+ ArrayList<Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>>
+ callbacksToRemove = new ArrayList<>();
+ try {
+ synchronized (mLock) {
+ ArraySet<RectF> areas = mLocalColorCallbackAreas.remove(callback.asBinder());
+ mLocalColorCallbackDisplayId.remove(callback.asBinder());
+ if (areas == null) areas = new ArraySet<>();
+ for (RectF area : areas) {
+ RemoteCallbackList callbacks = mLocalColorAreaCallbacks.get(area);
+ if (callbacks == null) continue;
+ callbacksToRemove.add(new Pair<>(callbacks, callback));
+ if (callbacks.getRegisteredCallbackCount() == 0) {
+ mLocalColorAreaCallbacks.remove(area);
+ removeAreas.add(area);
+ }
+ ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
+ if (displayAreas != null) {
+ displayAreas.remove(area);
+ }
+ }
+ }
+ for (int i = 0; i < callbacksToRemove.size(); i++) {
+ Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>
+ pair = callbacksToRemove.get(i);
+ pair.first.unregister(pair.second);
+ }
+ } catch (Exception e) {
+ // ignore any exception
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ if (removeAreas.size() == 0) return;
+ IWallpaperEngine engine = getEngine(which, userId, displayId);
+ if (engine == null) return;
+ engine.removeLocalColorsAreas(removeAreas);
+ }
+
@Override
public WallpaperColors getWallpaperColors(int which, int userId, int displayId)
throws RemoteException {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f440e56..9bf6df4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5378,6 +5378,7 @@
}
}
+ mDisplayContent.handleActivitySizeCompatModeIfNeeded(this);
mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e51d690..86968ed 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -240,6 +240,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -663,12 +664,8 @@
*/
private boolean mRemoved;
- /**
- * Non-null if the last size compatibility mode activity is using non-native screen
- * configuration. The activity is not able to put in multi-window mode, so it exists only one
- * per display.
- */
- private ActivityRecord mLastCompatModeActivity;
+ /** Set of activities in foreground size compat mode. */
+ private Set<ActivityRecord> mActiveSizeCompatActivities = new ArraySet<>();
// Used in updating the display size
private Point mTmpDisplaySize = new Point();
@@ -5553,24 +5550,23 @@
/** Checks whether the given activity is in size compatibility mode and notifies the change. */
void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) {
final Task organizedTask = r.getOrganizedTask();
- if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
- || organizedTask == null) {
- // The callback is only interested in the foreground changes of fullscreen activity.
+ if (organizedTask == null) {
+ mActiveSizeCompatActivities.remove(r);
return;
}
- // TODO(b/178327644) Update for per Task size compat
- if (!r.inSizeCompatMode()) {
- if (mLastCompatModeActivity != null) {
+
+ if (r.isState(RESUMED) && r.inSizeCompatMode()) {
+ if (mActiveSizeCompatActivities.add(r)) {
+ // Trigger task event for new size compat activity.
organizedTask.onSizeCompatActivityChanged();
}
- mLastCompatModeActivity = null;
return;
}
- if (mLastCompatModeActivity == r) {
- return;
+
+ if (mActiveSizeCompatActivities.remove(r)) {
+ // Trigger task event for activity no longer in foreground size compat.
+ organizedTask.onSizeCompatActivityChanged();
}
- mLastCompatModeActivity = r;
- organizedTask.onSizeCompatActivityChanged();
}
boolean isUidPresent(int uid) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8bd4dfd..66d5c77 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5855,12 +5855,8 @@
mTaskSupervisor.acquireLaunchWakelock();
}
- if (didAutoPip) {
- // Already entered PIP mode, no need to keep pausing.
- return true;
- }
-
- if (mPausingActivity != null) {
+ // If already entered PIP mode, no need to keep pausing.
+ if (mPausingActivity != null && !didAutoPip) {
// Have the window manager pause its key dispatching until the new
// activity has started. If we're pausing the activity just because
// the screen is being turned off and the UI is sleeping, don't interrupt
@@ -5883,9 +5879,9 @@
}
} else {
- // This activity failed to schedule the
- // pause, so just treat it as being paused now.
- ProtoLog.v(WM_DEBUG_STATES, "Activity not running, resuming next.");
+ // This activity either failed to schedule the pause or it entered PIP mode,
+ // so just treat it as being paused now.
+ ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next.");
if (resuming == null) {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 286d31e..c0bce6b 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -674,7 +674,7 @@
// Remove and add for re-ordering.
mPendingTaskEvents.remove(pending);
}
- pending.mForce = force;
+ pending.mForce |= force;
mPendingTaskEvents.add(pending);
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 3012d73..11e3ecf 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -30,6 +30,7 @@
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
"com_android_server_ConsumerIrService.cpp",
"com_android_server_devicepolicy_CryptoTestHelper.cpp",
+ "com_android_server_connectivity_Vpn.cpp",
"com_android_server_gpu_GpuService.cpp",
"com_android_server_HardwarePropertiesManagerService.cpp",
"com_android_server_input_InputManagerService.cpp",
diff --git a/packages/Connectivity/service/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp
similarity index 100%
rename from packages/Connectivity/service/jni/com_android_server_connectivity_Vpn.cpp
rename to services/core/jni/com_android_server_connectivity_Vpn.cpp
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 24221e7..1815f0c 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -41,6 +41,7 @@
int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env);
int register_android_server_vibrator_VibratorManagerService(JavaVM* vm, JNIEnv* env);
int register_android_server_location_GnssLocationProvider(JNIEnv* env);
+int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
int register_android_server_tv_TvUinputBridge(JNIEnv* env);
int register_android_server_tv_TvInputHal(JNIEnv* env);
@@ -91,6 +92,7 @@
register_android_server_vibrator_VibratorManagerService(vm, env);
register_android_server_SystemServer(env);
register_android_server_location_GnssLocationProvider(env);
+ register_android_server_connectivity_Vpn(env);
register_android_server_devicepolicy_CryptoTestHelper(env);
register_android_server_ConsumerIrService(env);
register_android_server_BatteryStatsService(env);
diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
index e27e1b8..1406dbb 100644
--- a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
+++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
@@ -27,6 +27,13 @@
<xs:attribute type="xs:boolean" name="enabled" use="required" />
</xs:complexType>
+ <xs:complexType name="raw-override-value">
+ <xs:attribute type="xs:string" name="packageName" use="required" />
+ <xs:attribute type="xs:long" name="minVersionCode" />
+ <xs:attribute type="xs:long" name="maxVersionCode" />
+ <xs:attribute type="xs:boolean" name="enabled" use="required" />
+ </xs:complexType>
+
<xs:complexType name="change-overrides">
<xs:attribute type="xs:long" name="changeId" use="required"/>
<xs:element name="validated">
@@ -43,6 +50,13 @@
</xs:sequence>
</xs:complexType>
</xs:element>
+ <xs:element name="raw">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="raw-override-value" type="raw-override-value" maxOccurs="unbounded" minOccurs="0" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
</xs:complexType>
<xs:element name="overrides">
diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt
index 08b8207..a5ccffc 100644
--- a/services/core/xsd/platform-compat/overrides/schema/current.txt
+++ b/services/core/xsd/platform-compat/overrides/schema/current.txt
@@ -5,9 +5,11 @@
ctor public ChangeOverrides();
method public long getChangeId();
method public com.android.server.compat.overrides.ChangeOverrides.Deferred getDeferred();
+ method public com.android.server.compat.overrides.ChangeOverrides.Raw getRaw();
method public com.android.server.compat.overrides.ChangeOverrides.Validated getValidated();
method public void setChangeId(long);
method public void setDeferred(com.android.server.compat.overrides.ChangeOverrides.Deferred);
+ method public void setRaw(com.android.server.compat.overrides.ChangeOverrides.Raw);
method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated);
}
@@ -16,6 +18,11 @@
method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
}
+ public static class ChangeOverrides.Raw {
+ ctor public ChangeOverrides.Raw();
+ method public java.util.List<com.android.server.compat.overrides.RawOverrideValue> getRawOverrideValue();
+ }
+
public static class ChangeOverrides.Validated {
ctor public ChangeOverrides.Validated();
method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
@@ -34,6 +41,18 @@
method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides();
}
+ public class RawOverrideValue {
+ ctor public RawOverrideValue();
+ method public boolean getEnabled();
+ method public long getMaxVersionCode();
+ method public long getMinVersionCode();
+ method public String getPackageName();
+ method public void setEnabled(boolean);
+ method public void setMaxVersionCode(long);
+ method public void setMinVersionCode(long);
+ method public void setPackageName(String);
+ }
+
public class XmlParser {
ctor public XmlParser();
method public static com.android.server.compat.overrides.Overrides read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 59b7367..e76597d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -138,6 +138,7 @@
private static final String TAG_ENROLLMENT_SPECIFIC_ID = "enrollment-specific-id";
private static final String TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS =
"admin-can-grant-sensors-permissions";
+ private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling";
private static final String ATTR_VALUE = "value";
private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -281,6 +282,9 @@
public String mEnrollmentSpecificId;
public boolean mAdminCanGrantSensorsPermissions;
+ private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true;
+ boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT;
+
ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
this.info = info;
this.isParent = isParent;
@@ -548,6 +552,9 @@
}
writeAttributeValueToXml(out, TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS,
mAdminCanGrantSensorsPermissions);
+ if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) {
+ writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled);
+ }
}
void writeTextToXml(TypedXmlSerializer out, String tag, String text) throws IOException {
@@ -800,6 +807,9 @@
} else if (TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS.equals(tag)) {
mAdminCanGrantSensorsPermissions = parser.getAttributeBoolean(null, ATTR_VALUE,
false);
+ } else if (TAG_USB_DATA_SIGNALING.equals(tag)) {
+ mUsbDataSignalingEnabled = parser.getAttributeBoolean(null, ATTR_VALUE,
+ USB_DATA_SIGNALING_ENABLED_DEFAULT);
} else {
Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown admin tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -1154,5 +1164,8 @@
pw.print("mAdminCanGrantSensorsPermissions");
pw.println(mAdminCanGrantSensorsPermissions);
+
+ pw.print("mUsbDataSignaling=");
+ pw.println(mUsbDataSignalingEnabled);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9a692be7..604d1d6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -214,6 +214,7 @@
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
+import android.hardware.usb.UsbManager;
import android.location.LocationManager;
import android.media.AudioManager;
import android.media.IAudioService;
@@ -421,10 +422,12 @@
DELEGATION_CERT_SELECTION,
};
- // Subset of delegations that can only be delegated by Device Owner.
- private static final List<String> DEVICE_OWNER_DELEGATIONS = Arrays.asList(new String[] {
- DELEGATION_NETWORK_LOGGING,
- });
+ // Subset of delegations that can only be delegated by Device Owner or Profile Owner of a
+ // managed profile.
+ private static final List<String> DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS =
+ Arrays.asList(new String[]{
+ DELEGATION_NETWORK_LOGGING,
+ });
// Subset of delegations that only one single package within a given user can hold
private static final List<String> EXCLUSIVE_DELEGATIONS = Arrays.asList(new String[] {
@@ -781,8 +784,7 @@
* however it's too early in the boot process to register with IIpConnectivityMetrics
* to listen for events.
*/
- if (Intent.ACTION_USER_STARTED.equals(action)
- && userHandle == mOwners.getDeviceOwnerUserId()) {
+ if (Intent.ACTION_USER_STARTED.equals(action) && userHandle == UserHandle.USER_SYSTEM) {
synchronized (getLockObject()) {
if (isNetworkLoggingEnabledInternalLocked()) {
setNetworkLoggingActiveInternal(true);
@@ -1357,6 +1359,10 @@
return mContext.getSystemService(WifiManager.class);
}
+ UsbManager getUsbManager() {
+ return mContext.getSystemService(UsbManager.class);
+ }
+
@SuppressWarnings("AndroidFrameworkBinderIdentity")
long binderClearCallingIdentity() {
return Binder.clearCallingIdentity();
@@ -2928,6 +2934,7 @@
revertTransferOwnershipIfNecessaryLocked();
}
+ updateUsbDataSignal();
}
private void revertTransferOwnershipIfNecessaryLocked() {
@@ -5879,10 +5886,10 @@
}
// Retrieve the user ID of the calling process.
final int userId = caller.getUserId();
- final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS);
// Ensure calling process is device/profile owner.
- if (hasDoDelegation) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ if (!Collections.disjoint(scopes, DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())));
} else {
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
}
@@ -7527,6 +7534,20 @@
sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent);
}
+ void sendDeviceOwnerOrProfileOwnerCommand(String action, Bundle extras, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ userId = UserHandle.USER_SYSTEM;
+ }
+ ComponentName receiverComponent = null;
+ if (action.equals(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE)) {
+ receiverComponent = resolveDelegateReceiver(DELEGATION_NETWORK_LOGGING, action, userId);
+ }
+ if (receiverComponent == null) {
+ receiverComponent = getOwnerComponent(userId);
+ }
+ sendActiveAdminCommand(action, extras, userId, receiverComponent);
+ }
+
private void sendProfileOwnerCommand(String action, Bundle extras, @UserIdInt int userId) {
sendActiveAdminCommand(action, extras, userId,
mOwners.getProfileOwnerComponent(userId));
@@ -8351,6 +8372,7 @@
deleteTransferOwnershipBundleLocked(userId);
toggleBackupServiceActive(userId, true);
applyManagedProfileRestrictionIfDeviceOwnerLocked();
+ setNetworkLoggingActiveInternal(false);
}
@Override
@@ -14131,7 +14153,9 @@
return;
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isDeviceOwner(caller)
+ || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
synchronized (getLockObject()) {
@@ -14139,11 +14163,11 @@
// already in the requested state
return;
}
- ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- deviceOwner.isNetworkLoggingEnabled = enabled;
+ final ActiveAdmin activeAdmin = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
+ activeAdmin.isNetworkLoggingEnabled = enabled;
if (!enabled) {
- deviceOwner.numNetworkLoggingNotifications = 0;
- deviceOwner.lastNetworkLoggingNotificationTimeMs = 0;
+ activeAdmin.numNetworkLoggingNotifications = 0;
+ activeAdmin.lastNetworkLoggingNotificationTimeMs = 0;
}
saveSettingsLocked(caller.getUserId());
setNetworkLoggingActiveInternal(enabled);
@@ -14161,7 +14185,13 @@
synchronized (getLockObject()) {
mInjector.binderWithCleanCallingIdentity(() -> {
if (active) {
- mNetworkLogger = new NetworkLogger(this, mInjector.getPackageManagerInternal());
+ if (mNetworkLogger == null) {
+ final int affectedUserId = getNetworkLoggingAffectedUser();
+ mNetworkLogger = new NetworkLogger(this,
+ mInjector.getPackageManagerInternal(),
+ affectedUserId == UserHandle.USER_SYSTEM
+ ? UserHandle.USER_ALL : affectedUserId);
+ }
if (!mNetworkLogger.startNetworkLogging()) {
mNetworkLogger = null;
Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging"
@@ -14181,6 +14211,25 @@
}
}
+ private @UserIdInt int getNetworkLoggingAffectedUser() {
+ synchronized (getLockObject()) {
+ if (mOwners.hasDeviceOwner()) {
+ return mOwners.getDeviceOwnerUserId();
+ } else {
+ return mInjector.binderWithCleanCallingIdentity(
+ () -> getManagedUserId(UserHandle.USER_SYSTEM));
+ }
+ }
+ }
+
+ private ActiveAdmin getNetworkLoggingControllingAdminLocked() {
+ int affectedUserId = getNetworkLoggingAffectedUser();
+ if (affectedUserId < 0) {
+ return null;
+ }
+ return getDeviceOrProfileOwnerAdminLocked(affectedUserId);
+ }
+
@Override
public long forceNetworkLogs() {
Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()),
@@ -14201,10 +14250,12 @@
@GuardedBy("getLockObject()")
private void maybePauseDeviceWideLoggingLocked() {
if (!areAllUsersAffiliatedWithDeviceLocked()) {
- Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be "
- + "paused if enabled.");
- if (mNetworkLogger != null) {
- mNetworkLogger.pause();
+ if (mOwners.hasDeviceOwner()) {
+ Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be "
+ + "paused if enabled.");
+ if (mNetworkLogger != null) {
+ mNetworkLogger.pause();
+ }
}
if (!isOrganizationOwnedDeviceWithManagedProfile()) {
Slog.i(LOG_TAG, "Not org-owned managed profile device, security logging will be "
@@ -14223,7 +14274,9 @@
if (allUsersAffiliated || orgOwnedProfileDevice) {
mSecurityLogMonitor.resume();
}
- if (allUsersAffiliated) {
+ // If there is no device owner, then per-user network logging may be enabled for the
+ // managed profile. In which case, all users do not need to be affiliated.
+ if (allUsersAffiliated || !mOwners.hasDeviceOwner()) {
if (mNetworkLogger != null) {
mNetworkLogger.resume();
}
@@ -14250,7 +14303,9 @@
return false;
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isDeviceOwner(caller)
+ || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))
|| hasCallingOrSelfPermission(permission.MANAGE_USERS));
@@ -14260,8 +14315,8 @@
}
private boolean isNetworkLoggingEnabledInternalLocked() {
- ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- return (deviceOwner != null) && deviceOwner.isNetworkLoggingEnabled;
+ ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked();
+ return (activeAdmin != null) && activeAdmin.isNetworkLoggingEnabled;
}
/*
@@ -14278,9 +14333,13 @@
return null;
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isDeviceOwner(caller)
+ || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
- checkAllUsersAreAffiliatedWithDevice();
+ if (mOwners.hasDeviceOwner()) {
+ checkAllUsersAreAffiliatedWithDevice();
+ }
synchronized (getLockObject()) {
if (mNetworkLogger == null || !isNetworkLoggingEnabledInternalLocked()) {
@@ -14293,34 +14352,35 @@
.write();
final long currentTime = System.currentTimeMillis();
- DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
+ DevicePolicyData policyData = getUserData(caller.getUserId());
if (currentTime > policyData.mLastNetworkLogsRetrievalTime) {
policyData.mLastNetworkLogsRetrievalTime = currentTime;
- saveSettingsLocked(UserHandle.USER_SYSTEM);
+ saveSettingsLocked(caller.getUserId());
}
return mNetworkLogger.retrieveLogs(batchToken);
}
}
private void sendNetworkLoggingNotificationLocked() {
- final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- if (deviceOwner == null || !deviceOwner.isNetworkLoggingEnabled) {
+ ensureLocked();
+ final ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked();
+ if (activeAdmin == null || !activeAdmin.isNetworkLoggingEnabled) {
return;
}
- if (deviceOwner.numNetworkLoggingNotifications >=
- ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
+ if (activeAdmin.numNetworkLoggingNotifications
+ >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
return;
}
final long now = System.currentTimeMillis();
- if (now - deviceOwner.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) {
+ if (now - activeAdmin.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) {
return;
}
- deviceOwner.numNetworkLoggingNotifications++;
- if (deviceOwner.numNetworkLoggingNotifications
+ activeAdmin.numNetworkLoggingNotifications++;
+ if (activeAdmin.numNetworkLoggingNotifications
>= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
- deviceOwner.lastNetworkLoggingNotificationTimeMs = 0;
+ activeAdmin.lastNetworkLoggingNotificationTimeMs = 0;
} else {
- deviceOwner.lastNetworkLoggingNotificationTimeMs = now;
+ activeAdmin.lastNetworkLoggingNotificationTimeMs = now;
}
final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
@@ -14340,7 +14400,7 @@
.bigText(mContext.getString(R.string.network_logging_notification_text)))
.build();
mInjector.getNotificationManager().notify(SystemMessage.NOTE_NETWORK_LOGGING, notification);
- saveSettingsLocked(mOwners.getDeviceOwnerUserId());
+ saveSettingsLocked(activeAdmin.getUserHandle().getIdentifier());
}
/**
@@ -14404,8 +14464,12 @@
@Override
public long getLastNetworkLogRetrievalTime() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
- return getUserData(UserHandle.USER_SYSTEM).mLastNetworkLogsRetrievalTime;
+
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))
+ || canManageUsers(caller));
+ final int affectedUserId = getNetworkLoggingAffectedUser();
+ return affectedUserId >= 0 ? getUserData(affectedUserId).mLastNetworkLogsRetrievalTime : -1;
}
@Override
@@ -16567,4 +16631,78 @@
return mPolicyCache.canAdminGrantSensorsPermissionsForUser(userId);
}
+
+ @Override
+ public void setUsbDataSignalingEnabled(String packageName, boolean enabled) {
+ Objects.requireNonNull(packageName, "Admin package name must be provided");
+ final CallerIdentity caller = getCallerIdentity(packageName);
+ Preconditions.checkCallAuthorization(
+ isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "USB data signaling can only be controlled by a device owner or "
+ + "a profile owner on an organization-owned device.");
+ Preconditions.checkState(canUsbDataSignalingBeDisabled(),
+ "USB data signaling cannot be disabled.");
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+ if (admin.mUsbDataSignalingEnabled != enabled) {
+ admin.mUsbDataSignalingEnabled = enabled;
+ saveSettingsLocked(caller.getUserId());
+ updateUsbDataSignal();
+ }
+ }
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_USB_DATA_SIGNALING)
+ .setAdmin(packageName)
+ .setBoolean(enabled)
+ .write();
+ }
+
+ private void updateUsbDataSignal() {
+ if (!canUsbDataSignalingBeDisabled()) {
+ return;
+ }
+ final boolean usbEnabled;
+ synchronized (getLockObject()) {
+ final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+ UserHandle.USER_SYSTEM);
+ usbEnabled = admin != null && admin.mUsbDataSignalingEnabled;
+ }
+ if (!mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.getUsbManager().enableUsbDataSignal(usbEnabled))) {
+ Slog.w(LOG_TAG, "Failed to set usb data signaling state");
+ }
+ }
+
+ @Override
+ public boolean isUsbDataSignalingEnabled(String packageName) {
+ final CallerIdentity caller = getCallerIdentity(packageName);
+ Preconditions.checkCallAuthorization(
+ isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "USB data signaling can only be controlled by a device owner or "
+ + "a profile owner on an organization-owned device.");
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+ return admin.mUsbDataSignalingEnabled;
+ }
+ }
+
+ @Override
+ public boolean isUsbDataSignalingEnabledForUser(int userId) {
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(isSystemUid(caller));
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+ UserHandle.USER_SYSTEM);
+ return admin != null && admin.mUsbDataSignalingEnabled;
+ }
+ }
+
+ @Override
+ public boolean canUsbDataSignalingBeDisabled() {
+ return mInjector.binderWithCleanCallingIdentity(() ->
+ mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3);
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
index e9b2d7f..8843a5d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
@@ -26,6 +26,7 @@
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
@@ -47,6 +48,10 @@
private final PackageManagerInternal mPm;
private final AtomicBoolean mIsLoggingEnabled = new AtomicBoolean(false);
+ // The target userId to collect network events on. The target userId will be
+ // {@link android.os.UserHandle#USER_ALL} if network events should be collected for all users.
+ private final int mTargetUserId;
+
private IIpConnectivityMetrics mIpConnectivityMetrics;
private ServiceThread mHandlerThread;
private NetworkLoggingHandler mNetworkLoggingHandler;
@@ -58,6 +63,11 @@
if (!mIsLoggingEnabled.get()) {
return;
}
+ // If the network logging was enabled by the profile owner, then do not
+ // include events in the personal profile.
+ if (!shouldLogNetworkEvent(uid)) {
+ return;
+ }
DnsEvent dnsEvent = new DnsEvent(hostname, ipAddresses, ipAddressesCount,
mPm.getNameForUid(uid), timestamp);
sendNetworkEvent(dnsEvent);
@@ -68,6 +78,11 @@
if (!mIsLoggingEnabled.get()) {
return;
}
+ // If the network logging was enabled by the profile owner, then do not
+ // include events in the personal profile.
+ if (!shouldLogNetworkEvent(uid)) {
+ return;
+ }
ConnectEvent connectEvent = new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid),
timestamp);
sendNetworkEvent(connectEvent);
@@ -81,11 +96,17 @@
msg.setData(bundle);
mNetworkLoggingHandler.sendMessage(msg);
}
+
+ private boolean shouldLogNetworkEvent(int uid) {
+ return mTargetUserId == UserHandle.USER_ALL
+ || mTargetUserId == UserHandle.getUserId(uid);
+ }
};
- NetworkLogger(DevicePolicyManagerService dpm, PackageManagerInternal pm) {
+ NetworkLogger(DevicePolicyManagerService dpm, PackageManagerInternal pm, int targetUserId) {
mDpm = dpm;
mPm = pm;
+ mTargetUserId = targetUserId;
}
private boolean checkIpConnectivityMetricsService() {
@@ -114,7 +135,7 @@
/* allowIo */ false);
mHandlerThread.start();
mNetworkLoggingHandler = new NetworkLoggingHandler(mHandlerThread.getLooper(),
- mDpm);
+ mDpm, mTargetUserId);
mNetworkLoggingHandler.scheduleBatchFinalization();
mIsLoggingEnabled.set(true);
return true;
@@ -153,7 +174,7 @@
}
/**
- * If logs are being collected, keep collecting them but stop notifying the device owner that
+ * If logs are being collected, keep collecting them but stop notifying the admin that
* new logs are available (since they cannot be retrieved)
*/
void pause() {
@@ -163,11 +184,11 @@
}
/**
- * If logs are being collected, start notifying the device owner when logs are ready to be
+ * If logs are being collected, start notifying the admin when logs are ready to be
* collected again (if it was paused).
* <p>If logging is enabled and there are logs ready to be retrieved, this method will attempt
- * to notify the device owner. Therefore calling identity should be cleared before calling it
- * (in case the method is called from a user other than the DO's user).
+ * to notify the admin. Therefore calling identity should be cleared before calling it
+ * (in case the method is called from a user other than the admin's user).
*/
void resume() {
if (mNetworkLoggingHandler != null) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 0a7070f..84e89a0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -50,7 +50,7 @@
private static final int MAX_EVENTS_PER_BATCH = 1200;
/**
- * Maximum number of batches to store in memory. If more batches are generated and the DO
+ * Maximum number of batches to store in memory. If more batches are generated and the admin
* doesn't fetch them, we will discard the oldest one.
*/
private static final int MAX_BATCHES = 5;
@@ -74,6 +74,7 @@
private final AlarmManager mAlarmManager;
private long mId;
+ private int mTargetUserId;
private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() {
@Override
@@ -82,10 +83,10 @@
+ mNetworkEvents.size() + " pending events.");
Bundle notificationExtras = null;
synchronized (NetworkLoggingHandler.this) {
- notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
+ notificationExtras = finalizeBatchAndBuildAdminMessageLocked();
}
if (notificationExtras != null) {
- notifyDeviceOwner(notificationExtras);
+ notifyDeviceOwnerOrProfileOwner(notificationExtras);
}
}
};
@@ -98,8 +99,8 @@
private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
/**
- * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the DO. Already
- * retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}.
+ * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the admin.
+ * Already retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}.
*/
@GuardedBy("this")
private final LongSparseArray<ArrayList<NetworkEvent>> mBatches =
@@ -115,16 +116,18 @@
@GuardedBy("this")
private long mLastRetrievedBatchToken;
- NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
- this(looper, dpm, 0 /* event id */);
+ NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, int targetUserId) {
+ this(looper, dpm, 0 /* event id */, targetUserId);
}
@VisibleForTesting
- NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id) {
+ NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id,
+ int targetUserId) {
super(looper);
this.mDpm = dpm;
this.mAlarmManager = mDpm.mInjector.getAlarmManager();
this.mId = id;
+ this.mTargetUserId = targetUserId;
}
@Override
@@ -137,11 +140,11 @@
synchronized (NetworkLoggingHandler.this) {
mNetworkEvents.add(networkEvent);
if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) {
- notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
+ notificationExtras = finalizeBatchAndBuildAdminMessageLocked();
}
}
if (notificationExtras != null) {
- notifyDeviceOwner(notificationExtras);
+ notifyDeviceOwnerOrProfileOwner(notificationExtras);
}
}
break;
@@ -176,10 +179,10 @@
if (toWaitNanos > 0) {
return NANOSECONDS.toMillis(toWaitNanos) + 1; // Round up.
}
- notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
+ notificationExtras = finalizeBatchAndBuildAdminMessageLocked();
}
if (notificationExtras != null) {
- notifyDeviceOwner(notificationExtras);
+ notifyDeviceOwnerOrProfileOwner(notificationExtras);
}
return 0;
}
@@ -201,14 +204,15 @@
+ ", LastRetrievedBatch=" + mLastRetrievedBatchToken);
mPaused = false;
- // If there is a batch ready that the device owner hasn't been notified about, do it now.
+ // If there is a batch ready that the device owner or profile owner hasn't been
+ // notified about, do it now.
if (mBatches.size() > 0 && mLastRetrievedBatchToken != mCurrentBatchToken) {
scheduleBatchFinalization();
- notificationExtras = buildDeviceOwnerMessageLocked();
+ notificationExtras = buildAdminMessageLocked();
}
}
if (notificationExtras != null) {
- notifyDeviceOwner(notificationExtras);
+ notifyDeviceOwnerOrProfileOwner(notificationExtras);
}
}
@@ -219,8 +223,8 @@
}
@GuardedBy("this")
- /** @returns extras if a message should be sent to the device owner */
- private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() {
+ /** @return extras if a message should be sent to the device owner or profile owner */
+ private Bundle finalizeBatchAndBuildAdminMessageLocked() {
mLastFinalizationNanos = System.nanoTime();
Bundle notificationExtras = null;
if (mNetworkEvents.size() > 0) {
@@ -243,10 +247,10 @@
mBatches.append(mCurrentBatchToken, mNetworkEvents);
mNetworkEvents = new ArrayList<>();
if (!mPaused) {
- notificationExtras = buildDeviceOwnerMessageLocked();
+ notificationExtras = buildAdminMessageLocked();
}
} else {
- // Don't notify the DO, since there are no events; DPC can still retrieve
+ // Don't notify the admin, since there are no events; DPC can still retrieve
// the last full batch if not paused.
Slog.d(TAG, "Was about to finalize the batch, but there were no events to send to"
+ " the DPC, the batchToken of last available batch: " + mCurrentBatchToken);
@@ -257,9 +261,9 @@
}
@GuardedBy("this")
- /** Build extras notification to the DO. Should only be called when there
+ /** Build extras notification to the admin. Should only be called when there
is a batch available. */
- private Bundle buildDeviceOwnerMessageLocked() {
+ private Bundle buildAdminMessageLocked() {
final Bundle extras = new Bundle();
final int lastBatchSize = mBatches.valueAt(mBatches.size() - 1).size();
extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentBatchToken);
@@ -267,16 +271,18 @@
return extras;
}
- /** Sends a notification to the DO. Should not hold locks as DevicePolicyManagerService may
- call into NetworkLoggingHandler. */
- private void notifyDeviceOwner(Bundle extras) {
- Slog.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
- + extras.getLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, -1));
+ /** Sends a notification to the device owner or profile owner. Should not hold locks as
+ DevicePolicyManagerService may call into NetworkLoggingHandler. */
+ private void notifyDeviceOwnerOrProfileOwner(Bundle extras) {
if (Thread.holdsLock(this)) {
Slog.wtfStack(TAG, "Shouldn't be called with NetworkLoggingHandler lock held");
return;
}
- mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
+ Slog.d(TAG, "Sending network logging batch broadcast to device owner or profile owner, "
+ + "batchToken: "
+ + extras.getLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, -1));
+ mDpm.sendDeviceOwnerOrProfileOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE,
+ extras, mTargetUserId);
}
synchronized List<NetworkEvent> retrieveFullLogBatch(final long batchToken) {
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index 1801f3b..a2768c6 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -21,6 +21,10 @@
import android.Manifest;
import android.content.Context;
import android.os.ISystemConfig;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
import java.util.List;
@@ -64,6 +68,22 @@
return SystemConfig.getInstance()
.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
}
+
+ @Override
+ public int[] getSystemPermissionUids(String permissionName) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS,
+ "getSystemPermissionUids requires GET_RUNTIME_PERMISSIONS");
+ final List<Integer> uids = new ArrayList<>();
+ final SparseArray<ArraySet<String>> systemPermissions =
+ SystemConfig.getInstance().getSystemPermissions();
+ for (int i = 0; i < systemPermissions.size(); i++) {
+ final ArraySet<String> permissions = systemPermissions.valueAt(i);
+ if (permissions != null && permissions.contains(permissionName)) {
+ uids.add(systemPermissions.keyAt(i));
+ }
+ }
+ return ArrayUtils.convertToIntArray(uids);
+ }
};
public SystemConfigService(Context context) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bd20464..dd2dd81 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -138,6 +138,7 @@
import com.android.server.lights.LightsService;
import com.android.server.location.LocationManagerService;
import com.android.server.media.MediaRouterService;
+import com.android.server.media.metrics.MediaMetricsManagerService;
import com.android.server.media.projection.MediaProjectionManagerService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
@@ -253,6 +254,10 @@
"com.android.server.companion.CompanionDeviceManagerService";
private static final String STATS_COMPANION_APEX_PATH =
"/apex/com.android.os.statsd/javalib/service-statsd.jar";
+ private static final String SCHEDULING_APEX_PATH =
+ "/apex/com.android.scheduling/javalib/service-scheduling.jar";
+ private static final String REBOOT_READINESS_LIFECYCLE_CLASS =
+ "com.android.server.scheduling.RebootReadinessManagerService$Lifecycle";
private static final String CONNECTIVITY_SERVICE_APEX_PATH =
"/apex/com.android.tethering/javalib/service-connectivity.jar";
private static final String STATS_COMPANION_LIFECYCLE_CLASS =
@@ -321,6 +326,8 @@
"com.android.server.musicrecognition.MusicRecognitionManagerService";
private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
"com.android.server.systemcaptions.SystemCaptionsManagerService";
+ private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS =
+ "com.android.server.texttospeech.TextToSpeechManagerService";
private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
"com.android.server.timezone.RulesManagerService$Lifecycle";
private static final String IOT_SERVICE_CLASS =
@@ -1708,6 +1715,7 @@
startAttentionService(context, t);
startRotationResolverService(context, t);
startSystemCaptionsManagerService(context, t);
+ startTextToSpeechManagerService(context, t);
// System Speech Recognition Service
if (deviceHasConfigString(context,
@@ -2392,6 +2400,10 @@
t.traceBegin("StartPeopleService");
mSystemServiceManager.startService(PeopleService.class);
t.traceEnd();
+
+ t.traceBegin("StartMediaMetricsManager");
+ mSystemServiceManager.startService(MediaMetricsManagerService.class);
+ t.traceEnd();
}
if (!isWatch) {
@@ -2447,6 +2459,12 @@
STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH);
t.traceEnd();
+ // Reboot Readiness
+ t.traceBegin("StartRebootReadinessManagerService");
+ mSystemServiceManager.startServiceFromJar(
+ REBOOT_READINESS_LIFECYCLE_CLASS, SCHEDULING_APEX_PATH);
+ t.traceEnd();
+
// Statsd pulled atoms
t.traceBegin("StartStatsPullAtomService");
mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS);
@@ -2903,6 +2921,13 @@
t.traceEnd();
}
+ private void startTextToSpeechManagerService(@NonNull Context context,
+ @NonNull TimingsTraceAndSlog t) {
+ t.traceBegin("StartTextToSpeechManagerService");
+ mSystemServiceManager.startService(TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+ }
+
private void startContentCaptureService(@NonNull Context context,
@NonNull TimingsTraceAndSlog t) {
// First check if it was explicitly enabled by DeviceConfig
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index c2e0b77..2d23fb4 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -334,6 +334,7 @@
this[0] = PackageUserState()
}
}
+ whenever(getInstantApp(anyInt())) { false }
}
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
index a76152c..a92ab9e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -104,14 +104,14 @@
userSelectionStates[1] = DomainVerificationUserState(1).apply {
addHosts(setOf("example-user1.com", "example-user1.org"))
- isDisallowLinkHandling = false
+ isLinkHandlingAllowed = true
}
}
val stateOne = mockEmptyPkgState(1).apply {
// It's valid to have a user selection without any autoVerify domains
userSelectionStates[1] = DomainVerificationUserState(1).apply {
addHosts(setOf("example-user1.com", "example-user1.org"))
- isDisallowLinkHandling = true
+ isLinkHandlingAllowed = false
}
}
@@ -137,13 +137,15 @@
hasAutoVerifyDomains="true"
>
<state>
- <domain name="example.com" state="${DomainVerificationManager.STATE_SUCCESS}"/>
- <domain name="example.org" state="${DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
+ <domain name="example.com" state="${
+ DomainVerificationManager.STATE_SUCCESS}"/>
+ <domain name="example.org" state="${
+ DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
<not-domain name="not-domain.com" state="1"/>
<domain name="missing-state.com"/>
</state>
<user-states>
- <user-state userId="1" disallowLinkHandling="false">
+ <user-state userId="1" allowLinkHandling="true">
<enabled-hosts>
<host name="example-user1.com"/>
<not-host name="not-host.com"/>
@@ -171,7 +173,7 @@
>
<state/>
<user-states>
- <user-state userId="1" disallowLinkHandling="true">
+ <user-state userId="1" allowLinkHandling="false">
<enabled-hosts>
<host name="example-user1.com"/>
<host name="example-user1.org"/>
@@ -214,9 +216,9 @@
stateMap["$packageName.com"] = id
userSelectionStates[id] = DomainVerificationUserState(id).apply {
addHosts(setOf("$packageName-user.com"))
- isDisallowLinkHandling = true
+ isLinkHandlingAllowed = true
}
}
- private fun pkgName(id: Int) = "${PKG_PREFIX}.pkg$id"
+ private fun pkgName(id: Int) = "$PKG_PREFIX.pkg$id"
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 5629d1c..48518f46 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -244,6 +244,7 @@
this[0] = PackageUserState()
}
}
+ whenever(getInstantApp(anyInt())) { false }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceIdleHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceIdleHelper.java
new file mode 100644
index 0000000..d2ab646
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceIdleHelper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+public class FakeDeviceIdleHelper extends DeviceIdleHelper {
+
+ private boolean mDeviceIdle = false;
+
+ public void setIdle(boolean deviceIdle) {
+ mDeviceIdle = deviceIdle;
+ notifyDeviceIdleChanged();
+ }
+
+ @Override
+ protected void registerInternal() {}
+
+ @Override
+ protected void unregisterInternal() {}
+
+ @Override
+ public boolean isDeviceIdle() {
+ return mDeviceIdle;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceStationaryHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceStationaryHelper.java
new file mode 100644
index 0000000..b1d921a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceStationaryHelper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import com.android.server.DeviceIdleInternal;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class FakeDeviceStationaryHelper extends DeviceStationaryHelper {
+
+ private final CopyOnWriteArrayList<DeviceIdleInternal.StationaryListener> mListeners =
+ new CopyOnWriteArrayList<>();
+
+ private boolean mStationary = false;
+
+ @Override
+ public void addListener(DeviceIdleInternal.StationaryListener listener) {
+ synchronized (mListeners) {
+ mListeners.add(listener);
+ listener.onDeviceStationaryChanged(mStationary);
+ }
+ }
+
+ @Override
+ public void removeListener(DeviceIdleInternal.StationaryListener listener) {
+ mListeners.remove(listener);
+ }
+
+ public void setStationary(boolean stationary) {
+ synchronized (mListeners) {
+ mStationary = stationary;
+ }
+
+ for (DeviceIdleInternal.StationaryListener listener : mListeners) {
+ listener.onDeviceStationaryChanged(stationary);
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 2822d5c..1f102ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -28,6 +28,8 @@
private final FakeAppForegroundHelper mAppForegroundHelper;
private final FakeLocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
private final FakeScreenInteractiveHelper mScreenInteractiveHelper;
+ private final FakeDeviceStationaryHelper mDeviceStationaryHelper;
+ private final FakeDeviceIdleHelper mDeviceIdleHelper;
private final LocationAttributionHelper mLocationAttributionHelper;
private final FakeEmergencyHelper mEmergencyHelper;
private final LocationUsageLogger mLocationUsageLogger;
@@ -45,6 +47,8 @@
mAppForegroundHelper = new FakeAppForegroundHelper();
mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog);
mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
+ mDeviceStationaryHelper = new FakeDeviceStationaryHelper();
+ mDeviceIdleHelper = new FakeDeviceIdleHelper();
mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
mEmergencyHelper = new FakeEmergencyHelper();
mLocationUsageLogger = new LocationUsageLogger();
@@ -91,6 +95,16 @@
}
@Override
+ public FakeDeviceStationaryHelper getDeviceStationaryHelper() {
+ return mDeviceStationaryHelper;
+ }
+
+ @Override
+ public FakeDeviceIdleHelper getDeviceIdleHelper() {
+ return mDeviceIdleHelper;
+ }
+
+ @Override
public LocationAttributionHelper getLocationAttributionHelper() {
return mLocationAttributionHelper;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
new file mode 100644
index 0000000..c3cca64
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.provider;
+
+import static com.android.server.location.LocationUtils.createLocation;
+import static com.android.server.location.LocationUtils.createLocationResult;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.location.Location;
+import android.location.LocationResult;
+import android.location.provider.ProviderRequest;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.location.eventlog.LocationEventLog;
+import com.android.server.location.injector.TestInjector;
+import com.android.server.location.test.FakeProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Random;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StationaryThrottlingLocationProviderTest {
+
+ private static final String TAG = "StationaryThrottlingLocationProviderTest";
+
+ private Random mRandom;
+ private TestInjector mInjector;
+ private FakeProvider mDelegateProvider;
+
+ private @Mock AbstractLocationProvider.Listener mListener;
+ private @Mock FakeProvider.FakeProviderInterface mDelegate;
+
+ private StationaryThrottlingLocationProvider mProvider;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+
+ long seed = System.currentTimeMillis();
+ Log.i(TAG, "location random seed: " + seed);
+
+ mRandom = new Random(seed);
+
+ mInjector = new TestInjector();
+ mDelegateProvider = new FakeProvider(mDelegate);
+
+ mProvider = new StationaryThrottlingLocationProvider("test_provider", mInjector,
+ mDelegateProvider, new LocationEventLog());
+ mProvider.getController().setListener(mListener);
+ mProvider.getController().start();
+ }
+
+ @After
+ public void tearDown() {
+ mProvider.getController().setRequest(ProviderRequest.EMPTY_REQUEST);
+ mProvider.getController().stop();
+ }
+
+ @Test
+ public void testThrottle() {
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+ mProvider.getController().setRequest(request);
+ verify(mDelegate).onSetRequest(request);
+
+ mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+ verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+
+ mInjector.getDeviceStationaryHelper().setStationary(true);
+ verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+
+ mInjector.getDeviceIdleHelper().setIdle(true);
+ verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+ verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+ verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+
+ mInjector.getDeviceStationaryHelper().setStationary(false);
+ verify(mDelegate, times(2)).onSetRequest(request);
+ verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+ }
+
+ @Test
+ public void testThrottle_NoInitialLocation() {
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+ mProvider.getController().setRequest(request);
+ verify(mDelegate).onSetRequest(request);
+
+ mInjector.getDeviceStationaryHelper().setStationary(true);
+ mInjector.getDeviceIdleHelper().setIdle(true);
+ verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+
+ mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+ verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+ verify(mDelegate, times(1)).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+ verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+
+ mInjector.getDeviceStationaryHelper().setStationary(false);
+ verify(mDelegate, times(2)).onSetRequest(request);
+ verify(mListener, after(75).times(2)).onReportLocation(any(LocationResult.class));
+ }
+
+ @Test
+ public void testNoThrottle_noLocation() {
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+ mProvider.getController().setRequest(request);
+ verify(mDelegate).onSetRequest(request);
+ verify(mListener, never()).onReportLocation(any(LocationResult.class));
+
+ mInjector.getDeviceStationaryHelper().setStationary(true);
+ mInjector.getDeviceIdleHelper().setIdle(true);
+ verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+ verify(mListener, after(75).times(0)).onReportLocation(any(LocationResult.class));
+ }
+
+ @Test
+ public void testNoThrottle_oldLocation() {
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+ mProvider.getController().setRequest(request);
+ verify(mDelegate).onSetRequest(request);
+
+ Location l = createLocation("test_provider", mRandom);
+ l.setElapsedRealtimeNanos(0);
+
+ LocationResult loc = LocationResult.wrap(l);
+ mDelegateProvider.reportLocation(loc);
+ verify(mListener, times(1)).onReportLocation(loc);
+
+ mInjector.getDeviceStationaryHelper().setStationary(true);
+ mInjector.getDeviceIdleHelper().setIdle(true);
+ verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+ verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
+ }
+
+ @Test
+ public void testNoThrottle_locationSettingsIgnored() {
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(
+ 50).setLocationSettingsIgnored(true).build();
+
+ mProvider.getController().setRequest(request);
+ verify(mDelegate).onSetRequest(request);
+
+ LocationResult loc = createLocationResult("test_provider", mRandom);
+ mDelegateProvider.reportLocation(loc);
+ verify(mListener, times(1)).onReportLocation(loc);
+
+ mInjector.getDeviceStationaryHelper().setStationary(true);
+ mInjector.getDeviceIdleHelper().setIdle(true);
+ verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+ verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
+ }
+}
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/values.xml
similarity index 82%
rename from services/tests/servicestests/res/values/strings.xml
rename to services/tests/servicestests/res/values/values.xml
index 1f07ad5..0404ebc 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/values.xml
@@ -36,4 +36,12 @@
<string name="module_2_name" translatable="false">module_2_name</string>
<string name="widget_description">widget description string</string>
+ <dimen name="widget_min_width">80dp</dimen>
+ <dimen name="widget_min_height">40dp</dimen>
+ <dimen name="widget_min_resize_width">40dp</dimen>
+ <dimen name="widget_min_resize_height">20dp</dimen>
+ <dimen name="widget_max_resize_width">160dp</dimen>
+ <dimen name="widget_max_resize_height">80dp</dimen>
+ <integer name="widget_target_cell_width">2</integer>
+ <integer name="widget_target_cell_height">1</integer>
</resources>
diff --git a/services/tests/servicestests/res/xml/dummy_appwidget_info.xml b/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
index 72f025d..de06191 100644
--- a/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
+++ b/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
@@ -16,12 +16,18 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
- android:minWidth="40dp"
- android:minHeight="40dp"
+ android:minWidth="@dimen/widget_min_width"
+ android:minHeight="@dimen/widget_min_height"
+ android:minResizeWidth="@dimen/widget_min_resize_width"
+ android:minResizeHeight="@dimen/widget_min_resize_height"
+ android:maxResizeWidth="@dimen/widget_max_resize_width"
+ android:maxResizeHeight="@dimen/widget_max_resize_height"
+ android:targetCellWidth="@integer/widget_target_cell_width"
+ android:targetCellHeight="@integer/widget_target_cell_height"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/icon1"
android:previewLayout="@layout/widget_preview"
android:resizeMode="horizontal|vertical"
android:description="@string/widget_description"
android:widgetCategory="home_screen">
-</appwidget-provider>
\ No newline at end of file
+</appwidget-provider>
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 9b6c723..73b0105 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
@@ -16,8 +16,6 @@
package com.android.server.appsearch.external.localstorage;
-import static android.app.appsearch.AppSearchResult.RESULT_OK;
-
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.expectThrows;
@@ -27,7 +25,7 @@
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
-import android.app.appsearch.SetSchemaResult;
+import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.util.ArraySet;
@@ -66,14 +64,14 @@
@Before
public void setUp() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
+
// Give ourselves global query permissions
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
context,
VisibilityStore.NO_OP_USER_ID,
- /*globalQuerierPackage
- =*/ context.getPackageName());
+ /*globalQuerierPackage=*/ context.getPackageName());
}
// TODO(b/175430168) add test to verify reset is working properly.
@@ -425,18 +423,24 @@
GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0);
- // delete 999 documents , we will reach the threshold to trigger optimize() in next
+ // delete 999 documents, we will reach the threshold to trigger optimize() in next
// deletion.
for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1; i++) {
mAppSearchImpl.remove("package", "database", "namespace", "uri" + i);
}
- // optimize() still not be triggered since we are in the interval to call getOptimizeInfo()
+ // Updates the check for optimize counter, checkForOptimize() will be triggered since
+ // CHECK_OPTIMIZE_INTERVAL is reached but optimize() won't since
+ // OPTIMIZE_THRESHOLD_DOC_COUNT is not.
+ mAppSearchImpl.checkForOptimize(
+ /*mutateBatchSize=*/ AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1);
+
+ // Verify optimize() still not be triggered.
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().
+ // Keep delete docs
for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT;
i
< AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
@@ -444,6 +448,9 @@
i++) {
mAppSearchImpl.remove("package", "database", "namespace", "uri" + i);
}
+ // updates the check for optimize counter, will reach both CHECK_OPTIMIZE_INTERVAL and
+ // OPTIMIZE_THRESHOLD_DOC_COUNT this time and trigger a optimize().
+ mAppSearchImpl.checkForOptimize(/*mutateBatchSize*/ AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
// Verify optimize() is triggered
optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
@@ -783,7 +790,7 @@
Collections.singletonList(new AppSearchSchema.Builder("Email").build());
// set email incompatible and delete text
- SetSchemaResult setSchemaResult =
+ SetSchemaResponse setSchemaResponse =
mAppSearchImpl.setSchema(
"package",
"database1",
@@ -791,9 +798,8 @@
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ true);
- assertThat(setSchemaResult.getDeletedSchemaTypes()).containsExactly("Text");
- assertThat(setSchemaResult.getIncompatibleSchemaTypes()).containsExactly("Email");
- assertThat(setSchemaResult.getResultCode()).isEqualTo(RESULT_OK);
+ assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Text");
+ assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Email");
}
@Test
@@ -836,7 +842,7 @@
final List<AppSearchSchema> finalSchemas =
Collections.singletonList(new AppSearchSchema.Builder("Email").build());
- SetSchemaResult setSchemaResult =
+ SetSchemaResponse setSchemaResponse =
mAppSearchImpl.setSchema(
"package",
"database1",
@@ -846,7 +852,7 @@
/*forceOverride=*/ false);
// Check the Document type has been deleted.
- assertThat(setSchemaResult.getDeletedSchemaTypes()).containsExactly("Document");
+ assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Document");
// ForceOverride to delete.
mAppSearchImpl.setSchema(
@@ -1046,4 +1052,136 @@
strippedDocumentProto.build()));
}
}
+
+ @Test
+ public void testThrowsExceptionIfClosed() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+ AppSearchImpl appSearchImpl =
+ AppSearchImpl.create(
+ mTemporaryFolder.newFolder(),
+ context,
+ VisibilityStore.NO_OP_USER_ID,
+ /*globalQuerierPackage=*/ "");
+
+ // Initial check that we could do something at first.
+ List<AppSearchSchema> schemas =
+ Collections.singletonList(new AppSearchSchema.Builder("type").build());
+ appSearchImpl.setSchema(
+ "package",
+ "database",
+ schemas,
+ /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+ /*schemasPackageAccessible=*/ Collections.emptyMap(),
+ /*forceOverride=*/ false);
+
+ appSearchImpl.close();
+
+ // Check all our public APIs
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.setSchema(
+ "package",
+ "database",
+ schemas,
+ /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+ /*schemasPackageAccessible=*/ Collections.emptyMap(),
+ /*forceOverride=*/ false);
+ });
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.getSchema("package", "database");
+ });
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.putDocument(
+ "package",
+ "database",
+ new GenericDocument.Builder<>("uri", "type")
+ .setNamespace("namespace")
+ .build());
+ });
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.getDocument(
+ "package", "database", "namespace", "uri", Collections.emptyMap());
+ });
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.query(
+ "package",
+ "database",
+ "query",
+ new SearchSpec.Builder()
+ .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+ .build());
+ });
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.globalQuery(
+ "query",
+ new SearchSpec.Builder()
+ .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+ .build(),
+ "package",
+ /*callerUid=*/ 1);
+ });
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.getNextPage(/*nextPageToken=*/ 1L);
+ });
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.invalidateNextPageToken(/*nextPageToken=*/ 1L);
+ });
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.reportUsage(
+ "package",
+ "database",
+ "namespace",
+ "uri",
+ /*usageTimestampMillis=*/ 1000L);
+ });
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.remove("package", "database", "namespace", "uri");
+ });
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.removeByQuery(
+ "package",
+ "database",
+ "query",
+ new SearchSpec.Builder()
+ .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+ .build());
+ });
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> {
+ appSearchImpl.persistToDisk();
+ });
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 78eb2df..ff8fedc 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -103,6 +103,26 @@
assertEquals(info.loadDescription(mTestContext), "widget description string");
}
+ public void testParseSizeConfiguration() {
+ AppWidgetProviderInfo info =
+ mManager.getInstalledProvidersForPackage(mPkgName, null).get(0);
+
+ assertThat(info.minWidth).isEqualTo(getDimensionResource(R.dimen.widget_min_width));
+ assertThat(info.minHeight).isEqualTo(getDimensionResource(R.dimen.widget_min_height));
+ assertThat(info.minResizeWidth)
+ .isEqualTo(getDimensionResource(R.dimen.widget_min_resize_width));
+ assertThat(info.minResizeHeight)
+ .isEqualTo(getDimensionResource(R.dimen.widget_min_resize_height));
+ assertThat(info.maxResizeWidth)
+ .isEqualTo(getDimensionResource(R.dimen.widget_max_resize_width));
+ assertThat(info.maxResizeHeight)
+ .isEqualTo(getDimensionResource(R.dimen.widget_max_resize_height));
+ assertThat(info.targetCellWidth)
+ .isEqualTo(getIntegerResource(R.integer.widget_target_cell_width));
+ assertThat(info.targetCellHeight)
+ .isEqualTo(getIntegerResource(R.integer.widget_target_cell_height));
+ }
+
public void testRequestPinAppWidget_otherProvider() {
ComponentName otherProvider = null;
for (AppWidgetProviderInfo provider : mManager.getInstalledProviders()) {
@@ -325,6 +345,14 @@
latch.await();
}
+ private int getDimensionResource(int resId) {
+ return mTestContext.getResources().getDimensionPixelSize(resId);
+ }
+
+ private int getIntegerResource(int resId) {
+ return mTestContext.getResources().getInteger(resId);
+ }
+
private class TestContext extends ContextWrapper {
public TestContext() {
diff --git a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
index d0767cc..c165c66 100644
--- a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
@@ -22,6 +22,7 @@
private boolean mIsDebuggable;
private int mTargetSdk;
private String mPackageName;
+ private long mVersionCode;
private ApplicationInfoBuilder() {
mTargetSdk = -1;
@@ -46,6 +47,11 @@
return this;
}
+ ApplicationInfoBuilder withVersionCode(Long versionCode) {
+ mVersionCode = versionCode;
+ return this;
+ }
+
ApplicationInfo build() {
final ApplicationInfo applicationInfo = new ApplicationInfo();
if (mIsDebuggable) {
@@ -53,6 +59,7 @@
}
applicationInfo.packageName = mPackageName;
applicationInfo.targetSdkVersion = mTargetSdk;
+ applicationInfo.longVersionCode = mVersionCode;
return applicationInfo;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index a53ff9b..8b0e948 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -25,6 +26,7 @@
import static org.testng.Assert.assertThrows;
import android.app.compat.ChangeIdStateCache;
+import android.app.compat.PackageOverride;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -33,6 +35,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.internal.compat.CompatibilityOverrideConfig;
import org.junit.Before;
import org.junit.Test;
@@ -46,6 +49,7 @@
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.util.Collections;
import java.util.UUID;
@RunWith(AndroidJUnit4.class)
@@ -83,6 +87,8 @@
when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
when(mBuildClassifier.isFinalBuild()).thenReturn(false);
ChangeIdStateCache.disable();
+ when(mPackageManager.getApplicationInfo(anyString(), anyInt()))
+ .thenThrow(new NameNotFoundException());
}
@Test
@@ -163,6 +169,10 @@
CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
.addDisabledChangeWithId(1234L)
.build();
+ ApplicationInfo info = ApplicationInfoBuilder.create()
+ .withPackageName("com.some.package").build();
+ when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+ .thenReturn(info);
compatConfig.addOverride(1234L, "com.some.package", true);
@@ -177,6 +187,10 @@
CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
.addEnabledChangeWithId(1234L)
.build();
+ ApplicationInfo info = ApplicationInfoBuilder.create()
+ .withPackageName("com.some.package").build();
+ when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+ .thenReturn(info);
compatConfig.addOverride(1234L, "com.some.package", false);
@@ -191,6 +205,10 @@
CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
compatConfig.forceNonDebuggableFinalForTest(false);
+ ApplicationInfo info = ApplicationInfoBuilder.create()
+ .withPackageName("com.some.package").build();
+ when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+ .thenReturn(info);
compatConfig.addOverride(1234L, "com.some.package", false);
@@ -265,6 +283,71 @@
}
@Test
+ public void testOverrideWithAppVersion() throws Exception {
+ ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+ .withPackageName("com.installed.foo")
+ .withVersionCode(100L)
+ .debuggable().build();
+ when(mPackageManager.getApplicationInfo(eq("com.installed.foo"), anyInt()))
+ .thenReturn(applicationInfo);
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledChangeWithId(1234L).build();
+ when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+ when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+ // Add override that doesn't include the installed app version
+ CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(
+ Collections.singletonMap(1234L,
+ new PackageOverride.Builder()
+ .setMaxVersionCode(99L)
+ .setEnabled(true)
+ .build()));
+ compatConfig.addOverrides(config, "com.installed.foo");
+ assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+
+ // Add override that does include the installed app version
+ config = new CompatibilityOverrideConfig(
+ Collections.singletonMap(1234L,
+ new PackageOverride.Builder()
+ .setMinVersionCode(100L)
+ .setMaxVersionCode(100L)
+ .setEnabled(true)
+ .build()));
+ compatConfig.addOverrides(config, "com.installed.foo");
+ assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+ }
+
+ @Test
+ public void testApplyDeferredOverridesAfterInstallingAppVersion() throws Exception {
+ ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+ .withPackageName("com.notinstalled.foo")
+ .withVersionCode(100L)
+ .debuggable().build();
+ when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt()))
+ .thenThrow(new NameNotFoundException());
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledChangeWithId(1234L).build();
+ when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+ when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+ // Add override before the app is available.
+ CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(
+ Collections.singletonMap(1234L, new PackageOverride.Builder()
+ .setMaxVersionCode(99L)
+ .setEnabled(true)
+ .build()));
+ compatConfig.addOverrides(config, "com.notinstalled.foo");
+ assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+
+ // Pretend the app is now installed.
+ when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt()))
+ .thenReturn(applicationInfo);
+
+ compatConfig.recheckOverrides("com.notinstalled.foo");
+ assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+ }
+
+ @Test
public void testApplyDeferredOverrideClearsOverrideAfterUninstall() throws Exception {
ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
.withPackageName("com.installedapp.foo")
@@ -384,6 +467,8 @@
ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
.withPackageName("com.some.package")
.build();
+ when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+ .thenReturn(applicationInfo);
assertThat(compatConfig.addOverride(1234L, "com.some.package", false)).isTrue();
assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
@@ -404,6 +489,8 @@
.withPackageName("foo.bar")
.withTargetSdk(2)
.build();
+ when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+ .thenReturn(applicationInfo);
assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isFalse();
assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse();
@@ -425,7 +512,8 @@
.withPackageName("foo.bar")
.withTargetSdk(2)
.build();
-
+ when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+ .thenReturn(applicationInfo);
assertThat(compatConfig.enableTargetSdkChangesForPackage("foo.bar", 3)).isEqualTo(1);
assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isTrue();
assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse();
@@ -533,22 +621,114 @@
+ " <override-value packageName=\"foo.bar\" enabled=\"true\">\n"
+ " </override-value>\n"
+ " </validated>\n"
- + " <deferred>\n"
- + " </deferred>\n"
+ + " <raw>\n"
+ + " <raw-override-value packageName=\"foo.bar\" "
+ + "minVersionCode=\"-9223372036854775808\" "
+ + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n"
+ + " </raw-override-value>\n"
+ + " </raw>\n"
+ " </change-overrides>\n"
+ " <change-overrides changeId=\"2\">\n"
+ " <validated>\n"
+ " </validated>\n"
- + " <deferred>\n"
- + " <override-value packageName=\"bar.baz\" enabled=\"false\">\n"
- + " </override-value>\n"
- + " </deferred>\n"
+ + " <raw>\n"
+ + " <raw-override-value packageName=\"bar.baz\" "
+ + "minVersionCode=\"-9223372036854775808\" "
+ + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n"
+ + " </raw-override-value>\n"
+ + " </raw>\n"
+ " </change-overrides>\n"
+ "</overrides>\n");
}
@Test
- public void testLoadOverrides() throws Exception {
+ public void testSaveOverridesWithRanges() throws Exception {
+ File overridesFile = new File(createTempDir(), "overrides.xml");
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledChangeWithId(1L)
+ .addEnableSinceSdkChangeWithId(2, 2L)
+ .build();
+ compatConfig.forceNonDebuggableFinalForTest(true);
+ compatConfig.initOverrides(overridesFile);
+
+ compatConfig.addOverrides(new CompatibilityOverrideConfig(Collections.singletonMap(1L,
+ new PackageOverride.Builder()
+ .setMinVersionCode(99L)
+ .setMaxVersionCode(101L)
+ .setEnabled(true)
+ .build())), "foo.bar");
+
+ assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<overrides>\n"
+ + " <change-overrides changeId=\"1\">\n"
+ + " <validated>\n"
+ + " </validated>\n"
+ + " <raw>\n"
+ + " <raw-override-value packageName=\"foo.bar\" "
+ + "minVersionCode=\"99\" maxVersionCode=\"101\" enabled=\"true\">\n"
+ + " </raw-override-value>\n"
+ + " </raw>\n"
+ + " </change-overrides>\n"
+ + "</overrides>\n");
+ }
+
+ @Test
+ public void testLoadOverridesRaw() throws Exception {
+ File tempDir = createTempDir();
+ File overridesFile = new File(tempDir, "overrides.xml");
+ // Change 1 is enabled for foo.bar (validated)
+ // Change 2 is disabled for bar.baz (deferred)
+ String xmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ + "<overrides>\n"
+ + " <change-overrides changeId=\"1\">\n"
+ + " <validated>\n"
+ + " <override-value packageName=\"foo.bar\" enabled=\"true\">\n"
+ + " </override-value>\n"
+ + " </validated>\n"
+ + " <raw>\n"
+ + " <raw-override-value packageName=\"foo.bar\" "
+ + "minVersionCode=\"-9223372036854775808\" "
+ + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n"
+ + " </raw-override-value>\n"
+ + " </raw>\n"
+ + " </change-overrides>\n"
+ + " <change-overrides changeId=\"2\">\n"
+ + " <validated>\n"
+ + " </validated>\n"
+ + " <raw>\n"
+ + " <raw-override-value packageName=\"bar.baz\" "
+ + "minVersionCode=\"-9223372036854775808\" "
+ + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n"
+ + " </raw-override-value>\n"
+ + " </raw>\n"
+ + " </change-overrides>\n"
+ + "</overrides>\n";
+ writeToFile(tempDir, "overrides.xml", xmlData);
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledChangeWithId(1L)
+ .addEnableSinceSdkChangeWithId(2, 2L)
+ .build();
+ compatConfig.forceNonDebuggableFinalForTest(true);
+ compatConfig.initOverrides(overridesFile);
+ ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+ .withPackageName("foo.bar")
+ .withVersionCode(100L)
+ .debuggable()
+ .build();
+ when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+ .thenReturn(applicationInfo);
+ when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt()))
+ .thenThrow(new NameNotFoundException());
+
+ assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue();
+ assertThat(compatConfig.willChangeBeEnabled(2L, "bar.baz")).isFalse();
+
+ compatConfig.recheckOverrides("foo.bar");
+ assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue();
+ }
+
+ @Test
+ public void testLoadOverridesDeferred() throws Exception {
File tempDir = createTempDir();
File overridesFile = new File(tempDir, "overrides.xml");
// Change 1 is enabled for foo.bar (validated)
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index a1b2dc8..799b067 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -196,6 +196,9 @@
mPlatformCompat.registerListener(1, mListener1);
mPlatformCompat.registerListener(2, mListener1);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
mPlatformCompat.setOverrides(
CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
@@ -208,6 +211,9 @@
mPlatformCompat.registerListener(1, mListener1);
mPlatformCompat.registerListener(2, mListener1);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
mPlatformCompat.setOverrides(
CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
@@ -219,6 +225,9 @@
public void testListenerCalledOnSetOverridesTwoListeners() throws Exception {
mPlatformCompat.registerListener(1, mListener1);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
mPlatformCompat.setOverrides(
CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
@@ -244,6 +253,9 @@
mPlatformCompat.registerListener(1, mListener1);
mPlatformCompat.registerListener(2, mListener1);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
mPlatformCompat.setOverrides(
CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
@@ -252,9 +264,12 @@
}
@Test
- public void testListenerCalledOnSetOverridesTwoListenersForTest() throws Exception {
+ public void testListenerCalledOnSetOverridesForTestTwoListeners() throws Exception {
mPlatformCompat.registerListener(1, mListener1);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
mPlatformCompat.setOverrides(
CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
@@ -280,6 +295,9 @@
mPlatformCompat.registerListener(1, mListener1);
mPlatformCompat.registerListener(2, mListener2);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
mPlatformCompat.setOverrides(
CompatibilityChangeConfigBuilder.create().enable(1L).build(),
PACKAGE_NAME);
@@ -299,6 +317,9 @@
mPlatformCompat.registerListener(1, mListener1);
mPlatformCompat.registerListener(2, mListener2);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
mPlatformCompat.setOverrides(
CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
@@ -318,6 +339,9 @@
mPlatformCompat.registerListener(1, mListener1);
mPlatformCompat.registerListener(2, mListener2);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
mPlatformCompat.setOverrides(
CompatibilityChangeConfigBuilder.create().enable(1L).build(),
PACKAGE_NAME);
@@ -336,6 +360,9 @@
public void testListenerCalledOnClearOverrideDoesntExist() throws Exception {
mPlatformCompat.registerListener(1, mListener1);
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
mPlatformCompat.clearOverride(1, PACKAGE_NAME);
// Listener not called when a non existing override is removed.
verify(mListener1, never()).onCompatChange(PACKAGE_NAME);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 5b0704d..61d7ede 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -28,6 +28,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
+import android.hardware.usb.UsbManager;
import android.media.IAudioService;
import android.net.IIpConnectivityMetrics;
import android.net.Uri;
@@ -244,6 +245,11 @@
}
@Override
+ UsbManager getUsbManager() {
+ return services.usbManager;
+ }
+
+ @Override
boolean storageManagerIsFileBasedEncryptionEnabled() {
return services.storageManager.isFileBasedEncryptionEnabled();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 7597cbf..5bb65ab 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -89,6 +89,7 @@
import android.content.pm.StringParceledListSlice;
import android.content.pm.UserInfo;
import android.graphics.Color;
+import android.hardware.usb.UsbManager;
import android.net.Uri;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -4329,6 +4330,68 @@
}
@Test
+ public void testSetNetworkLoggingEnabled_asPo() throws Exception {
+ final int managedProfileUserId = CALLER_USER_HANDLE;
+ final int managedProfileAdminUid =
+ UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
+ mContext.binder.callingUid = managedProfileAdminUid;
+ mContext.applicationInfo = new ApplicationInfo();
+ mContext.packageName = admin1.getPackageName();
+ addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.S);
+ when(getServices().iipConnectivityMetrics
+ .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+
+ // Check no logs have been retrieved so far.
+ assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+ // Enable network logging
+ dpm.setNetworkLoggingEnabled(admin1, true);
+ assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+ // Retrieve the network logs and verify timestamp has been updated.
+ final long beforeRetrieval = System.currentTimeMillis();
+
+ dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
+
+ final long networkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
+ final long afterRetrieval = System.currentTimeMillis();
+ assertThat(networkLogRetrievalTime >= beforeRetrieval).isTrue();
+ assertThat(networkLogRetrievalTime <= afterRetrieval).isTrue();
+ }
+
+ @Test
+ public void testSetNetworkLoggingEnabled_asPoOfOrgOwnedDevice() throws Exception {
+ // Setup profile owner on organization-owned device
+ final int MANAGED_PROFILE_ADMIN_UID =
+ UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+ configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
+
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ mContext.packageName = admin1.getPackageName();
+ mContext.applicationInfo = new ApplicationInfo();
+ when(getServices().iipConnectivityMetrics
+ .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+
+ // Check no logs have been retrieved so far.
+ assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+ // Enable network logging
+ dpm.setNetworkLoggingEnabled(admin1, true);
+ assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+ // Retrieve the network logs and verify timestamp has been updated.
+ final long beforeRetrieval = System.currentTimeMillis();
+
+ dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
+
+ final long networkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
+ final long afterRetrieval = System.currentTimeMillis();
+ assertThat(networkLogRetrievalTime >= beforeRetrieval).isTrue();
+ assertThat(networkLogRetrievalTime <= afterRetrieval).isTrue();
+ }
+
+ @Test
public void testGetBindDeviceAdminTargetUsers() throws Exception {
mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS);
@@ -6940,6 +7003,82 @@
DevicePolicyManager.PASSWORD_QUALITY_COMPLEX));
}
+ @Test
+ public void testSetUsbDataSignalingEnabled_noDeviceOwnerOrPoOfOrgOwnedDevice() {
+ assertThrows(SecurityException.class,
+ () -> dpm.setUsbDataSignalingEnabled(true));
+ }
+
+ @Test
+ public void testSetUsbDataSignalingEnabled_asDeviceOwner() throws Exception {
+ setDeviceOwner();
+ when(getServices().usbManager.enableUsbDataSignal(false)).thenReturn(true);
+ when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+
+ assertThat(dpm.isUsbDataSignalingEnabled()).isTrue();
+
+ dpm.setUsbDataSignalingEnabled(false);
+
+ assertThat(dpm.isUsbDataSignalingEnabled()).isFalse();
+ }
+
+ @Test
+ public void testIsUsbDataSignalingEnabledForUser_systemUser() throws Exception {
+ when(getServices().usbManager.enableUsbDataSignal(false)).thenReturn(true);
+ when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+ setDeviceOwner();
+ dpm.setUsbDataSignalingEnabled(false);
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+
+ assertThat(dpm.isUsbDataSignalingEnabledForUser(UserHandle.myUserId())).isFalse();
+ }
+
+ @Test
+ public void testSetUsbDataSignalingEnabled_asPoOfOrgOwnedDevice() throws Exception {
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
+ mContext.binder.callingUid = managedProfileAdminUid;
+ when(getServices().usbManager.enableUsbDataSignal(false)).thenReturn(true);
+ when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+
+ assertThat(dpm.isUsbDataSignalingEnabled()).isTrue();
+
+ dpm.setUsbDataSignalingEnabled(false);
+
+ assertThat(dpm.isUsbDataSignalingEnabled()).isFalse();
+ }
+
+ @Test
+ public void testCanUsbDataSignalingBeDisabled_canBeDisabled() throws Exception {
+ when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+
+ assertThat(dpm.canUsbDataSignalingBeDisabled()).isTrue();
+ }
+
+ @Test
+ public void testCanUsbDataSignalingBeDisabled_cannotBeDisabled() throws Exception {
+ setDeviceOwner();
+ when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_2);
+
+ assertThat(dpm.canUsbDataSignalingBeDisabled()).isFalse();
+ assertThrows(IllegalStateException.class,
+ () -> dpm.setUsbDataSignalingEnabled(true));
+ }
+
+ @Test
+ public void testSetUsbDataSignalingEnabled_noChangeToActiveAdmin()
+ throws Exception {
+ setDeviceOwner();
+ when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+ boolean enabled = dpm.isUsbDataSignalingEnabled();
+
+ dpm.setUsbDataSignalingEnabled(true);
+
+ assertThat(dpm.isUsbDataSignalingEnabled()).isEqualTo(enabled);
+ }
+
private void setUserUnlocked(int userHandle, boolean unlocked) {
when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 944ef8d..f6dee38 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -45,6 +45,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.database.Cursor;
+import android.hardware.usb.UsbManager;
import android.media.IAudioService;
import android.net.IIpConnectivityMetrics;
import android.net.Uri;
@@ -119,6 +120,7 @@
public final CrossProfileApps crossProfileApps;
public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
public final AppOpsManager appOpsManager;
+ public final UsbManager usbManager;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
public final BuildMock buildMock = new BuildMock();
@@ -163,6 +165,7 @@
crossProfileApps = mock(CrossProfileApps.class);
persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
appOpsManager = mock(AppOpsManager.class);
+ usbManager = mock(UsbManager.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index 7506dd4..743b25f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -125,7 +125,7 @@
private List<NetworkEvent> fillHandlerWithFullBatchOfEvents(long startingId) throws Exception {
// GIVEN a handler with events
NetworkLoggingHandler handler = new NetworkLoggingHandler(new TestLooper().getLooper(),
- mDpmTestable, startingId);
+ mDpmTestable, startingId, DpmMockContext.CALLER_USER_HANDLE);
// GIVEN network events are sent to the handler.
for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) {
ConnectEvent event = new ConnectEvent("some_ip_address", 800, "com.google.foo",
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 23a4c2f..732c08c 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -30,6 +30,7 @@
import android.hardware.SensorManager;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -43,6 +44,7 @@
import org.mockito.MockitoAnnotations;
@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class AutomaticBrightnessControllerTest {
private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index f0b4f1b..9396ed2 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -30,6 +30,7 @@
import android.content.res.TypedArray;
import android.hardware.display.BrightnessConfiguration;
import android.os.PowerManager;
+import android.platform.test.annotations.Presubmit;
import android.util.MathUtils;
import android.util.Spline;
@@ -42,6 +43,7 @@
import java.util.Arrays;
@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class BrightnessMappingStrategyTest {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 2e3178b..ee0f2e8 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -41,12 +41,13 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.database.ContentObserver;
-import android.hardware.display.DisplayManager;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
@@ -82,6 +83,7 @@
import java.util.stream.Collectors;
@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class DisplayModeDirectorTest {
// The tolerance within which we consider something approximately equals.
@@ -588,6 +590,12 @@
@Test
public void testSensorRegistration() {
+ // First, configure brightness zones or DMD won't register for sensor data.
+ final FakeDeviceConfig config = mInjector.getDeviceConfig();
+ config.setRefreshRateInHighZone(60);
+ config.setHighDisplayBrightnessThresholds(new int[] { 255 });
+ config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
setPeakRefreshRate(90 /*fps*/);
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index ac45017..ece0a627 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -20,17 +20,23 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.app.PropertyInvalidatedCache;
import android.graphics.Point;
+import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceControl;
+import androidx.test.filters.SmallTest;
+
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.io.OutputStream;
+@SmallTest
+@Presubmit
public class LogicalDisplayTest {
private static final int DISPLAY_ID = 0;
private static final int LAYER_STACK = 0;
@@ -52,6 +58,9 @@
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(displayDeviceInfo);
+ // Disable binder caches in this process.
+ PropertyInvalidatedCache.disableForTestMode();
+
DisplayDeviceRepository repo = new DisplayDeviceRepository(
new DisplayManagerService.SyncRoot(),
new PersistentDataStore(new PersistentDataStore.Injector() {
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 196454b..d72606a 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertTrue;
import android.hardware.display.BrightnessConfiguration;
+import android.platform.test.annotations.Presubmit;
import android.util.Pair;
import androidx.test.filters.SmallTest;
@@ -40,6 +41,7 @@
import java.nio.charset.StandardCharsets;
@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class PersistentDataStoreTest {
private PersistentDataStore mDataStore;
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 4bbf96f..7771afc 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -684,8 +684,8 @@
newAddFontFamilyRequest("<family lang='en'>"
+ " <font>test.ttf</font>"
+ "</family>")));
- fail("Expect IllegalArgumentException");
- } catch (IllegalArgumentException e) {
+ fail("Expect NullPointerException");
+ } catch (NullPointerException e) {
// Expect
}
}
@@ -703,9 +703,10 @@
dir.update(Arrays.asList(newAddFontFamilyRequest("<family name='test'>"
+ " <font>test.ttf</font>"
+ "</family>")));
- fail("Expect IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expect
+ fail("Expect SystemFontException");
+ } catch (FontManagerService.SystemFontException e) {
+ assertThat(e.getErrorCode())
+ .isEqualTo(FontManager.RESULT_ERROR_FONT_NOT_FOUND);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index bb57a69..fcbd897 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -15,12 +15,9 @@
*/
package com.android.server.hdmi;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPortInfo;
-import android.hardware.tv.cec.V1_0.CecMessage;
-import android.hardware.tv.cec.V1_0.HotplugEvent;
import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.os.RemoteException;
-import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.hdmi.HdmiCecController.NativeWrapper;
@@ -99,7 +96,7 @@
@Override
public int nativeGetVersion() {
- return 0;
+ return HdmiControlManager.HDMI_CEC_VERSION_2_0;
}
@Override
@@ -139,20 +136,15 @@
if (mCallback == null) {
return;
}
- CecMessage message = new CecMessage();
- message.initiator = hdmiCecMessage.getSource();
- message.destination = hdmiCecMessage.getDestination();
- ArrayList<Byte> body = new ArrayList<>();
- body.add((byte) hdmiCecMessage.getOpcode());
+ int source = hdmiCecMessage.getSource();
+ int destination = hdmiCecMessage.getDestination();
+ byte[] body = new byte[hdmiCecMessage.getParams().length + 1];
+ int i = 0;
+ body[i++] = (byte) hdmiCecMessage.getOpcode();
for (byte param : hdmiCecMessage.getParams()) {
- body.add(param);
+ body[i++] = param;
}
- message.body = body;
- try {
- mCallback.onCecMessage(message);
- } catch (RemoteException e) {
- Log.e(TAG, "Error sending CEC message", e);
- }
+ mCallback.onCecMessage(source, destination, body);
}
public void onHotplugEvent(int port, boolean connected) {
@@ -160,15 +152,7 @@
return;
}
- HotplugEvent hotplugEvent = new HotplugEvent();
- hotplugEvent.portId = port;
- hotplugEvent.connected = connected;
-
- try {
- mCallback.onHotplugEvent(hotplugEvent);
- } catch (RemoteException e) {
- Log.e(TAG, "Error sending hotplug event", e);
- }
+ mCallback.onHotplugEvent(port, connected);
}
public List<HdmiCecMessage> getResultMessages() {
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 9f0d982..5342486 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_INVALID;
@@ -49,6 +50,7 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
@SmallTest
@Presubmit
@@ -1586,4 +1588,46 @@
assertThat(features.contains(
Constants.RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU)).isFalse();
}
+
+ @Test
+ public void doesNotSupportRecordTvScreen() {
+ HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_TV, mPlaybackLogicalAddress,
+ Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM);
+
+ mNativeWrapper.onCecMessage(recordTvScreen);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_RECORD_TV_SCREEN,
+ ABORT_UNRECOGNIZED_OPCODE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+ }
+
+ @Test
+ public void shouldHandleUserControlPressedAndReleased() {
+ HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
+ ADDR_TV, mPlaybackLogicalAddress,
+ HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP);
+ HdmiCecMessage userControlReleased = HdmiCecMessageBuilder.buildUserControlReleased(
+ ADDR_TV, mPlaybackLogicalAddress);
+
+ mNativeWrapper.onCecMessage(userControlPressed);
+ mTestLooper.dispatchAll();
+
+ // Move past the follower safety timeout
+ mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(2));
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.onCecMessage(userControlReleased);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage featureAbortPressed = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_USER_CONTROL_PRESSED,
+ ABORT_UNRECOGNIZED_OPCODE);
+ HdmiCecMessage featureAbortReleased = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_USER_CONTROL_RELEASED,
+ ABORT_UNRECOGNIZED_OPCODE);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortPressed);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortReleased);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index d5271ef..4623eb5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -15,9 +15,11 @@
*/
package com.android.server.hdmi;
+import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_RECORDER_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -484,4 +486,18 @@
ADDR_AUDIO_SYSTEM);
assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
}
+
+ @Test
+ public void supportsRecordTvScreen() {
+ HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_RECORDER_1, mTvLogicalAddress,
+ Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM);
+
+ mNativeWrapper.onCecMessage(recordTvScreen);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ mTvLogicalAddress, ADDR_RECORDER_1, Constants.MESSAGE_RECORD_TV_SCREEN,
+ ABORT_UNRECOGNIZED_OPCODE);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index f9b25d9..f87d599 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -30,6 +30,7 @@
import android.util.Pair;
import android.util.SparseIntArray;
+import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
@@ -496,6 +497,7 @@
}
@Test
+ @LargeTest
public void testRandom13() {
assertThat(EQUAL_PROBABILITY_CDF.length).isEqualTo(NUM_WORK_TYPES);
@@ -508,9 +510,9 @@
Pair.create(WORK_TYPE_BGUSER, 3));
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1));
- final double probStop = 0.01;
- final double[] numTypesCdf = buildCdf(0, 0.05, 0.05, 0.9);
- final double probStart = 0.99;
+ final double probStop = 0.13;
+ final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.85);
+ final double probStart = 0.87;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
EQUAL_PROBABILITY_CDF, numTypesCdf, probStop);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
index 32445fd..2eedc32 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
@@ -19,19 +19,17 @@
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.security.GeneralSecurityException;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
-import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
/**
* atest FrameworksServicesTests:RebootEscrowDataTest
@@ -41,22 +39,18 @@
private RebootEscrowKey mKey;
private SecretKey mKeyStoreEncryptionKey;
- private SecretKey generateNewRebootEscrowEncryptionKey() throws GeneralSecurityException {
- KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
- generator.init(new KeyGenParameterSpec.Builder(
- "reboot_escrow_data_test_key",
- KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
- .setKeySize(256)
- .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .build());
- return generator.generateKey();
- }
+ // Hex encoding of a randomly generated AES key for test.
+ private static final byte[] TEST_AES_KEY = new byte[] {
+ 0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61,
+ 0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31,
+ 0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09,
+ 0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23,
+ };
@Before
public void generateKey() throws Exception {
mKey = RebootEscrowKey.generate();
- mKeyStoreEncryptionKey = generateNewRebootEscrowEncryptionKey();
+ mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES");
}
private static byte[] getTestSp() {
@@ -114,4 +108,23 @@
assertThat(decrypted, is(testSp));
}
+ @Test
+ public void fromEncryptedData_legacyVersion_success() throws Exception {
+ byte[] testSp = getTestSp();
+ byte[] ksEncryptedBlob = AesEncryptionUtil.encrypt(mKey.getKey(), testSp);
+
+ // Write a legacy blob encrypted only by k_s.
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+ dos.writeInt(1);
+ dos.writeByte(3);
+ dos.write(ksEncryptedBlob);
+ byte[] legacyBlob = bos.toByteArray();
+
+ RebootEscrowData actual = RebootEscrowData.fromEncryptedData(mKey, legacyBlob, null);
+
+ assertThat(actual.getSpVersion(), is((byte) 3));
+ assertThat(actual.getKey().getKeyBytes(), is(mKey.getKeyBytes()));
+ assertThat(actual.getSyntheticPassword(), is(testSp));
+ }
}
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 23fcf70..4d0beef 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -103,7 +103,6 @@
import android.util.Xml;
import com.android.frameworks.servicestests.R;
-import com.android.internal.util.FastXmlSerializer;
import com.android.server.pm.ShortcutService.ConfigConstants;
import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
import com.android.server.pm.ShortcutUser.PackageWithUser;
@@ -111,7 +110,6 @@
import org.mockito.ArgumentCaptor;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -141,6 +139,7 @@
private static final int CACHE_OWNER_0 = LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
private static final int CACHE_OWNER_1 = LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
+ private static final int CACHE_OWNER_2 = LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
@Override
protected void tearDown() throws Exception {
@@ -1531,7 +1530,8 @@
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
- makeLongLivedShortcut("s4"))));
+ makeLongLivedShortcut("s4"), makeLongLivedShortcut("s5"),
+ makeLongLivedShortcut("s6"))));
});
// Pin s2
@@ -1545,28 +1545,30 @@
mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"),
HANDLE_USER_0, CACHE_OWNER_0);
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4", "s5"),
HANDLE_USER_0, CACHE_OWNER_1);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s5", "s6"),
+ HANDLE_USER_0, CACHE_OWNER_2);
});
setCaller(CALLING_PACKAGE_1);
// Get dynamic shortcuts
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
- "s1", "s2", "s3", "s4");
+ "s1", "s2", "s3", "s4", "s5", "s6");
// Get pinned shortcuts
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED),
"s2");
// Get cached shortcuts
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2", "s4");
+ "s2", "s4", "s5", "s6");
// Remove a dynamic cached shortcut
- mManager.removeDynamicShortcuts(list("s4"));
+ mManager.removeDynamicShortcuts(list("s4", "s5"));
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
- "s1", "s2", "s3");
+ "s1", "s2", "s3", "s6");
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2", "s4");
+ "s2", "s4", "s5", "s6");
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
@@ -1574,15 +1576,21 @@
});
// s2 still cached by owner1. s4 wasn't cached by owner0 so didn't get removed.
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2", "s4");
+ "s2", "s4", "s5", "s6");
// uncache a non-dynamic shortcut. Should be removed.
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s4"),
HANDLE_USER_0, CACHE_OWNER_1);
});
+
+ // uncache s6 by its only owner. s5 still cached by owner1
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s5", "s6"),
+ HANDLE_USER_0, CACHE_OWNER_2);
+ });
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2");
+ "s2", "s5");
// Cache another shortcut
runWithCaller(LAUNCHER_1, USER_0, () -> {
@@ -1590,14 +1598,14 @@
HANDLE_USER_0, CACHE_OWNER_0);
});
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2", "s3");
+ "s2", "s3", "s5");
// Remove a dynamic cached pinned long lived shortcut
mManager.removeLongLivedShortcuts(list("s2"));
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
- "s1", "s3");
+ "s1", "s3", "s6");
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s3");
+ "s3", "s5");
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED),
"s2");
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index 0dcd608..a1b2f38 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -39,6 +39,7 @@
import com.android.frameworks.servicestests.R;
import com.android.server.pm.PackageManagerException;
import com.android.server.pm.parsing.TestPackageParser2;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.parsing.pkg.ParsedPackage;
@@ -55,6 +56,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -64,6 +67,9 @@
public class DexMetadataHelperTest {
private static final String APK_FILE_EXTENSION = ".apk";
private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
+ private static final String DEX_METADATA_PACKAGE_NAME =
+ "com.android.frameworks.servicestests.install_split";
+ private static long DEX_METADATA_VERSION_CODE = 30;
@Rule
public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@@ -76,12 +82,46 @@
}
private File createDexMetadataFile(String apkFileName) throws IOException {
+ return createDexMetadataFile(apkFileName, /*validManifest=*/true);
+ }
+
+ private File createDexMetadataFile(String apkFileName, boolean validManifest) throws IOException
+ {
+ return createDexMetadataFile(apkFileName,DEX_METADATA_PACKAGE_NAME,
+ DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, validManifest);
+ }
+
+ private File createDexMetadataFile(String apkFileName, String packageName, Long versionCode,
+ boolean emptyManifest, boolean validManifest) throws IOException {
File dmFile = new File(mTmpDir, apkFileName.replace(APK_FILE_EXTENSION,
DEX_METADATA_FILE_EXTENSION));
try (FileOutputStream fos = new FileOutputStream(dmFile)) {
try (ZipOutputStream zipOs = new ZipOutputStream(fos)) {
zipOs.putNextEntry(new ZipEntry("primary.prof"));
zipOs.closeEntry();
+
+ if (validManifest) {
+ zipOs.putNextEntry(new ZipEntry("manifest.json"));
+ if (!emptyManifest) {
+ String manifestStr = "{";
+
+ if (packageName != null) {
+ manifestStr += "\"packageName\": " + "\"" + packageName + "\"";
+
+ if (versionCode != null) {
+ manifestStr += ", ";
+ }
+ }
+ if (versionCode != null) {
+ manifestStr += " \"versionCode\": " + versionCode;
+ }
+
+ manifestStr += "}";
+ byte[] bytes = manifestStr.getBytes(StandardCharsets.UTF_8);
+ zipOs.write(bytes, /*off=*/0, /*len=*/bytes.length);
+ }
+ zipOs.closeEntry();
+ }
}
}
return dmFile;
@@ -96,17 +136,38 @@
return outFile;
}
+ private static void validatePackageDexMetadata(AndroidPackage pkg, boolean requireManifest)
+ throws PackageParserException {
+ Collection<String> apkToDexMetadataList =
+ AndroidPackageUtils.getPackageDexMetadata(pkg).values();
+ String packageName = pkg.getPackageName();
+ long versionCode = pkg.toAppInfoWithoutState().longVersionCode;
+ for (String dexMetadata : apkToDexMetadataList) {
+ DexMetadataHelper.validateDexMetadataFile(
+ dexMetadata, packageName, versionCode, requireManifest);
+ }
+ }
+
+ private static void validatePackageDexMetatadataVaryingRequireManifest(ParsedPackage pkg)
+ throws PackageParserException {
+ validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+ validatePackageDexMetadata(pkg, /*requireManifest=*/false);
+ }
+
@Test
public void testParsePackageWithDmFileValid() throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk");
- ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
assertEquals(1, packageDexMetadata.size());
String baseDexMetadata = packageDexMetadata.get(pkg.getBaseApkPath());
assertNotNull(baseDexMetadata);
assertTrue(isDexMetadataForApk(baseDexMetadata, pkg.getBaseApkPath()));
+
+ // Should throw no exceptions.
+ validatePackageDexMetatadataVaryingRequireManifest(pkg);
}
@Test
@@ -116,7 +177,7 @@
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_base.apk");
createDexMetadataFile("install_split_feature_a.apk");
- ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
assertEquals(2, packageDexMetadata.size());
@@ -127,6 +188,9 @@
String splitDexMetadata = packageDexMetadata.get(pkg.getSplitCodePaths()[0]);
assertNotNull(splitDexMetadata);
assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.getSplitCodePaths()[0]));
+
+ // Should throw no exceptions.
+ validatePackageDexMetatadataVaryingRequireManifest(pkg);
}
@Test
@@ -135,7 +199,7 @@
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_feature_a.apk");
- ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
assertEquals(1, packageDexMetadata.size());
@@ -143,6 +207,9 @@
String splitDexMetadata = packageDexMetadata.get(pkg.getSplitCodePaths()[0]);
assertNotNull(splitDexMetadata);
assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.getSplitCodePaths()[0]));
+
+ // Should throw no exceptions.
+ validatePackageDexMetatadataVaryingRequireManifest(pkg);
}
@Test
@@ -151,9 +218,17 @@
File invalidDmFile = new File(mTmpDir, "install_split_base.dm");
Files.createFile(invalidDmFile.toPath());
try {
- ParsedPackage pkg = new TestPackageParser2()
- .parsePackage(mTmpDir, 0 /* flags */, false);
- AndroidPackageUtils.validatePackageDexMetadata(pkg);
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+ validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+ fail("Should fail validation: empty .dm file");
+ } catch (PackageParserException e) {
+ assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+ }
+
+ try {
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+ validatePackageDexMetadata(pkg, /*requireManifest=*/false);
+ fail("Should fail validation: empty .dm file");
} catch (PackageParserException e) {
assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
}
@@ -169,9 +244,112 @@
Files.createFile(invalidDmFile.toPath());
try {
- ParsedPackage pkg = new TestPackageParser2()
- .parsePackage(mTmpDir, 0 /* flags */, false);
- AndroidPackageUtils.validatePackageDexMetadata(pkg);
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+ validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+ fail("Should fail validation: empty .dm file");
+ } catch (PackageParserException e) {
+ assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+ }
+
+ try {
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+ validatePackageDexMetadata(pkg, /*requireManifest=*/false);
+ fail("Should fail validation: empty .dm file");
+ } catch (PackageParserException e) {
+ assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+ }
+ }
+
+ @Test
+ public void testParsePackageWithDmFileInvalidManifest()
+ throws IOException, PackageParserException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ createDexMetadataFile("install_split_base.apk", /*validManifest=*/false);
+
+ try {
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+ validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+ fail("Should fail validation: missing manifest.json in the .dm archive");
+ } catch (PackageParserException e) {
+ assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+ }
+ }
+
+ @Test
+ public void testParsePackageWithDmFileEmptyManifest()
+ throws IOException, PackageParserException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ createDexMetadataFile("install_split_base.apk", /*packageName=*/"doesn't matter",
+ /*versionCode=*/-12345L, /*emptyManifest=*/true, /*validManifest=*/true);
+
+ try {
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+ validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+ fail("Should fail validation: empty manifest.json in the .dm archive");
+ } catch (PackageParserException e) {
+ assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+ }
+ }
+
+ @Test
+ public void testParsePackageWithDmFileBadPackageName()
+ throws IOException, PackageParserException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ createDexMetadataFile("install_split_base.apk", /*packageName=*/"bad package name",
+ DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
+
+ try {
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+ validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+ fail("Should fail validation: bad package name in the .dm archive");
+ } catch (PackageParserException e) {
+ assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+ }
+ }
+
+ @Test
+ public void testParsePackageWithDmFileBadVersionCode()
+ throws IOException, PackageParserException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
+ /*versionCode=*/12345L, /*emptyManifest=*/false, /*validManifest=*/true);
+
+ try {
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+ validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+ fail("Should fail validation: bad version code in the .dm archive");
+ } catch (PackageParserException e) {
+ assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+ }
+ }
+
+ @Test
+ public void testParsePackageWithDmFileMissingPackageName()
+ throws IOException, PackageParserException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ createDexMetadataFile("install_split_base.apk", /*packageName=*/null,
+ DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
+
+ try {
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+ validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+ fail("Should fail validation: missing package name in the .dm archive");
+ } catch (PackageParserException e) {
+ assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+ }
+ }
+
+ @Test
+ public void testParsePackageWithDmFileMissingVersionCode()
+ throws IOException, PackageParserException {
+ copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+ createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
+ /*versionCode=*/null, /*emptyManifest=*/false, /*validManifest=*/true);
+
+ try {
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+ validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+ fail("Should fail validation: missing version code in the .dm archive");
} catch (PackageParserException e) {
assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
}
@@ -184,7 +362,7 @@
try {
DexMetadataHelper.validateDexPaths(mTmpDir.list());
- fail("Should fail validation");
+ fail("Should fail validation: .dm filename has no match against .apk");
} catch (IllegalStateException e) {
// expected.
}
@@ -200,7 +378,7 @@
try {
DexMetadataHelper.validateDexPaths(mTmpDir.list());
- fail("Should fail validation");
+ fail("Should fail validation: split .dm filename unmatched against .apk");
} catch (IllegalStateException e) {
// expected.
}
@@ -211,7 +389,7 @@
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
final File dm = createDexMetadataFile("install_split_base.apk");
final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
- ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, 0 /* flags */);
+ ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, /*flags=*/0);
if (result.isError()) {
throw new IllegalStateException(result.getErrorMessage(), result.getException());
}
@@ -228,7 +406,7 @@
try (FileInputStream is = new FileInputStream(base)) {
final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(
ParseTypeImpl.forDefaultParsing().reset(), is.getFD(),
- base.getAbsolutePath(), /* flags */ 0);
+ base.getAbsolutePath(), /*flags=*/0);
if (result.isError()) {
throw new PackageManagerException(result.getErrorCode(),
result.getErrorMessage(), result.getException());
diff --git a/services/tests/servicestests/src/com/android/server/rotationresolver/OWNERS b/services/tests/servicestests/src/com/android/server/rotationresolver/OWNERS
new file mode 100644
index 0000000..81b6f05
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/rotationresolver/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/rotationresolver/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java b/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java
index 5f64249..22c38c1 100644
--- a/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java
@@ -19,15 +19,21 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.rotationresolver.RotationResolverInternal;
import android.view.Surface;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import org.junit.Before;
@@ -41,75 +47,81 @@
*/
@SmallTest
public class RotationResolverManagerPerUserServiceTest {
-
- @Mock
- Context mContext;
+ private static final String PACKAGE_NAME = "test_pkg";
+ private static final String CLASS_NAME = "test_class";
@Mock
RotationResolverInternal.RotationResolverCallbackInternal mMockCallbackInternal;
-
@Mock
- ComponentName mMockComponentName;
+ PackageManager mMockPackageManager;
+ private Context mContext;
private CancellationSignal mCancellationSignal;
-
- private RotationResolverManagerPerUserService mSpyService;
+ private RotationResolverManagerPerUserService mService;
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
- // setup context mock
+ // setup context.
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ doReturn(PACKAGE_NAME).when(mMockPackageManager).getRotationResolverPackageName();
+ doReturn(createTestingResolveInfo()).when(mMockPackageManager).resolveServiceAsUser(any(),
+ anyInt(), anyInt());
+ doReturn(mMockPackageManager).when(mContext).getPackageManager();
doReturn(true).when(mContext).bindServiceAsUser(any(), any(), anyInt(), any());
// setup a spy for the RotationResolverManagerPerUserService.
final RotationResolverManagerService mainService = new RotationResolverManagerService(
mContext);
- final RotationResolverManagerPerUserService mService =
- new RotationResolverManagerPerUserService(mainService, /* Lock */ new Object(),
- mContext.getUserId());
+ mService = new RotationResolverManagerPerUserService(mainService, /* Lock */ new Object(),
+ mContext.getUserId());
mCancellationSignal = new CancellationSignal();
- mSpyService = Mockito.spy(mService);
- mSpyService.mCurrentRequest = new RemoteRotationResolverService.RotationRequest(
+ this.mService.mCurrentRequest = new RemoteRotationResolverService.RotationRequest(
mMockCallbackInternal, Surface.ROTATION_0, Surface.ROTATION_0, "", 1000L,
mCancellationSignal);
- mSpyService.getMaster().mIsServiceEnabled = true;
+ this.mService.getMaster().mIsServiceEnabled = true;
- mSpyService.mRemoteService = new MockRemoteRotationResolverService(mContext,
- mMockComponentName, mContext.getUserId(),
+ ComponentName componentName = new ComponentName(PACKAGE_NAME, CLASS_NAME);
+ this.mService.mRemoteService = new MockRemoteRotationResolverService(mContext,
+ componentName, mContext.getUserId(),
/* idleUnbindTimeoutMs */60000L,
/* Lock */ new Object());
}
@Test
public void testResolveRotation_callOnSuccess() {
- doReturn(true).when(mSpyService).isServiceAvailableLocked();
- mSpyService.mCurrentRequest = null;
+ mService.mCurrentRequest = null;
RotationResolverInternal.RotationResolverCallbackInternal callbackInternal =
Mockito.mock(RotationResolverInternal.RotationResolverCallbackInternal.class);
- mSpyService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
+ mService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
"", 1000L, mCancellationSignal);
verify(callbackInternal).onSuccess(anyInt());
}
@Test
public void testResolveRotation_noCrashWhenCancelled() {
- doReturn(true).when(mSpyService).isServiceAvailableLocked();
-
RotationResolverInternal.RotationResolverCallbackInternal callbackInternal =
Mockito.mock(RotationResolverInternal.RotationResolverCallbackInternal.class);
final CancellationSignal cancellationSignal = new CancellationSignal();
- mSpyService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
+ mService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
"", 1000L, cancellationSignal);
cancellationSignal.cancel();
+ }
- verify(mSpyService.mCurrentRequest).cancelInternal();
+ private ResolveInfo createTestingResolveInfo() {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = new ServiceInfo();
+ resolveInfo.serviceInfo.packageName = PACKAGE_NAME;
+ resolveInfo.serviceInfo.name = CLASS_NAME;
+ resolveInfo.serviceInfo.permission = Manifest.permission.BIND_ROTATION_RESOLVER_SERVICE;
+ return resolveInfo;
}
static class MockRemoteRotationResolverService extends RemoteRotationResolverService {
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 cc4d4ea..e843dd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -482,8 +482,7 @@
public void testHandleActivitySizeCompatModeChanged() {
setUpDisplaySizeWithApp(1000, 2000);
doReturn(true).when(mTask).isOrganized();
- ActivityRecord activity = mActivity;
- activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+ mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
assertFitted();
@@ -499,12 +498,12 @@
// Make the activity resizable again by restarting it
clearInvocations(mTask);
- activity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
- activity.mVisibleRequested = true;
- activity.restartProcessIfVisible();
+ mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
+ mActivity.mVisibleRequested = true;
+ mActivity.restartProcessIfVisible();
// The full lifecycle isn't hooked up so manually set state to resumed
- activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
- mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity);
+ mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+ mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(mActivity);
// Expect null token when switching to non-size-compat mode activity.
verify(mTask).onSizeCompatActivityChanged();
@@ -515,6 +514,46 @@
}
@Test
+ public void testHandleActivitySizeCompatModeChangedOnDifferentTask() {
+ setUpDisplaySizeWithApp(1000, 2000);
+ doReturn(true).when(mTask).isOrganized();
+ mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+ prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ assertFitted();
+
+ // Resize the display so that the activity exercises size-compat mode.
+ resizeDisplay(mTask.mDisplayContent, 1000, 2500);
+
+ // Expect the exact token when the activity is in size compatibility mode.
+ verify(mTask).onSizeCompatActivityChanged();
+ ActivityManager.RunningTaskInfo taskInfo = mTask.getTaskInfo();
+
+ assertEquals(mActivity.appToken, taskInfo.topActivityToken);
+ assertTrue(taskInfo.topActivityInSizeCompat);
+
+ // Create another Task to hold another size compat activity.
+ clearInvocations(mTask);
+ final Task secondTask = new TaskBuilder(mSupervisor).setDisplay(mTask.getDisplayContent())
+ .setCreateActivity(true).build();
+ final ActivityRecord secondActivity = secondTask.getTopNonFinishingActivity();
+ doReturn(true).when(secondTask).isOrganized();
+ secondActivity.setState(Task.ActivityState.RESUMED,
+ "testHandleActivitySizeCompatModeChanged");
+ prepareUnresizable(secondActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Resize the display so that the activity exercises size-compat mode.
+ resizeDisplay(mTask.mDisplayContent, 1000, 3000);
+
+ // Expect the exact token when the activity is in size compatibility mode.
+ verify(secondTask).onSizeCompatActivityChanged();
+ verify(mTask, never()).onSizeCompatActivityChanged();
+ taskInfo = secondTask.getTaskInfo();
+
+ assertEquals(secondActivity.appToken, taskInfo.topActivityToken);
+ assertTrue(taskInfo.topActivityInSizeCompat);
+ }
+
+ @Test
public void testShouldUseSizeCompatModeOnResizableTask() {
setUpDisplaySizeWithApp(1000, 2500);
diff --git a/services/texttospeech/Android.bp b/services/texttospeech/Android.bp
new file mode 100644
index 0000000..bacc932
--- /dev/null
+++ b/services/texttospeech/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+ name: "services.texttospeech-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.texttospeech",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.texttospeech-sources"],
+ libs: ["services.core"],
+}
\ No newline at end of file
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
new file mode 100644
index 0000000..f805904
--- /dev/null
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
@@ -0,0 +1,184 @@
+/*
+ * 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.texttospeech;
+
+import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.speech.tts.ITextToSpeechService;
+import android.speech.tts.ITextToSpeechSession;
+import android.speech.tts.ITextToSpeechSessionCallback;
+import android.speech.tts.TextToSpeech;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.ServiceConnector;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Manages per-user text to speech session activated by {@link TextToSpeechManagerService}.
+ * Creates {@link TtsClient} interface object with direct connection to
+ * {@link android.speech.tts.TextToSpeechService} provider.
+ *
+ * @see ITextToSpeechSession
+ * @see TextToSpeech
+ */
+final class TextToSpeechManagerPerUserService extends
+ AbstractPerUserSystemService<TextToSpeechManagerPerUserService,
+ TextToSpeechManagerService> {
+
+ private static final String TAG = TextToSpeechManagerPerUserService.class.getSimpleName();
+
+ TextToSpeechManagerPerUserService(
+ @NonNull TextToSpeechManagerService master,
+ @NonNull Object lock, @UserIdInt int userId) {
+ super(master, lock, userId);
+ }
+
+ void createSessionLocked(String engine, ITextToSpeechSessionCallback sessionCallback) {
+ TextToSpeechSessionConnection.start(getContext(), mUserId, engine, sessionCallback);
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ @NonNull
+ protected ServiceInfo newServiceInfoLocked(
+ @SuppressWarnings("unused") @NonNull ComponentName serviceComponent)
+ throws PackageManager.NameNotFoundException {
+ try {
+ return AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new PackageManager.NameNotFoundException(
+ "Could not get service for " + serviceComponent);
+ }
+ }
+
+ private static class TextToSpeechSessionConnection extends
+ ServiceConnector.Impl<ITextToSpeechService> {
+
+ private final String mEngine;
+ private final ITextToSpeechSessionCallback mCallback;
+ private final DeathRecipient mUnbindOnDeathHandler;
+
+ static void start(Context context, @UserIdInt int userId, String engine,
+ ITextToSpeechSessionCallback callback) {
+ new TextToSpeechSessionConnection(context, userId, engine, callback).start();
+ }
+
+ private TextToSpeechSessionConnection(Context context, @UserIdInt int userId, String engine,
+ ITextToSpeechSessionCallback callback) {
+ super(context,
+ new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE).setPackage(engine),
+ Context.BIND_AUTO_CREATE,
+ userId,
+ ITextToSpeechService.Stub::asInterface);
+ mEngine = engine;
+ mCallback = callback;
+ mUnbindOnDeathHandler = () -> unbindEngine("client process death is reported");
+ }
+
+ private void start() {
+ Slog.d(TAG, "Trying to start connection to TTS engine: " + mEngine);
+
+ connect()
+ .thenAccept(
+ serviceBinder -> {
+ if (serviceBinder != null) {
+ Slog.d(TAG,
+ "Connected successfully to TTS engine: " + mEngine);
+ try {
+ mCallback.onConnected(new ITextToSpeechSession.Stub() {
+ @Override
+ public void disconnect() {
+ unbindEngine("client disconnection request");
+ }
+ }, serviceBinder.asBinder());
+
+ mCallback.asBinder().linkToDeath(mUnbindOnDeathHandler, 0);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Error notifying the client on connection", ex);
+
+ unbindEngine(
+ "failed communicating with the client - process "
+ + "is dead");
+ }
+ } else {
+ Slog.w(TAG, "Failed to obtain TTS engine binder");
+ runSessionCallbackMethod(
+ () -> mCallback.onError("Failed creating TTS session"));
+ }
+ })
+ .exceptionally(ex -> {
+ Slog.w(TAG, "TTS engine binding error", ex);
+ runSessionCallbackMethod(
+ () -> mCallback.onError(
+ "Failed creating TTS session: " + ex.getCause()));
+
+ return null;
+ });
+ }
+
+ @Override // from ServiceConnector.Impl
+ protected void onServiceConnectionStatusChanged(
+ ITextToSpeechService service, boolean connected) {
+ if (!connected) {
+ Slog.w(TAG, "Disconnected from TTS engine");
+ runSessionCallbackMethod(mCallback::onDisconnected);
+
+ try {
+ mCallback.asBinder().unlinkToDeath(mUnbindOnDeathHandler, 0);
+ } catch (NoSuchElementException ex) {
+ Slog.d(TAG, "The death recipient was not linked.");
+ }
+ }
+ }
+
+ @Override // from ServiceConnector.Impl
+ protected long getAutoDisconnectTimeoutMs() {
+ return PERMANENT_BOUND_TIMEOUT_MS;
+ }
+
+ private void unbindEngine(String reason) {
+ Slog.d(TAG, "Unbinding TTS engine: " + mEngine + ". Reason: " + reason);
+ unbind();
+ }
+ }
+
+ static void runSessionCallbackMethod(ThrowingRunnable callbackRunnable) {
+ try {
+ callbackRunnable.runOrThrow();
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed running callback method", ex);
+ }
+ }
+
+ interface ThrowingRunnable {
+ void runOrThrow() throws RemoteException;
+ }
+}
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
new file mode 100644
index 0000000..9015563
--- /dev/null
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.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.texttospeech;
+
+import static com.android.server.texttospeech.TextToSpeechManagerPerUserService.runSessionCallbackMethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.UserHandle;
+import android.speech.tts.ITextToSpeechManager;
+import android.speech.tts.ITextToSpeechSessionCallback;
+
+import com.android.server.infra.AbstractMasterSystemService;
+
+
+/**
+ * A service that allows secured synthesizing of text to speech audio. Upon request creates a
+ * session
+ * that is managed by {@link TextToSpeechManagerPerUserService}.
+ *
+ * @see ITextToSpeechManager
+ */
+public final class TextToSpeechManagerService extends
+ AbstractMasterSystemService<TextToSpeechManagerService,
+ TextToSpeechManagerPerUserService> {
+
+ private static final String TAG = TextToSpeechManagerService.class.getSimpleName();
+
+ public TextToSpeechManagerService(@NonNull Context context) {
+ super(context, /* serviceNameResolver= */ null,
+ /* disallowProperty = */null);
+ }
+
+ @Override // from SystemService
+ public void onStart() {
+ publishBinderService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE,
+ new TextToSpeechManagerServiceStub());
+ }
+
+ @Override
+ protected TextToSpeechManagerPerUserService newServiceLocked(
+ @UserIdInt int resolvedUserId, boolean disabled) {
+ return new TextToSpeechManagerPerUserService(this, mLock, resolvedUserId);
+ }
+
+ private final class TextToSpeechManagerServiceStub extends ITextToSpeechManager.Stub {
+ @Override
+ public void createSession(String engine,
+ ITextToSpeechSessionCallback sessionCallback) {
+ synchronized (mLock) {
+ TextToSpeechManagerPerUserService perUserService = getServiceForUserLocked(
+ UserHandle.getCallingUserId());
+ if (perUserService != null) {
+ perUserService.createSessionLocked(engine, sessionCallback);
+ } else {
+ runSessionCallbackMethod(
+ () -> sessionCallback.onError("Service is not available for user"));
+ }
+ }
+ }
+ }
+}
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index aa8bbde..89e5d5f 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -19,6 +19,7 @@
"android.hardware.usb-V1.0-java",
"android.hardware.usb-V1.1-java",
"android.hardware.usb-V1.2-java",
+ "android.hardware.usb-V1.3-java",
"android.hardware.usb.gadget-V1.0-java",
"android.hardware.usb.gadget-V1.1-java",
"android.hardware.usb.gadget-V1.2-java",
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index ca18c57..6bf6715 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -42,13 +42,13 @@
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
-import android.hardware.usb.V1_0.IUsb;
import android.hardware.usb.V1_0.PortRole;
import android.hardware.usb.V1_0.PortRoleType;
import android.hardware.usb.V1_0.Status;
import android.hardware.usb.V1_1.PortStatus_1_1;
import android.hardware.usb.V1_2.IUsbCallback;
import android.hardware.usb.V1_2.PortStatus;
+import android.hardware.usb.V1_3.IUsb;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.Bundle;
@@ -156,6 +156,9 @@
*/
private int mIsPortContaminatedNotificationId;
+ private boolean mEnableUsbDataSignaling;
+ protected int mCurrentUsbHalVersion;
+
public UsbPortManager(Context context) {
mContext = context;
try {
@@ -181,6 +184,7 @@
if (mProxy != null) {
try {
mProxy.queryPortStatus();
+ mEnableUsbDataSignaling = true;
} catch (RemoteException e) {
logAndPrintException(null,
"ServiceStart: Failed to query port status", e);
@@ -346,6 +350,66 @@
}
}
+ /**
+ * Enable/disable the USB data signaling
+ *
+ * @param enable enable or disable USB data signaling
+ */
+ public boolean enableUsbDataSignal(boolean enable) {
+ try {
+ mEnableUsbDataSignaling = enable;
+ // Call into the hal. Use the castFrom method from HIDL.
+ android.hardware.usb.V1_3.IUsb proxy = android.hardware.usb.V1_3.IUsb.castFrom(mProxy);
+ return proxy.enableUsbDataSignal(enable);
+ } catch (RemoteException e) {
+ logAndPrintException(null, "Failed to set USB data signaling", e);
+ return false;
+ } catch (ClassCastException e) {
+ logAndPrintException(null, "Method only applicable to V1.3 or above implementation", e);
+ return false;
+ }
+ }
+
+ /**
+ * Get USB HAL version
+ *
+ * @param none
+ */
+ public int getUsbHalVersion() {
+ return mCurrentUsbHalVersion;
+ }
+
+ /**
+ * update USB HAL version
+ *
+ * @param none
+ */
+ private void updateUsbHalVersion() {
+ android.hardware.usb.V1_3.IUsb usbProxy_V1_3 =
+ android.hardware.usb.V1_3.IUsb.castFrom(mProxy);
+ if (usbProxy_V1_3 != null) {
+ mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_3;
+ return;
+ }
+
+ android.hardware.usb.V1_2.IUsb usbProxy_V1_2 =
+ android.hardware.usb.V1_2.IUsb.castFrom(mProxy);
+ if (usbProxy_V1_2 != null) {
+ mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_2;
+ return;
+ }
+
+ android.hardware.usb.V1_1.IUsb usbProxy_V1_1 =
+ android.hardware.usb.V1_1.IUsb.castFrom(mProxy);
+ if (usbProxy_V1_1 != null) {
+ mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_1;
+ return;
+ }
+
+ mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_0;
+ return;
+ }
+
public void setPortRoles(String portId, int newPowerRole, int newDataRole,
IndentingPrintWriter pw) {
synchronized (mLock) {
@@ -610,6 +674,9 @@
for (PortInfo portInfo : mPorts.values()) {
portInfo.dump(dump, "usb_ports", UsbPortManagerProto.USB_PORTS);
}
+
+ dump.write("enable_usb_data_signaling", UsbPortManagerProto.ENABLE_USB_DATA_SIGNALING,
+ mEnableUsbDataSignaling);
}
dump.end(token);
@@ -783,6 +850,7 @@
mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);
mProxy.setCallback(mHALCallback);
mProxy.queryPortStatus();
+ mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_0;
} catch (NoSuchElementException e) {
logAndPrintException(pw, "connectToProxy: usb hal service not found."
+ " Did the service fail to start?", e);
@@ -1115,6 +1183,7 @@
case MSG_SYSTEM_READY: {
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ updateUsbHalVersion();
break;
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index edd4a38..9a13d76 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -745,6 +745,38 @@
}
@Override
+ public int getUsbHalVersion() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mPortManager != null) {
+ return mPortManager.getUsbHalVersion();
+ } else {
+ return UsbManager.USB_HAL_NOT_SUPPORTED;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public boolean enableUsbDataSignal(boolean enable) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mPortManager != null) {
+ return mPortManager.enableUsbDataSignal(enable);
+ } else {
+ return false;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
synchronized (mLock) {
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index d79225f..ec12040 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -16,6 +16,7 @@
package com.android.internal.telephony.uicc;
+import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
@@ -28,6 +29,7 @@
import com.android.telephony.Rlog;
import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
import java.util.List;
/**
@@ -253,13 +255,48 @@
}
if ((b & 0x0f) <= 0x09) {
- ret += (b & 0xf);
+ ret += (b & 0xf);
}
return ret;
}
/**
+ * Encodes a string to be formatted like the EF[ADN] alpha identifier.
+ *
+ * <p>See javadoc for {@link #adnStringFieldToString(byte[], int, int)} for more details on
+ * the relevant specs.
+ *
+ * <p>This will attempt to encode using the GSM 7-bit alphabet but will fallback to UCS-2 if
+ * there are characters that are not supported by it.
+ *
+ * @return the encoded string including the prefix byte necessary to identify the encoding.
+ * @see #adnStringFieldToString(byte[], int, int)
+ */
+ @NonNull
+ public static byte[] stringToAdnStringField(@NonNull String alphaTag) {
+ int septets = GsmAlphabet.countGsmSeptetsUsingTables(alphaTag, false, 0, 0);
+ if (septets != -1) {
+ byte[] ret = new byte[septets];
+ GsmAlphabet.stringToGsm8BitUnpackedField(alphaTag, ret, 0, ret.length);
+ return ret;
+ }
+
+ // Strictly speaking UCS-2 disallows surrogate characters but it's much more complicated to
+ // validate that the string contains only valid UCS-2 characters. Since the read path
+ // in most modern software will decode "UCS-2" by treating it as UTF-16 this should be fine
+ // (e.g. the adnStringFieldToString has done this for a long time on Android). Also there's
+ // already a precedent in SMS applications to ignore the UCS-2/UTF-16 distinction.
+ byte[] alphaTagBytes = alphaTag.getBytes(StandardCharsets.UTF_16BE);
+ byte[] ret = new byte[alphaTagBytes.length + 1];
+ // 0x80 tags the remaining bytes as UCS-2
+ ret[0] = (byte) 0x80;
+ System.arraycopy(alphaTagBytes, 0, ret, 1, alphaTagBytes.length);
+
+ return ret;
+ }
+
+ /**
* Decodes a string field that's formatted like the EF[ADN] alpha
* identifier
*
@@ -309,7 +346,7 @@
ret = new String(data, offset + 1, ucslen * 2, "utf-16be");
} catch (UnsupportedEncodingException ex) {
Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException",
- ex);
+ ex);
}
if (ret != null) {
@@ -342,7 +379,7 @@
len = length - 4;
base = (char) (((data[offset + 2] & 0xFF) << 8) |
- (data[offset + 3] & 0xFF));
+ (data[offset + 3] & 0xFF));
offset += 4;
isucs2 = true;
}
@@ -366,7 +403,7 @@
count++;
ret.append(GsmAlphabet.gsm8BitUnpackedToString(data,
- offset, count));
+ offset, count));
offset += count;
len -= count;
diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
index eb04f69..ac9e681 100644
--- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
+++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
@@ -76,11 +76,11 @@
Params(enableDisable = null, targetSdk = 29, result = false),
Params(enableDisable = null, targetSdk = 30, result = true),
- Params(enableDisable = true, targetSdk = 29, result = true),
+ Params(enableDisable = true, targetSdk = 29, result = false),
Params(enableDisable = true, targetSdk = 30, result = true),
Params(enableDisable = false, targetSdk = 29, result = false),
- Params(enableDisable = false, targetSdk = 30, result = false)
+ Params(enableDisable = false, targetSdk = 30, result = true)
)
}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 8b34755..0508125 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -496,6 +496,45 @@
}
@Test
+ public void testWatchdogMonitorsAcrossReboots_Phase1_Install() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+
+ Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit();
+ }
+
+ @Test
+ public void testWatchdogMonitorsAcrossReboots_Phase2_VerifyInstall() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+ // Trigger rollback of test app.
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+ Integer.toString(5), false);
+
+ // The final crash that causes rollback will come from the host side.
+ RollbackUtils.sendCrashBroadcast(TestApp.A, 4);
+ }
+
+ @Test
+ public void testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback() {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TestApp.A);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(rollback).causePackagesContainsExactly(TestApp.ACrashing2);
+ assertThat(rollback).isStaged();
+ assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1);
+ }
+
+ @Test
public void hasMainlineModule() throws Exception {
String pkgName = getModuleMetadataPackageName();
boolean existed = InstrumentationRegistry.getInstrumentation().getContext()
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 67417bd..65fb7b6c 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -554,6 +554,30 @@
});
}
+ /**
+ * Tests that packages are monitored across multiple reboots.
+ */
+ @Test
+ public void testWatchdogMonitorsAcrossReboots() throws Exception {
+ runPhase("testWatchdogMonitorsAcrossReboots_Phase1_Install");
+
+ // The first reboot will make the rollback available.
+ // Information about which packages are monitored will be persisted to a file before the
+ // second reboot, and read from disk after the second reboot.
+ getDevice().reboot();
+ getDevice().reboot();
+
+ runPhase("testWatchdogMonitorsAcrossReboots_Phase2_VerifyInstall");
+
+ // Launch the app to crash to trigger rollback
+ startActivity(TESTAPP_A);
+ // Wait for reboot to happen
+ waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
+ getDevice().waitForDeviceAvailable();
+
+ runPhase("testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback");
+ }
+
private void pushTestApex() throws Exception {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
diff --git a/tests/StagedInstallTest/StagedInstallInternalTest.xml b/tests/StagedInstallTest/StagedInstallInternalTest.xml
index 1b8fa67..1f22cae 100644
--- a/tests/StagedInstallTest/StagedInstallInternalTest.xml
+++ b/tests/StagedInstallTest/StagedInstallInternalTest.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<configuration description="Runs the internal staged install tests">
- <option name="test-suite-tag" value="StagedInstallTest" />
+ <option name="test-suite-tag" value="StagedInstallInternalTest" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="StagedInstallInternalTestApp.apk" />
diff --git a/tests/StagedInstallTest/TEST_MAPPING b/tests/StagedInstallTest/TEST_MAPPING
index fa2a60b..cc31f2c 100644
--- a/tests/StagedInstallTest/TEST_MAPPING
+++ b/tests/StagedInstallTest/TEST_MAPPING
@@ -1,6 +1,16 @@
{
"presubmit-large": [
{
+ "name": "StagedInstallInternalTest",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.LargeTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
"name": "StagedInstallInternalTest"
}
]
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index 2201efd..8dc53ac 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -25,6 +25,7 @@
import static org.junit.Assume.assumeTrue;
import android.cts.install.lib.host.InstallUtilsHost;
+import android.platform.test.annotations.LargeTest;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.ddmlib.Log;
@@ -201,6 +202,7 @@
// Test rollback-app command waits for staged sessions to be ready
@Test
+ @LargeTest
public void testAdbRollbackAppWaitsForStagedReady() throws Exception {
assumeTrue("Device does not support updating APEX",
mHostUtils.isApexUpdateSupported());
diff --git a/tests/UpdatableSystemFontTest/TEST_MAPPING b/tests/UpdatableSystemFontTest/TEST_MAPPING
index a5c4479..7fbf426 100644
--- a/tests/UpdatableSystemFontTest/TEST_MAPPING
+++ b/tests/UpdatableSystemFontTest/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "UpdatableSystemFontTest"
}
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index e1da3d0..dc9e587 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -17,7 +17,6 @@
package com.android.server;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -85,7 +84,6 @@
final String typeName = ConnectivityManager.getNetworkTypeName(type);
mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities();
mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
- mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
mNetworkCapabilities.addTransportType(transport);
switch (transport) {
case TRANSPORT_ETHERNET:
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index c5e76a9..2a693eb 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -2754,10 +2754,6 @@
NetworkCapabilities filter = new NetworkCapabilities();
filter.addCapability(capability);
- // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add
- // NOT_VCN_MANAGED automatically but not for NetworkCapabilities,
- // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details.
- filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
handlerThread.start();
final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
@@ -4117,7 +4113,6 @@
handlerThread.start();
NetworkCapabilities filter = new NetworkCapabilities()
.addTransportType(TRANSPORT_CELLULAR)
- .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.addCapability(NET_CAPABILITY_INTERNET);
final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
mServiceContext, "testFactory", filter);
@@ -6021,7 +6016,6 @@
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_CONGESTED)
- .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.setLinkDownstreamBandwidthKbps(10);
final NetworkCapabilities wifiNc = new NetworkCapabilities()
.addTransportType(TRANSPORT_WIFI)
@@ -6030,7 +6024,6 @@
.addCapability(NET_CAPABILITY_NOT_ROAMING)
.addCapability(NET_CAPABILITY_NOT_CONGESTED)
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
- .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.setLinkUpstreamBandwidthKbps(20);
mCellNetworkAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */);
mWiFiNetworkAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */);
@@ -7718,13 +7711,19 @@
mWiFiNetworkAgent.removeCapability(testCap);
callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent);
callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent);
- verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
- reset(mMockNetd);
+ // TODO: Test default network changes for NOT_VCN_MANAGED once the default request has
+ // it.
+ if (testCap == NET_CAPABILITY_TRUSTED) {
+ verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
+ reset(mMockNetd);
+ }
mCellNetworkAgent.removeCapability(testCap);
callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
callbackWithoutCap.assertNoCallback();
- verify(mMockNetd).networkClearDefault();
+ if (testCap == NET_CAPABILITY_TRUSTED) {
+ verify(mMockNetd).networkClearDefault();
+ }
mCm.unregisterNetworkCallback(callbackWithCap);
mCm.unregisterNetworkCallback(callbackWithoutCap);
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index 3556c72..8f5ae97 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -89,8 +89,8 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class PermissionMonitorTest {
- private static final int MOCK_USER1 = 0;
- private static final int MOCK_USER2 = 1;
+ private static final UserHandle MOCK_USER1 = UserHandle.of(0);
+ private static final UserHandle MOCK_USER2 = UserHandle.of(1);
private static final int MOCK_UID1 = 10001;
private static final int MOCK_UID2 = 10086;
private static final int SYSTEM_UID1 = 1000;
@@ -123,10 +123,7 @@
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
when(mUserManager.getUserHandles(eq(true))).thenReturn(
- Arrays.asList(new UserHandle[] {
- new UserHandle(MOCK_USER1),
- new UserHandle(MOCK_USER2),
- }));
+ Arrays.asList(new UserHandle[] { MOCK_USER1, MOCK_USER2 }));
mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps));
@@ -184,7 +181,8 @@
return packageInfo;
}
- private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) {
+ private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid,
+ UserHandle user) {
final PackageInfo pkgInfo;
if (hasSystemPermission) {
pkgInfo = systemPackageInfoWithPermissions(
@@ -192,7 +190,7 @@
} else {
pkgInfo = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, new String[] {}, "");
}
- pkgInfo.applicationInfo.uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
+ pkgInfo.applicationInfo.uid = UserHandle.getUid(user, UserHandle.getAppId(uid));
return pkgInfo;
}
@@ -382,8 +380,8 @@
}).when(mockNetd).networkClearPermissionForUser(any(int[].class));
}
- public void expectPermission(Boolean permission, int[] users, int[] apps) {
- for (final int user : users) {
+ public void expectPermission(Boolean permission, UserHandle[] users, int[] apps) {
+ for (final UserHandle user : users) {
for (final int app : apps) {
final int uid = UserHandle.getUid(user, app);
if (!mApps.containsKey(uid)) {
@@ -396,8 +394,8 @@
}
}
- public void expectNoPermission(int[] users, int[] apps) {
- for (final int user : users) {
+ public void expectNoPermission(UserHandle[] users, int[] apps) {
+ for (final UserHandle user : users) {
for (final int app : apps) {
final int uid = UserHandle.getUid(user, app);
if (mApps.containsKey(uid)) {
@@ -425,46 +423,48 @@
// Add SYSTEM_PACKAGE2, expect only have network permission.
mPermissionMonitor.onUserAdded(MOCK_USER1);
- addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID);
- mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+ addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID);
+ mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID});
// Add SYSTEM_PACKAGE1, expect permission escalate.
- addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID);
- mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+ addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID);
+ mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID});
mPermissionMonitor.onUserAdded(MOCK_USER2);
- mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID});
- addPackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
- mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+ addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
+ mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID});
- mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2},
new int[]{MOCK_UID1});
// Remove MOCK_UID1, expect no permission left for all user.
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
- removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
- mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1});
+ removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
+ mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
+ new int[]{MOCK_UID1});
// Remove SYSTEM_PACKAGE1, expect permission downgrade.
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2});
- removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, SYSTEM_PACKAGE1, SYSTEM_UID);
- mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+ removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2},
+ SYSTEM_PACKAGE1, SYSTEM_UID);
+ mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID});
mPermissionMonitor.onUserRemoved(MOCK_USER1);
- mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID});
+ mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER2}, new int[]{SYSTEM_UID});
// Remove all packages, expect no permission left.
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{});
- removePackageForUsers(new int[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID);
- mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+ removePackageForUsers(new UserHandle[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID);
+ mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID, MOCK_UID1});
// Remove last user, expect no redundant clearPermission is invoked.
mPermissionMonitor.onUserRemoved(MOCK_USER2);
- mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID, MOCK_UID1});
}
@@ -548,14 +548,14 @@
// Normal package add/remove operations will trigger multiple intent for uids corresponding to
// each user. To simulate generic package operations, the onPackageAdded/Removed will need to be
// called multiple times with the uid corresponding to each user.
- private void addPackageForUsers(int[] users, String packageName, int uid) {
- for (final int user : users) {
+ private void addPackageForUsers(UserHandle[] users, String packageName, int uid) {
+ for (final UserHandle user : users) {
mPermissionMonitor.onPackageAdded(packageName, UserHandle.getUid(user, uid));
}
}
- private void removePackageForUsers(int[] users, String packageName, int uid) {
- for (final int user : users) {
+ private void removePackageForUsers(UserHandle[] users, String packageName, int uid) {
+ for (final UserHandle user : users) {
mPermissionMonitor.onPackageRemoved(packageName, UserHandle.getUid(user, uid));
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 278d93a..e715480 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -34,8 +34,10 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.net.LinkProperties;
+import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import androidx.test.filters.SmallTest;
@@ -73,6 +75,11 @@
}
@Test
+ public void testEnterStateDoesNotCancelSafemodeAlarm() {
+ verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ }
+
+ @Test
public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
mGatewayConnection
.getUnderlyingNetworkTrackerCallback()
@@ -81,6 +88,7 @@
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
verify(mIkeSession, never()).close();
+ verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -120,7 +128,24 @@
}
@Test
+ public void testMigratedTransformsAreApplied() throws Exception {
+ getChildSessionCallback()
+ .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform());
+ mTestLooper.dispatchAll();
+
+ for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) {
+ verify(mIpSecSvc)
+ .applyTunnelModeTransform(
+ eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
+ }
+ assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+ }
+
+ @Test
public void testChildOpenedRegistersNetwork() throws Exception {
+ // Verify scheduled but not canceled when entering ConnectedState
+ verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
final VcnChildSessionConfiguration mMockChildSessionConfig =
mock(VcnChildSessionConfiguration.class);
doReturn(Collections.singletonList(TEST_INTERNAL_ADDR))
@@ -162,22 +187,41 @@
for (int cap : mConfig.getAllExposedCapabilities()) {
assertTrue(nc.hasCapability(cap));
}
+
+ // Now that Vcn Network is up, notify it as validated and verify the Safemode alarm is
+ // canceled
+ mGatewayConnection.mNetworkAgent.onValidationStatus(
+ NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */);
+ verify(mSafemodeTimeoutAlarm).cancel();
}
@Test
public void testChildSessionClosedTriggersDisconnect() throws Exception {
+ // Verify scheduled but not canceled when entering ConnectedState
+ verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
getChildSessionCallback().onClosed();
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+ verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
+ // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
+ verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
}
@Test
public void testIkeSessionClosedTriggersDisconnect() throws Exception {
+ // Verify scheduled but not canceled when entering ConnectedState
+ verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
getIkeSessionCallback().onClosed();
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
verify(mIkeSession).close();
+
+ // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
+ verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index d936183..07282c9 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -61,6 +61,7 @@
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verify(mIkeSession).kill();
+ verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -73,6 +74,7 @@
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verify(mIkeSession).close();
verify(mIkeSession, never()).kill();
+ verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -92,6 +94,7 @@
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verify(mIkeSession).close();
+ verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -101,5 +104,11 @@
assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
verify(mIkeSession).close();
+ verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
+ }
+
+ @Test
+ public void testSafemodeTimeoutNotifiesCallback() {
+ verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 16181b6..49ce54d 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -48,7 +48,8 @@
.createIpSecTunnelInterface(
DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network);
mGatewayConnection.setTunnelInterface(tunnelIface);
- mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState);
+
+ // Don't need to transition to DisconnectedState because it is the starting state
mTestLooper.dispatchAll();
}
@@ -78,6 +79,7 @@
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+ verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -88,6 +90,7 @@
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+ verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -97,5 +100,6 @@
assertNull(mGatewayConnection.getCurrentState());
verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any());
+ verifySafemodeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
index d0fec55..22eab2a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -16,9 +16,9 @@
package com.android.server.vcn;
-import static com.android.server.vcn.VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS;
import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import androidx.test.filters.SmallTest;
@@ -28,8 +28,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.concurrent.TimeUnit;
-
/** Tests for VcnGatewayConnection.DisconnectedState */
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -40,6 +38,9 @@
mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession());
+ // ensure that mGatewayConnection has an underlying Network before entering
+ // DisconnectingState
+ mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_2);
mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState);
mTestLooper.dispatchAll();
}
@@ -49,12 +50,22 @@
getIkeSessionCallback().onClosed();
mTestLooper.dispatchAll();
- assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+ assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
+ verify(mMockIkeSession).close();
+ verify(mMockIkeSession, never()).kill();
+ verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
}
@Test
public void testTimeoutExpired() throws Exception {
- mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+ Runnable delayedEvent =
+ verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
+ // Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages
+ // (which are mocked here). Directly invoke the runnable instead. This is still sufficient,
+ // since verifyTeardownTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled
+ // with the correct delay.
+ delayedEvent.run();
mTestLooper.dispatchAll();
verify(mMockIkeSession).kill();
@@ -67,5 +78,11 @@
// Should do nothing; already tearing down.
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+ verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ }
+
+ @Test
+ public void testSafemodeTimeoutNotifiesCallback() {
+ verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
index 3f2b47c..6c26075 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
@@ -29,10 +29,14 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnectionTestBase {
+ private long mFirstRetryInterval;
+
@Before
public void setUp() throws Exception {
super.setUp();
+ mFirstRetryInterval = mConfig.getRetryInterval()[0];
+
mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
mGatewayConnection.transitionTo(mGatewayConnection.mRetryTimeoutState);
mTestLooper.dispatchAll();
@@ -46,6 +50,7 @@
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+ verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
}
@Test
@@ -56,6 +61,7 @@
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
+ verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, false /* expectCanceled */);
}
@Test
@@ -66,13 +72,28 @@
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+ verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
}
@Test
public void testTimeoutElapsingTriggersRetry() throws Exception {
- mTestLooper.moveTimeForward(mConfig.getRetryIntervalsMs()[0]);
+ final Runnable delayedEvent =
+ verifyRetryTimeoutAlarmAndGetCallback(
+ mFirstRetryInterval, false /* expectCanceled */);
+
+ // Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages
+ // (which are mocked here). Directly invoke the runnable instead. This is still sufficient,
+ // since verifyRetryTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled
+ // with the correct delay.
+ delayedEvent.run();
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+ verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
+ }
+
+ @Test
+ public void testSafemodeTimeoutNotifiesCallback() {
+ verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index bc6bee2..748c792 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -132,10 +132,32 @@
@Test
public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() {
+ verifyWakeLockSetUp();
+
final TelephonySubscriptionSnapshot updatedSnapshot =
mock(TelephonySubscriptionSnapshot.class);
mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot);
verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot));
+ verifyWakeLockAcquired();
+
+ mTestLooper.dispatchAll();
+
+ verifyWakeLockReleased();
+ }
+
+ @Test
+ public void testNonNullUnderlyingNetworkRecordUpdateCancelsAlarm() {
+ mGatewayConnection
+ .getUnderlyingNetworkTrackerCallback()
+ .onSelectedUnderlyingNetworkChanged(null);
+
+ verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
+
+ mGatewayConnection
+ .getUnderlyingNetworkTrackerCallback()
+ .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
+
+ verify(mDisconnectRequestAlarm).cancel();
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index d449eab..ac9ec06 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -20,10 +20,16 @@
import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
import static com.android.server.vcn.VcnTestUtils.setupIpSecManager;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.annotation.NonNull;
import android.content.Context;
@@ -42,12 +48,16 @@
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.os.ParcelUuid;
+import android.os.PowerManager;
import android.os.test.TestLooper;
+import com.android.internal.util.State;
+import com.android.internal.util.WakeupMessage;
import com.android.server.IpSecService;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
+import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock;
import org.junit.Before;
import org.mockito.ArgumentCaptor;
@@ -55,6 +65,7 @@
import java.net.InetAddress;
import java.util.Collections;
import java.util.UUID;
+import java.util.concurrent.TimeUnit;
public class VcnGatewayConnectionTestBase {
protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID());
@@ -68,6 +79,7 @@
protected static final int TEST_IPSEC_TRANSFORM_RESOURCE_ID = 2;
protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 3;
protected static final int TEST_SUB_ID = 5;
+ protected static final long ELAPSED_REAL_TIME = 123456789L;
protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE";
protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 =
new UnderlyingNetworkRecord(
@@ -94,6 +106,11 @@
@NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback;
@NonNull protected final VcnGatewayConnection.Dependencies mDeps;
@NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+ @NonNull protected final VcnWakeLock mWakeLock;
+ @NonNull protected final WakeupMessage mTeardownTimeoutAlarm;
+ @NonNull protected final WakeupMessage mDisconnectRequestAlarm;
+ @NonNull protected final WakeupMessage mRetryTimeoutAlarm;
+ @NonNull protected final WakeupMessage mSafemodeTimeoutAlarm;
@NonNull protected final IpSecService mIpSecSvc;
@NonNull protected final ConnectivityManager mConnMgr;
@@ -110,6 +127,11 @@
mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class);
mDeps = mock(VcnGatewayConnection.Dependencies.class);
mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class);
+ mWakeLock = mock(VcnWakeLock.class);
+ mTeardownTimeoutAlarm = mock(WakeupMessage.class);
+ mDisconnectRequestAlarm = mock(WakeupMessage.class);
+ mRetryTimeoutAlarm = mock(WakeupMessage.class);
+ mSafemodeTimeoutAlarm = mock(WakeupMessage.class);
mIpSecSvc = mock(IpSecService.class);
setupIpSecManager(mContext, mIpSecSvc);
@@ -125,6 +147,20 @@
doReturn(mUnderlyingNetworkTracker)
.when(mDeps)
.newUnderlyingNetworkTracker(any(), any(), any(), any(), any());
+ doReturn(mWakeLock)
+ .when(mDeps)
+ .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
+
+ setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM);
+ setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM);
+ setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM);
+ setUpWakeupMessage(mSafemodeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM);
+
+ doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime();
+ }
+
+ private void setUpWakeupMessage(@NonNull WakeupMessage msg, @NonNull String cmdName) {
+ doReturn(msg).when(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(cmdName), any());
}
@Before
@@ -166,4 +202,80 @@
verify(mDeps).newIkeSession(any(), any(), any(), any(), captor.capture());
return (VcnChildSessionCallback) captor.getValue();
}
+
+ protected void verifyWakeLockSetUp() {
+ verify(mDeps).newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
+ verifyNoMoreInteractions(mWakeLock);
+ }
+
+ protected void verifyWakeLockAcquired() {
+ verify(mWakeLock).acquire();
+ verifyNoMoreInteractions(mWakeLock);
+ }
+
+ protected void verifyWakeLockReleased() {
+ verify(mWakeLock).release();
+ verifyNoMoreInteractions(mWakeLock);
+ }
+
+ private Runnable verifyWakeupMessageSetUpAndGetCallback(
+ @NonNull String tag,
+ @NonNull WakeupMessage msg,
+ long delayInMillis,
+ boolean expectCanceled) {
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(tag), runnableCaptor.capture());
+
+ verify(mDeps, atLeastOnce()).getElapsedRealTime();
+ verify(msg).schedule(ELAPSED_REAL_TIME + delayInMillis);
+ verify(msg, expectCanceled ? times(1) : never()).cancel();
+
+ return runnableCaptor.getValue();
+ }
+
+ protected Runnable verifyTeardownTimeoutAlarmAndGetCallback(boolean expectCanceled) {
+ return verifyWakeupMessageSetUpAndGetCallback(
+ VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM,
+ mTeardownTimeoutAlarm,
+ TimeUnit.SECONDS.toMillis(VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS),
+ expectCanceled);
+ }
+
+ protected Runnable verifyDisconnectRequestAlarmAndGetCallback(boolean expectCanceled) {
+ return verifyWakeupMessageSetUpAndGetCallback(
+ VcnGatewayConnection.DISCONNECT_REQUEST_ALARM,
+ mDisconnectRequestAlarm,
+ TimeUnit.SECONDS.toMillis(
+ VcnGatewayConnection.NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS),
+ expectCanceled);
+ }
+
+ protected Runnable verifyRetryTimeoutAlarmAndGetCallback(
+ long delayInMillis, boolean expectCanceled) {
+ return verifyWakeupMessageSetUpAndGetCallback(
+ VcnGatewayConnection.RETRY_TIMEOUT_ALARM,
+ mRetryTimeoutAlarm,
+ delayInMillis,
+ expectCanceled);
+ }
+
+ protected Runnable verifySafemodeTimeoutAlarmAndGetCallback(boolean expectCanceled) {
+ return verifyWakeupMessageSetUpAndGetCallback(
+ VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM,
+ mSafemodeTimeoutAlarm,
+ TimeUnit.SECONDS.toMillis(VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS),
+ expectCanceled);
+ }
+
+ protected void verifySafemodeTimeoutNotifiesCallback(@NonNull State expectedState) {
+ // Safemode timer starts when VcnGatewayConnection exits DisconnectedState (the initial
+ // state)
+ final Runnable delayedEvent =
+ verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ delayedEvent.run();
+ mTestLooper.dispatchAll();
+
+ verify(mGatewayStatusCallback).onEnteredSafemode();
+ assertEquals(expectedState, mGatewayConnection.getCurrentState());
+ }
}